Notice
Recent Posts
Recent Comments
Link
«   2026/02   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
Archives
Today
Total
관리 메뉴

CTF PRE

[dreamhack] level 1 아 문제 이름 뭘로하지 문제 풀이.. 본문

카테고리 없음

[dreamhack] level 1 아 문제 이름 뭘로하지 문제 풀이..

hstuak 2025. 3. 25. 02:49

 

ida로 디컴파일 해보니 사용자에게 입력을 받고, s를 파라미터로 하는 함수인 sub_145A의 값이 참이면 correct 출력, 아니라면 wrong을 출력한다. 해당 문제는 어떤 값을 입력해야 correct가 나오는지 알아내야 하는 문제 같다. 

sub_145A 함수 분석

만약 input 값의 길이가 39가 아니라면 0을 반환

v2~v7 : 순서대로 input + 8, 24, 32, 36, 38을 연산한 값을 저장

s1[0~3] : 순서대로 Input,  v2, input + 16, v3를 저장

sub_1273 함수 호출

sub_139D 함수 호출

s1과 s2의 첫 0x27 바이트를 비교하여 결과가 0이라면 참(1)을 반환하고 아니라면 거짓(2)를 반환.

 

** main 함수에서 correct가 나오려면 memcmp 비교 결과가 0이어야 함. **

 

sub_1273 함수 분석

1바이트씩 해석함. 만약 i가 0x27보다 크거나 같으면 break

byte_2020[(unsigned __int8)(((-83 - (i & 7)) * ((i & 7) - 34)) ^ i)] + (*(_BYTE *)(s1 + i) ^ (unsigned __int8)(i + (-83 - (i & 7)) * ((i & 7) - 34))), (unsigned __int8)(i % 7) + 3 의 연산값을 인자로 하는 sub_123E 함수의 반환값을 v2에 저장 

 

unsigned char ida_chars[] =

{

99, 124, 119, 123, 242, 107, 111, 197, 48, 1,

103, 43, 254, 215, 171, 118, 202, 130, 201, 125,

250, 89, 71, 240, 173, 212, 162, 175, 156, 164,

114, 192, 183, 253, 147, 38, 54, 63, 247, 204,

52, 165, 229, 241, 113, 216, 49, 21, 4, 199,

35, 195, 24, 150, 5, 154, 7, 18, 128, 226,

235, 39, 178, 117, 9, 131, 44, 26, 27, 110,

90, 160, 82, 59, 214, 179, 41, 227, 47, 132,

83, 209, 0, 237, 32, 252, 177, 91, 106, 203,

190, 57, 74, 76, 88, 207, 208, 239, 170, 251,

67, 77, 51, 133, 69, 249, 2, 127, 80, 60,

159, 168, 81, 163, 64, 143, 146, 157, 56, 245,

188, 182, 218, 33, 16, 255, 243, 210, 205, 12,

19, 236, 95, 151, 68, 23, 196, 167, 126, 61,

100, 93, 25, 115, 96, 129, 79, 220, 34, 42,

144, 136, 70, 238, 184, 20, 222, 94, 11, 219,

224, 50, 58, 10, 73, 6, 36, 92, 194, 211,

172, 98, 145, 149, 228, 121, 231, 200, 55, 109,

141, 213, 78, 169, 108, 86, 244, 234, 101, 122,

174, 8, 186, 120, 37, 46, 28, 166, 180, 198,

232, 221, 116, 31, 75, 189, 139, 138, 112, 62,

181, 102, 72, 3, 246, 14, 97, 53, 87, 185,

134, 193, 29, 158, 225, 248, 152, 17, 105, 217,

142, 148, 155, 30, 135, 233, 206, 85, 40, 223,

140, 161, 137, 13, 191, 230, 66, 104, 65, 153,

45, 15, 176, 84, 187, 22

};

 

sub_123E 함수 분석

해당 함수는 a1, a2에 대해. 비트 쉬프트 연산을 한다.

 

sub_139D 함수 분석

0x26만큼 반복하고 특정 연산을 한 뒤에 sub_1273 함수를 실행한다. 그리고 v9 - _readfsqword 연산을 수행한 값을 반환한다.

 

unsigned char ida_chars[] =

