EHC SVATTT 2023 Training

EHC SVATTT 2023 TRAINING

CRYPTOGRAPHY WRITEUP

Author:

  • Pham Quoc Trung

Used Language:

  • Python3

Problem Solving:

CRY301

server.py

import random
import socketserver
import sys


def easyone(x):
    assert(x < 2 ** 128)
    x ^= x >> (64 + 19)
    x *= 0xd3856e824d9c8a26aef65c0fe1cc96db
    x &= 0xffffffffffffffffffffffffffffffff
    x ^= x >> (64 + 3)
    x *= 0xe44035c8f8387dc11dd3dd67097007cb
    x &= 0xffffffffffffffffffffffffffffffff
    x ^= x >> (64 + 20)
    x *= 0xc9f54782b4f17cb68ecf11d7b378e445
    x &= 0xffffffffffffffffffffffffffffffff
    x ^= x >> (64 + 2)
    return x


def alittlebitharderone(x):
    assert(x < 2 ** 128)
    x ^= x >> 19
    x *= 0xd3856e824d9c8a26aef65c0fe1cc96db
    x &= 0xffffffffffffffffffffffffffffffff
    x ^= x >> 3
    x *= 0xe44035c8f8387dc11dd3dd67097007cb
    x &= 0xffffffffffffffffffffffffffffffff
    x ^= x >> 20
    x *= 0xc9f54782b4f17cb68ecf11d7b378e445
    x &= 0xffffffffffffffffffffffffffffffff
    x ^= x >> 2
    return x


def rewards():
    try:
        with open('flag.txt', 'rb') as f:
            flag = f.read()
            return b'Congrats, here is your flag: %s' % (flag)
    except Exception as e:
        print(e)
        return b'Server is not configured correctly. Please contact admins to fix the problem'


class RequestHandler(socketserver.StreamRequestHandler):

    def handle(self):
        try:
            secret_number = random.randint(2**127, 2**128)
            self.request.sendall(b'First round: ',)
            self.request.sendall(str(easyone(secret_number)).encode())
            self.request.sendall(b'\n')

            # Yes, I do allow you to try multiple times. But please
            # remember that this is NOT a bruteforce challenge.
            while True:
                try:
                    self.request.sendall(b'What is the secret number? ')
                    s = int(self.rfile.readline().decode())
                except ValueError:
                    self.request.sendall(
                        b'THIS IS A CRYPTOGRAPHIC CHALLENGE!!!\n')
                    continue

                if s != secret_number:
                    self.request.sendall(b'Oops\n')
                    continue

                break

            secret_number = random.randint(2**127, 2**128)
            self.request.sendall(b'Second round: ',)
            self.request.sendall(
                str(alittlebitharderone(secret_number)).encode())
            self.request.sendall(b'\n')

            while True:
                try:
                    self.request.sendall(b'What is the secret number? ')
                    s = int(self.rfile.readline().decode())
                except ValueError:
                    self.request.sendall(
                        b'THIS IS A CRYPTOGRAPHIC CHALLENGE!!!\n')
                    continue

                if s != secret_number:
                    self.request.sendall(b'Oops\n')
                    continue

                break

            # if you reach here, you deserve a reward!!!
            print("{} solved the challenge".format(self.client_address[0]))
            self.request.sendall(rewards())
            self.request.sendall(b'\n')

        except (ConnectionResetError, ConnectionAbortedError, BrokenPipeError):
            print("{} disconnected".format(self.client_address[0]))


class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    pass


def main(argv):
    host, port = '0.0.0.0', 8000

    if len(argv) == 2:
        port = int(argv[1])
    elif len(argv) >= 3:
        host, port = argv[1], int(argv[2])

    sys.stderr.write('Listening {}:{}\n'.format(host, port))
    server = ThreadedTCPServer((host, port), RequestHandler)
    server.daemon_threads = True
    server.allow_reuse_address = True
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        pass
    server.server_close()


if __name__ == '__main__':
    main(sys.argv)

