[Plaid CTF 2016] quite quixotic quest (Re300)

Hãy chuẩn bị tinh thần, vì đây là bài 300 điểm của một giải có rating 90.

2016fd1c9ed8-0e7f-40f3-b2b9-a23104c6361e

quite quixotic quest

Reversing (300 pts)

Well yes, it certainly is quite quixotic. (Yes, the flag format is PCTF{} )

Link dự phòng: quixotic

Đề bài là một file ELF 32 bit. Mặc dù đã có trong tay IDA phiên bản hịn (mà cả thế giới đều có), mình vẫn ưa 32 bit. Cười trong ô tô thì vẫn thích hơn là khóc trên xe đạp, right?

Xem danh sách string, dễ thấy nó chính là curl cải trang:

➜ Desktop ./qqq
curl: try 'curl --help' or 'curl --manual' for more information

Mình đã định dùng BinDiff, nhưng thật thô bỉ khi đồng đội của mình chỉ cho mình cái này:

.rodata:08121788 0000004A C --pctfkey KEY Validate KEY as the PlaidCTF flag for this challenge

Tức là flag sẽ được nhập qua option –pctfkey. No fun.

➜ Desktop ./qqq --pctf "PCTF{đồ ngốc}"
Validating key...
wrong

Still no fun, chuỗi Validating key… kia đáng ra cần được che giấu kỹ càng hơn. Mình hoàn toàn cảm thấy bị xúc phạm khi mọi thứ cứ thẳng tuồn tuột như thế.

.text:08052AAC mov dword ptr [esp], offset aValidatingKey_ ; "Validating key...\n"
.text:08052AB3 call curl_mprintf ; Call Procedure
.text:08052AB8 mov edx, [ebx+128h]
.text:08052ABE mov eax, offset magic_buf
.text:08052AC3 mov esp, eax
.text:08052AC5 mov eax, edx
.text:08052AC7 retn

Vì EAX = magic_buf, rồi ESP = EAX, nên ESP = magic_buf. Sau khi debug một chút, chúng ta thấy rằng (có vẻ như) chương trình thực thi một rop chain (trỏ bởi magic_buf). Sau khi step over một hồi, chúng ta sẽ gặp badboy, được build từng char một (‘w’, ‘r’, ‘o’, ‘n’, ‘g’) bằng câu lệnh:

.text:08066CB9 mov eax, offset hostname_cache
.text:0804E242 pop edx
.text:08095621 mov [eax], edx
.text:080E161F inc eax
.text:0804E242 pop edx
.text:08095621 mov [eax], edx
.text:080E161F inc eax
.text:0804E242 pop edx
.text:08095621 mov [eax], edx
...

F8 không văn minh lắm, hãy chuyển qua chức năng trace của IDA. Đặt BP ở 0x08052AC7 và chạy lại, bật Instruction Tracing -> F9 -> Badboy -> Mở Trace window (mình quy ước rằng trace log mình post chỉ chứa những câu lệnh mà các bạn cần quan tâm):

00001567 .text:addbyter:loc_805DB7D add ebx, eax ; Add EBX=0000000C
00001567 .text:getparameter+2562 pop edx EDX=00000035 ESP=0818C0E0
00001567 .text:curlx_tvdiff+37 sub edx, ebx ; Integer Subtraction EDX=00000029 PF=0 AF=1
00001567 .text:curlx_tvdiff+3C pop ebx EBX=00000000 ESP=0818C0E8
00001567 .text:_Unwind_GetDataRelBase+6 pop eax EAX=00000094 ESP=0818C0F0
00001567 .text:malloc_usable_size+1E cmovnz eax, ebx ; Move if Not Zero (ZF=0) EAX=00000000
00001567 .text:memcpy:loc_80AD35D mov edi, eax EDI=00000000
00001567 .eh_frame:081887E4 add esp, edi ; Add AF=0

Ta thấy EBX = len(key) (mình đang nhập key PCTF{123456}), sau đó EDX -= EBX, tiếp theo EAX = 0x94 và EBX = 0. Nếu cờ ZF = 0 thì lệnh CMOVNZ làm cho EAX = EBX = 0, ngược lại EAX giữ nguyên = 0x94. Sau đó EDI = EAX và ESP += EDI.