{

238, 226, 209, 250, 198, 207, 203, 217, 207, 245,

198, 197, 220, 207, 245, 222, 194, 207, 245, 237,

238, 245, 255, 232, 239, 248, 231, 239, 249, 226,

245, 203, 198, 200, 223, 199, 139, 139, 215

};

 

위의 함수들을 활용하여 모두 역연산하면 될 것 같다. 

 

역연산 코드

def rotate_right(value, amount):
    """Rotate bits to the right (equivalent to sub_123E)
    Handles negative shift counts by converting to positive"""
    # Ensure amount is positive and within range
    amount = amount % 8
    return ((value >> amount) | (value << (8 - amount))) & 0xFF


def rotate_left(value, amount):
    """Rotate bits to the left (inverse of sub_123E)
    Handles negative shift counts by converting to positive"""
    # Ensure amount is positive and within range
    amount = amount % 8
    return ((value << amount) | (value >> (8 - amount))) & 0xFF


# S-box from the disassembly
byte_2020 = [
    99, 124, 119, 123, 242, 107, 111, 197, 48, 1,
    103, 43, 254, 215, 171, 118, 202, 130, 201, 125,
    250, 89, 71, 240, 173, 212, 162, 175, 156, 164,
    114, 192, 183, 253, 147, 38, 54, 63, 247, 204,
    52, 165, 229, 241, 113, 216, 49, 21, 4, 199,
    35, 195, 24, 150, 5, 154, 7, 18, 128, 226,
    235, 39, 178, 117, 9, 131, 44, 26, 27, 110,
    90, 160, 82, 59, 214, 179, 41, 227, 47, 132,
    83, 209, 0, 237, 32, 252, 177, 91, 106, 203,
    190, 57, 74, 76, 88, 207, 208, 239, 170, 251,
    67, 77, 51, 133, 69, 249, 2, 127, 80, 60,
    159, 168, 81, 163, 64, 143, 146, 157, 56, 245,
    188, 182, 218, 33, 16, 255, 243, 210, 205, 12,
    19, 236, 95, 151, 68, 23, 196, 167, 126, 61,
    100, 93, 25, 115, 96, 129, 79, 220, 34, 42,
    144, 136, 70, 238, 184, 20, 222, 94, 11, 219,
    224, 50, 58, 10, 73, 6, 36, 92, 194, 211,
    172, 98, 145, 149, 228, 121, 231, 200, 55, 109,
    141, 213, 78, 169, 108, 86, 244, 234, 101, 122,
    174, 8, 186, 120, 37, 46, 28, 166, 180, 198,
    232, 221, 116, 31, 75, 189, 139, 138, 112, 62,
    181, 102, 72, 3, 246, 14, 97, 53, 87, 185,
    134, 193, 29, 158, 225, 248, 152, 17, 105, 217,
    142, 148, 155, 30, 135, 233, 206, 85, 40, 223,
    140, 161, 137, 13, 191, 230, 66, 104, 65, 153,
    45, 15, 176, 84, 187, 22
]

# Target bytes from the disassembly
byte_2120 = [
    238, 226, 209, 250, 198, 207, 203, 217, 207, 245,
    198, 197, 220, 207, 245, 222, 194, 207, 245, 237,
    238, 245, 255, 232, 239, 248, 231, 239, 249, 226,
    245, 203, 198, 200, 223, 199, 139, 139, 215
]


def sub_123E(a1, a2):
    """Implementation of the sub_123E function according to the disassembly

    Based on the assembly, this seems to be a right rotate:
    return ((a1 >> a2) | (a1 << (8 - a2))) & 0xFF

    But looking at the disassembly, it might actually be different.
    Let's check the original code:
    ```
    __int64 __fastcall sub_123E(unsigned __int8 a1, char a2)
    {
      return ((int)a1 >> a2) | (a1 << (8 - a2));
    }
    ```

    This seems to be a rotate right, but let's verify it's a right rotate.
    """
    # Ensure a2 is a valid positive shift amount
    a2 = a2 & 0x7  # Same as a2 % 8, but faster
    return ((a1 >> a2) | (a1 << (8 - a2))) & 0xFF