Ở đây, chương trình sẽ in ra kết quả của hàm easyone(x) và bắt người dùng nhập vào giá trị $x$ ban đầu. Nếu đúng, chương trình sẽ trả về kết quả của hàm alittlebitharderone(x) và bắt người dùng nhập vào giá trị $x$ ban đầu. Nếu đúng lần nữa sẽ trả về flag.

Thử phân tích hàm easyone:

Ở đây, điều kiện của x là nhỏ hơn 128-bit. Để ý thì giá trị x được chương trình nhập vào cũng sẽ luôn là 128 bit (secret_number = random.randint(2 ** 127, 2 ** 128)) nên ta không cần lo đến lắm.

Tiếp theo là hàng loạt các phép tính, tuy nhiên gói gọn chỉ có phép XOR với x được dịch phải 1 số lượng nào đó, cùng với đó là phép nhân và XOR với 0xffffffffffffffffffffffffffffffff để giữ lại 128-bit. Vì vậy, mình chỉ cần viết hàm reverse 2 phép tính đó là được.

Nếu các bạn đã đọc wu mình viết nhiều lần thì có thể dễ dàng viết được 2 hàm này

Việc còn lại chỉ là sử dụng pwntool để connect tới server. Lưu ý code này các bạn có thể viết hoàn toàn ngắn hơn.

Flag: EHCCTF{5h1ft_t0_th3_d3ath}

CRY302

source.py

Giao diện sử dụng:

Ở đây, chương trình cho chúng ta 1 số tiền được giới hạn bằng dòng self.money = random.randint(1, 2000). Có thể thấy, số tiền này không thể đủ để mua FLAG ($99999). Vậy phải làm như nào?

Hãy thử phân tích đoạn code để tạo ra mã order:

Ở đây ta thấy order được cấu tạo từ 2 thành phần là payment và signature. Payment được cấu tạo từ 3 thành phần bao gồm tên product, price, và time là thời gian tạo order. Signature được tạo ra bằng cách mã hóa payment sử dụng SHA512 với self.key rồi chuyển sang hệ hexa. Payment được ghép với Signature bằng cụm b'&sign='. Order sẽ có dạng như này trước khi chuyển sang base64:

Khi mua chúng ta sẽ sử dụng hàm confirm

Cách hoạt động của nó như sau:

  • Chương trình sẽ decode đoạn base64, loại bỏ kí tự '\n'

  • Tìm vị trí của chuỗi '&sign='. Nếu không tìm thấy trả về "Invalid order\n"

  • Kiểm tra signature bằng cách mã hóa đoạn trước của '&sign=' bằng SHA512 và so sánh. Nếu không trùng, trả về "Invalid order\n"

  • Tạo ra 1 dict chứa các key và value tương ứng cho từng tham số của order. Thực hiện qua hàm parse_sql:

  • Gán giá trị của product, price vào pname, price. Nếu lỗi, trả về "Invalid order\n"

  • Cuối cùng, so sánh price với money của mình. Nếu nhỏ hơn => mua được => đưa cho người dùng sản phẩm tên pname

Ở đây, điều duy nhất chúng ta có thể làm là khai thác đoạn tạo ra order bởi trong đó có chứa giá trị price. Vì đây là SHA512, ta có thể nghĩ tới kỹ thuật Hash length Extension Attack.

Về lý thuyết, các bạn có thể xem trên CyberJutsu:

  • Vid 1: https://www.youtube.com/watch?v=9yOKVqayixM

  • Vid 2: https://www.youtube.com/watch?v=GnCTXf_avdo

Độ dài của secret key sẽ phải brute force. Malicious data của mình sẽ là &product=FLAG&price=1 để đảm bảo sau hàm parse_sql giá của FLAG sẽ là 1.

Để tạo payload, mình sẽ sử dụng tool sau https://github.com/viensea1106/hash-length-extension

Flag: EHCCTF{h45h_l3ngth_3xt3ns10n_4tt4ck}

© 2023,Pham Quoc Trung. All rights reserved.

Last updated