Do cờ ZF được quyết định bởi lệnh SUB EDX, EBX, nên kết quả của phép trừ EDX (0x35) cho EBX (len(key)) sẽ ảnh hưởng trực tiếp đến luồng thực thi của chương trình (hoặc ESP += 0, hoặc ESP += 0x94). Như vậy, len(key) = 0x35.

yOy5OI1

Nhập key mới (PCTF{1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJK}) và trace lại:

00001572 .text:__underflow:loc_80A40C9 movzx eax, byte ptr [eax]; Move with Zero-Extend EAX=0000007D
00001572 .eh_frame:loc_8187036 pop ecx ECX=00000000 ESP=0818C258
00001572 .text:__guess_grouping+3C add ecx, eax ; Add ECX=0000007D PF=1
00001572 .text:getparameter+2562 pop edx EDX=081CCF10 ESP=0818C26C
00001572 .eh_frame:081887E6 add [edx], ecx ; Add CF=0 PF=1
00001572 .text:__underflow:loc_80A40C9 movzx eax, byte ptr [eax]; Move with Zero-Extend EAX=0000004B
00001572 .eh_frame:loc_8187036 pop ecx ECX=00000000 ESP=0818C258
00001572 .text:__guess_grouping+3C add ecx, eax ; Add ECX=0000004B PF=1
00001572 .text:getparameter+2562 pop edx EDX=081CCF10 ESP=0818C26C
00001572 .eh_frame:081887E6 add [edx], ecx ; Add CF=0 AF=1

Đoạn code này làm nhiệm vụ cộng từng ký tự của key vào [EDX]. Khi đã nắm được ý tưởng, ta có thể filter log trace, hoặc đặt BP và F9 để qua nhanh nhưng vẫn đảm bảo việc theo dõi được vòng lặp. Thực tế thì chương trình chỉ tính sum(key[1:]) mà thôi.

00001572 .text:memcpy+4F mov esi, edx ESI=081CCF10
00001572 .text:__strnlen_sse2+6CE ror byte ptr [edx], 5Fh; Rotate Right OF=1
00001572 .eh_frame:08178748 rol dword ptr [edx], 1; Rotate Left OF=0
00001572 .eh_frame:loc_8187036 pop ecx ECX=01F9933D ESP=0818C29C
00001572 .text:gmtime_r+1B xor [esi], ecx ; Logical Exclusive OR CF=0 AF=0
00001572 .text:__strcpy_ssse3+173C mov eax, edx EAX=081CCF10
00001572 .text:Curl_has:loc_807BD79 mov eax, [eax] EAX=01F9B731
00001572 .text:getcwd+668 xor eax, 0C7FFFFFAh ; Logical Exclusive OR EAX=C60648CB AF=0 SF=1
00001572 .text:memcpy:loc_80AD35D mov edi, eax EDI=C60648CB
00001572 .text:_IO_new_file_seekoff+1A1 mov edx, edi EDX=C60648CB
00001572 .eh_frame:08176458 pop eax EAX=081CCF20 ESP=0819C2D8
00001572 .text:_Unwind_SetIP+8 mov [eax+4Ch], edx
00001572 .text:Curl_multi_content...+12 xor edx, edx ; Logical Exclusive OR EDX=00000000 PF=1 ZF=1 SF=0
00001572 .text:__strnlen_sse2+4C9 lea eax, [eax+4] ; Load Effective Address EAX=081CCF24
00001572 .text:_Unwind_SetIP+8 mov [eax+4Ch], edx
00001721 .text:Curl_md5it push esi ESP=0819C3A8
...
MD5_Update
...
MD5Final
...

Giá trị 01F9933D khi đổi qua DEC sẽ là 33133373, không có gì đặc biệt, mình chỉ nhắc thế thôi. Hãy chuyển sang debug để quan sát stack và các vùng nhớ liên quan. Tại hàm Curl_md5it, tham số thứ 2 trỏ đến 081CCF6C, đúng bằng [eax+4Ch], và chuỗi MD5 kết quả sẽ lưu ở 081CE9C0. Tổng kết lại, đến thời điểm này chúng ta có giả mã sau:

s = sum(key[1:])
s = ror(s, 0x5f)
s = rol(s, 1)
s ^= 0x01F9933D
s ^= 0x0C7FFFFFA

[0x081CCF6C] = s
[0x081CE9C0] = md5([0x081CCF6C])

Tiếp tục:

00001728 .text:MD5_Final+1FF pop esi ESI=081CE9C0 ESP=0819C3B4
00001728 .text:smtp_endofresp+53 mov eax, esi EAX=081CE9C0
00001728 .text:Curl_has...:loc_807BD79 mov eax, [eax] EAX=FEBA6E76
00001728 .text:__registe...+7E pop edi EDI=86F4FA3F ESP=0819C3D8
00001728 .text:__readvall+2C xor eax, edi ; Logical Exclusive OR EAX=784E9449
00001728 .text:__strncpy_ssse3+1F80 cmp eax, 5BFFFFFFh ; Compare Two Operands AF=1

Mã giả tương ứng:

eax = md5_hash[0]
eax ^= 0x86F4FA3F
eax == 0x5BFFFFFF

Đoạn code sau sẽ cho ta biết giá trị của s:

for i in xrange(0xFF * 0x34):
    s = i
    s = (s & 0xFFFFFF00) | ror(s & 0xFF, 0x5f, 8)
    s = rol(s, 1, 32)
    s ^= 0x01F9933D
    s ^= 0x0C7FFFFFA

    m = unpack('I', md5(pack('I', s))[:8].decode('hex'))[0]
    if m == 0x5BFFFFFF ^ 0x86F4FA3F:
        print 'found:', i, md5(pack('I', s))
        break
found: 5215 c0050bdd747721646f14ff008c6978b9

Input lại một key phù hợp (PCTF{eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeed}).

00001738 .text:getparameter+2562 pop edx EDX=081CCF38 ESP=081AC3F4
00001738 .text:get_scope:loc_80C91EF mov edx, [edx+8] EDX=082045F0
00001738 .text:multi_get...:loc_8061D38 xor eax, eax ; Logical Exclusive OR EAX=00000000 PF=1 ZF=1
00001738 .text:customrequest_helper+65 xor al, [ebx+5E5B30C4h]; Logical Exclusive OR EAX=000000C0 ZF=0 SF=1
00001738 .eh_frame:081768B5 xor [edx], al ; Logical Exclusive OR
00001738 .text:_nl_...+144 inc ebx ; Increment by 1 EBX=A9C1B90A SF=1
00001738 .text:__memset_sse2_rep+497 inc edx ; Increment by 1 EDX=0883B60E SF=0 OF=0
00001738 .text:multi_get...:loc_8061D38 xor eax, eax ; Logical Exclusive OR EAX=00000000 PF=1 ZF=1
00001738 .text:customrequest_helper+65 xor al, [ebx+5E5B30C4h]; Logical Exclusive OR EAX=000000C0 ZF=0 SF=1
00001738 .eh_frame:081768B5 xor [edx], al ; Logical Exclusive OR
...