def calculate_xor_value(i):
    """Calculate the XOR value used in sub_1273
    This needs to match the assembly's computation exactly"""
    # The assembly code: (-83 - (i & 7)) * ((i & 7) - 34)
    # We need to make sure this matches the binary's computation

    # Ensure we're doing the computation exactly as in the binary
    term1 = (-83 - (i & 7)) & 0xFF  # Keep in byte range
    term2 = ((i & 7) - 34) & 0xFF  # Keep in byte range
    result = (term1 * term2) & 0xFF  # Multiplication can overflow, so mask
    return (result ^ i) & 0xFF


def sub_1273(s1, length):
    """Properly implemented sub_1273 function with correct byte range handling"""
    v4 = 0

    for i in range(length):
        # Calculate the index into byte_2020
        xor_value = calculate_xor_value(i)
        index = xor_value & 0xFF

        # Ensure index is in valid range
        index = index % len(byte_2020)

        # Get value from byte_2020 and calculate
        sbox_value = byte_2020[index]
        input_byte = s1[i]
        i_plus_xor = (i + xor_value) & 0xFF
        sum_value = (sbox_value + (input_byte ^ i_plus_xor)) & 0xFF

        # Rotate the bits
        rot_amount = (i % 7) + 3
        v2 = sub_123E(sum_value, rot_amount)

        # Update the current byte and v4
        new_byte = ((i + v4) ^ ((3 * v2 + 7) & 0xFF)) & 0xFF
        s1[i] = new_byte
        v4 = new_byte

    return length


def reverse_sub_1273(encrypted, length):
    """Reverse the transformations of sub_1273"""
    original = bytearray(length)
    v4 = 0

    for i in range(length):
        # Extract the current encrypted byte
        encrypted_byte = encrypted[i]

        # Recover what v2 would have been using the relationship:
        # encrypted_byte = (i + v4) ^ (3 * v2 + 7)
        v4_contribution = (i + v4) & 0xFF
        target = (v4_contribution ^ encrypted_byte) & 0xFF

        # Find v2 such that (3 * v2 + 7) & 0xFF == target
        v2 = None
        for test_v2 in range(256):
            if ((3 * test_v2 + 7) & 0xFF) == target:
                v2 = test_v2
                break

        if v2 is None:
            raise ValueError(f"Could not find v2 for position {i}")

        # Undo the rotation
        rot_amount = (i % 7) + 3
        # For rotate right with amount 'a', the inverse is rotate left with amount 'a'
        unrotated = rotate_left(v2, rot_amount)

        # Calculate the XOR value used in the original function
        xor_value = calculate_xor_value(i)

        # Find the sbox value
        index = xor_value & 0xFF
        index %= len(byte_2020)
        sbox_value = byte_2020[index]

        # Recover the original input byte
        # We had: sum_value = (sbox_value + (input_byte ^ (i + xor_value))) & 0xFF
        # And: unrotated = sum_value
        # So: input_byte = (unrotated - sbox_value) ^ (i + xor_value)
        i_plus_xor = (i + xor_value) & 0xFF
        original_byte = ((unrotated - sbox_value) & 0xFF) ^ i_plus_xor
        original[i] = original_byte

        # Update v4 for the next iteration
        v4 = encrypted_byte

    return original


def main():
    """Main function to recover the flag"""
    # First, XOR byte_2120 with 0xAA as done in sub_139D
    expected = bytearray(39)
    for i in range(39):
        expected[i] = byte_2120[i] ^ 0xAA

    # Create a copy for processing
    processed = bytearray(expected)

    # Apply sub_1273 to see what should match in the memcmp
    sub_1273(processed, 0x27)

    # Now reverse the process to get the original flag
    flag_bytes = reverse_sub_1273(processed, 0x27)

    # Convert to ASCII and print
    flag = ''.join(chr(b) for b in flag_bytes)
    print("Recovered flag:", flag)

    # Verify our implementation is correct by applying sub_1273 to flag_bytes
    verification = bytearray(flag_bytes)
    sub_1273(verification, 0x27)

    # This should match the processed buffer
    if verification == processed:
        print("Verification successful - our implementation is correct!")
    else:
        print("Verification failed - our implementation might be incorrect")


if __name__ == "__main__":
    main()

 

flag : DH{Please_love_the_GD_UBERMESH_album!!}