Đoạn trên là một vòng lặp, với EDX sẽ trỏ đến key và EBX+5E5B30C4 trỏ đến chuỗi MD5.
Sắp xong rồi, cố lên >:(

36
00001759 .text:Curl_multi_...+12 xor edx, edx ; Logical Exclusive OR EDX=00000000 CF=0 PF=1 ZF=1
00001759 .text:mu...:loc_8061D38 xor eax, eax ; Logical Exclusive OR EAX=00000000
00001759 .eh_frame:08187951 or eax, [ebx+0Ah] ; Logical Inclusive OR EAX=9B5F4690 ZF=0 SF=1
00001759 .eh_frame:loc_8187036 pop ecx ECX=9B5F4690 ESP=081AC948
00001759 .text:__strcmp_ssse3+13E8 sub eax, ecx ; Integer Subtraction EAX=00000000 ZF=1 SF=0
00001759 .text:isnanl+3C or edx, eax ; Logical Inclusive OR
00001759 .text:0805D7B4 add ebx, esi ; Add EBX=0883B5F6 PF=1
...
00001759 .text:multi...:loc_8061D38 xor eax, eax ; Logical Exclusive OR PF=1 ZF=1
00001759 .eh_frame:08187951 or eax, [ebx+0Ah] ; Logical Inclusive OR EAX=0144120F ZF=0
00001759 .eh_frame:loc_8187036 pop ecx ECX=17541D0F ESP=081AC964
00001759 .text:__strcmp_ssse3+13E8 sub eax, ecx ; Integer Subtraction EAX=E9EFF500 CF=1 SF=1
00001759 .text:isnanl+3C or edx, eax ; Logical Inclusive OR EDX=E9EFF500 CF=0
00001759 .text:0805D7B4 add ebx, esi ; Add EBX=0883B5FA CF=0
...

Debug để thấy giá trị các biến, ta thấy rằng chương trình thực hiện việc lấy từng dword trong key (sau khi đã xor với chuỗi MD5), rồi trừ cho ECX, và OR vào EDX. Khi bắt đầu, EDX = 0. Các giá trị của ECX lần lượt như sau:

ecx = [0x9b5f4690, 0x17541d0f, 0x5F9E4B1B, 0xCD0C58E0, 0xA95460AC, 0x034F1E1C, 0x6CA02530, 0xE61D02BD, 0xBE5435B4, 0x3B4D1B15, 0x668F7B1D, 0xD81B1AF9, 0xB3646CB4, 0x00000009]
00001759 .text:_IO_default_showmanyc mov eax, 0FFFFFFFFh EAX=FFFFFFFF
00001759 .text:isinfl+55 and eax, edx ; Logical AND SF=1
00001759 .text:smtp_multi_statemach+25 pop ebx EBX=FFFDF620 ESP=081ACAD0
00001759 .text:malloc_usable_size+1E cmovnz eax, ebx ; Move if Not Zero (ZF=0) EAX=FFFDF620
00001759 .text:memcpy:loc_80AD35D mov edi, eax EDI=FFFDF620
00001759 .eh_frame:081887E4 add esp, edi ; Add ESP=0818C108 CF=1 PF=0 SF=0

Ở đây lại là một trick tương tự mà mình đã nhắc đến phần đầu bài, khi mà cờ ZF sẽ ảnh hưởng đến luồng thực thi của chương trình. Cờ ZF được quyết định ở câu lệnh AND EAX, EDX, và vì EAX = 0xFFFFFFFF, EDX cần bằng 0 để cờ này được bật.
Ta nhớ rằng EDX là phép OR của mọi giá trị EAX – ECX, do đó để EDX = 0 thì mọi hiệu EAX – ECX phải bằng 0, hay nói cách khác, EAX phải bằng ECX. Đoạn code sau, như mọi khi, mang lại cho chúng ta flag:

h = 'c0050bdd747721646f14ff008c6978b9'.decode('hex')
ecx = [0x9b5f4690, 0x17541d0f, 0x5F9E4B1B, 0xCD0C58E0, 0xA95460AC, 0x034F1E1C, 0x6CA02530, 0xE61D02BD, 0xBE5435B4, 0x3B4D1B15, 0x668F7B1D, 0xD81B1AF9, 0xB3646CB4, 0x00000009]
flag = ''
for i in xrange(len(ecx)):
    hi = i % 4
    s = pack('I', unpack('I', h[hi * 4:hi * 4 + 4])[0] ^ ecx[i])
    flag += s
print flag[:0x35]

Nếu bạn thắc mắc tại sao với cách làm hư cấu thế này, mà mình lại có thể trở thành người giải ra thứ hai (:”>), thì bạn thắc mắc đúng rồi đấy. Trong lúc thi mình không hề dùng cách này. Hihi.

32

You may also like...

3 Responses

  1. Tạ Quang Khánh says:

    Thực sự cảm thấy hư cấu…

  2. Ngọc Au says:

    Cho e hỏi a Tạ Quang Khánh học at9B phải ko ạ

Leave a Reply

Your email address will not be published. Required fields are marked *