この大会は2021/7/24 16:30(JST)~2021/7/25 16:30(JST)に開催されました。
今回もチームで参戦。結果は1099点で132チーム中34位でした。
自分で解けた問題をWriteupとして書いておきます。
Sanity (Reverse Engineering)
$ gdb -q ./sanity Reading symbols from ./sanity...(no debugging symbols found)...done. gdb-peda$ start [----------------------------------registers-----------------------------------] RAX: 0x555555555165 (<main>: push rbp) RBX: 0x0 RCX: 0x5555555552b0 (<__libc_csu_init>: push r15) RDX: 0x7fffffffdf38 --> 0x7fffffffe278 ("CLUTTER_IM_MODULE=xim") RSI: 0x7fffffffdf28 --> 0x7fffffffe260 ("/mnt/hgfs/Shared/sanity") RDI: 0x1 RBP: 0x7fffffffde40 --> 0x5555555552b0 (<__libc_csu_init>: push r15) RSP: 0x7fffffffde40 --> 0x5555555552b0 (<__libc_csu_init>: push r15) RIP: 0x555555555169 (<main+4>: push r15) R8 : 0x7ffff7dced80 --> 0x0 R9 : 0x7ffff7dced80 --> 0x0 R10: 0x0 R11: 0x0 R12: 0x555555555080 (<_start>: xor ebp,ebp) R13: 0x7fffffffdf20 --> 0x1 R14: 0x0 R15: 0x0 EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x555555555160 <frame_dummy>: jmp 0x5555555550e0 <register_tm_clones> 0x555555555165 <main>: push rbp 0x555555555166 <main+1>: mov rbp,rsp => 0x555555555169 <main+4>: push r15 0x55555555516b <main+6>: push r14 0x55555555516d <main+8>: push r12 0x55555555516f <main+10>: push rbx 0x555555555170 <main+11>: sub rsp,0x90 [------------------------------------stack-------------------------------------] 0000| 0x7fffffffde40 --> 0x5555555552b0 (<__libc_csu_init>: push r15) 0008| 0x7fffffffde48 --> 0x7ffff7a03bf7 (<__libc_start_main+231>: mov edi,eax) 0016| 0x7fffffffde50 --> 0x1 0024| 0x7fffffffde58 --> 0x7fffffffdf28 --> 0x7fffffffe260 ("/mnt/hgfs/Shared/sanity") 0032| 0x7fffffffde60 --> 0x100008000 0040| 0x7fffffffde68 --> 0x555555555165 (<main>: push rbp) 0048| 0x7fffffffde70 --> 0x0 0056| 0x7fffffffde78 --> 0xa4f5b027479a811b [------------------------------------------------------------------------------] Legend: code, data, rodata, value Temporary breakpoint 1, 0x0000555555555169 in main () gdb-peda$ disas main Dump of assembler code for function main: 0x0000555555555165 <+0>: push rbp 0x0000555555555166 <+1>: mov rbp,rsp => 0x0000555555555169 <+4>: push r15 0x000055555555516b <+6>: push r14 0x000055555555516d <+8>: push r12 0x000055555555516f <+10>: push rbx 0x0000555555555170 <+11>: sub rsp,0x90 0x0000555555555177 <+18>: mov rax,rsp 0x000055555555517a <+21>: mov r12,rax 0x000055555555517d <+24>: mov rax,QWORD PTR [rip+0x2ecc] # 0x555555558050 <flag> 0x0000555555555184 <+31>: mov rdi,rax 0x0000555555555187 <+34>: call 0x555555555040 <strlen@plt> 0x000055555555518c <+39>: mov rdx,rax 0x000055555555518f <+42>: sub rdx,0x1 0x0000555555555193 <+46>: mov QWORD PTR [rbp-0x30],rdx 0x0000555555555197 <+50>: mov QWORD PTR [rbp-0xb0],rax 0x000055555555519e <+57>: mov QWORD PTR [rbp-0xa8],0x0 0x00005555555551a9 <+68>: mov r14,rax 0x00005555555551ac <+71>: mov r15d,0x0 0x00005555555551b2 <+77>: mov edx,0x10 0x00005555555551b7 <+82>: sub rdx,0x1 0x00005555555551bb <+86>: add rax,rdx 0x00005555555551be <+89>: mov esi,0x10 0x00005555555551c3 <+94>: mov edx,0x0 0x00005555555551c8 <+99>: div rsi 0x00005555555551cb <+102>: imul rax,rax,0x10 0x00005555555551cf <+106>: sub rsp,rax 0x00005555555551d2 <+109>: mov rax,rsp 0x00005555555551d5 <+112>: add rax,0x0 0x00005555555551d9 <+116>: mov QWORD PTR [rbp-0x38],rax 0x00005555555551dd <+120>: mov DWORD PTR [rbp-0x24],0x0 0x00005555555551e4 <+127>: jmp 0x555555555223 <main+190> 0x00005555555551e6 <+129>: mov rdx,QWORD PTR [rip+0x2e63] # 0x555555558050 <flag> 0x00005555555551ed <+136>: mov eax,DWORD PTR [rbp-0x24] 0x00005555555551f0 <+139>: cdqe 0x00005555555551f2 <+141>: add rax,rdx 0x00005555555551f5 <+144>: movzx edx,BYTE PTR [rax] 0x00005555555551f8 <+147>: mov rcx,QWORD PTR [rip+0x2e49] # 0x555555558048 <key> 0x00005555555551ff <+154>: mov eax,DWORD PTR [rbp-0x24] 0x0000555555555202 <+157>: cdqe 0x0000555555555204 <+159>: add rax,rcx 0x0000555555555207 <+162>: movzx eax,BYTE PTR [rax] 0x000055555555520a <+165>: xor eax,edx 0x000055555555520c <+167>: mov BYTE PTR [rbp-0x39],al 0x000055555555520f <+170>: mov rdx,QWORD PTR [rbp-0x38] 0x0000555555555213 <+174>: mov eax,DWORD PTR [rbp-0x24] 0x0000555555555216 <+177>: cdqe 0x0000555555555218 <+179>: movzx ecx,BYTE PTR [rbp-0x39] 0x000055555555521c <+183>: mov BYTE PTR [rdx+rax*1],cl 0x000055555555521f <+186>: add DWORD PTR [rbp-0x24],0x1 0x0000555555555223 <+190>: mov eax,DWORD PTR [rbp-0x24] 0x0000555555555226 <+193>: movsxd rbx,eax 0x0000555555555229 <+196>: mov rax,QWORD PTR [rip+0x2e20] # 0x555555558050 <flag> 0x0000555555555230 <+203>: mov rdi,rax 0x0000555555555233 <+206>: call 0x555555555040 <strlen@plt> 0x0000555555555238 <+211>: cmp rbx,rax 0x000055555555523b <+214>: jb 0x5555555551e6 <main+129> 0x000055555555523d <+216>: lea rdi,[rip+0xe12] # 0x555555556056 0x0000555555555244 <+223>: call 0x555555555030 <puts@plt> 0x0000555555555249 <+228>: lea rax,[rbp-0xa0] 0x0000555555555250 <+235>: mov rsi,rax 0x0000555555555253 <+238>: lea rdi,[rip+0xe0c] # 0x555555556066 0x000055555555525a <+245>: mov eax,0x0 0x000055555555525f <+250>: call 0x555555555060 <__isoc99_scanf@plt> 0x0000555555555264 <+255>: mov rdx,QWORD PTR [rbp-0x38] 0x0000555555555268 <+259>: lea rax,[rbp-0xa0] 0x000055555555526f <+266>: mov rsi,rdx 0x0000555555555272 <+269>: mov rdi,rax 0x0000555555555275 <+272>: call 0x555555555050 <strcmp@plt> 0x000055555555527a <+277>: test eax,eax 0x000055555555527c <+279>: jne 0x55555555528c <main+295> 0x000055555555527e <+281>: lea rdi,[rip+0xde4] # 0x555555556069 0x0000555555555285 <+288>: call 0x555555555030 <puts@plt> 0x000055555555528a <+293>: jmp 0x555555555298 <main+307> 0x000055555555528c <+295>: lea rdi,[rip+0xddf] # 0x555555556072 0x0000555555555293 <+302>: call 0x555555555030 <puts@plt> 0x0000555555555298 <+307>: mov rsp,r12 0x000055555555529b <+310>: mov eax,0x0 0x00005555555552a0 <+315>: lea rsp,[rbp-0x20] 0x00005555555552a4 <+319>: pop rbx 0x00005555555552a5 <+320>: pop r12 0x00005555555552a7 <+322>: pop r14 0x00005555555552a9 <+324>: pop r15 0x00005555555552ab <+326>: pop rbp 0x00005555555552ac <+327>: ret End of assembler dump. gdb-peda$ b *0x0000555555555275 Breakpoint 2 at 0x555555555275 gdb-peda$ c Continuing. Whats the flag? hoge [----------------------------------registers-----------------------------------] RAX: 0x7fffffffdda0 --> 0x65676f68 ('hoge') RBX: 0x25 ('%') RCX: 0x7ffff7dce560 --> 0x7ffff7dca580 --> 0x7ffff7b978e1 --> 0x636d656d5f5f0043 ('C') RDX: 0x7fffffffdd60 ("IJCTF{you_did_not_fall_for_it_right?}") RSI: 0x7fffffffdd60 ("IJCTF{you_did_not_fall_for_it_right?}") RDI: 0x7fffffffdda0 --> 0x65676f68 ('hoge') RBP: 0x7fffffffde40 --> 0x5555555552b0 (<__libc_csu_init>: push r15) RSP: 0x7fffffffdd60 ("IJCTF{you_did_not_fall_for_it_right?}") RIP: 0x555555555275 (<main+272>: call 0x555555555050 <strcmp@plt>) R8 : 0x0 R9 : 0x0 R10: 0x0 R11: 0x555555556068 --> 0x74636572726f4300 ('') R12: 0x7fffffffdd90 --> 0x25 ('%') R13: 0x7fffffffdf20 --> 0x1 R14: 0x25 ('%') R15: 0x0 EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x555555555268 <main+259>: lea rax,[rbp-0xa0] 0x55555555526f <main+266>: mov rsi,rdx 0x555555555272 <main+269>: mov rdi,rax => 0x555555555275 <main+272>: call 0x555555555050 <strcmp@plt> 0x55555555527a <main+277>: test eax,eax 0x55555555527c <main+279>: jne 0x55555555528c <main+295> 0x55555555527e <main+281>: lea rdi,[rip+0xde4] # 0x555555556069 0x555555555285 <main+288>: call 0x555555555030 <puts@plt> Guessed arguments: arg[0]: 0x7fffffffdda0 --> 0x65676f68 ('hoge') arg[1]: 0x7fffffffdd60 ("IJCTF{you_did_not_fall_for_it_right?}") arg[2]: 0x7fffffffdd60 ("IJCTF{you_did_not_fall_for_it_right?}") [------------------------------------stack-------------------------------------] 0000| 0x7fffffffdd60 ("IJCTF{you_did_not_fall_for_it_right?}") 0008| 0x7fffffffdd68 ("u_did_not_fall_for_it_right?}") 0016| 0x7fffffffdd70 ("t_fall_for_it_right?}") 0024| 0x7fffffffdd78 ("or_it_right?}") 0032| 0x7fffffffdd80 --> 0x7d3f746867 ('ght?}') 0040| 0x7fffffffdd88 --> 0x55555555518c (<main+39>: mov rdx,rax) 0048| 0x7fffffffdd90 --> 0x25 ('%') 0056| 0x7fffffffdd98 --> 0x0 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 2, 0x0000555555555275 in main ()
IJCTF{you_did_not_fall_for_it_right?}
Black Letter (Forensic)
各チャンクのサイズとCRCが0になっている。チャンクを指定して、そのサイズとCRCを計算して埋める。
チャンクの順番はこのようになっている。
IHDR acTL tRNS fcTL IDAT fcTL fdAT fcTL fdAT fcTL fdAT : fcTL fdAT IEND
この前提でサイズとCRCを復元させる。
Anmated PNGとして復元したが、チャンクの順番がおかしく、4と5しか表示されない。
そこでさらにアニメーションの順番を確認して正しい順序に入れ替える。
データ形式は <チャンク名(4バイト)> + <シーケンス番号(4バイト)> + <データ>となっている。
最終的なコードは以下の通り。
from binascii import * from struct import * def calc_crc(d): pass with open('message.png', 'rb') as f: data = f.read() index = 8 next_index = data.find('IHDR', index) #### IHDR #### curr_index = next_index next_index = data.find('acTL', index) length = (next_index - 8) - (curr_index + 4) size = pack('>I', length) data = data[:curr_index - 4] + size + data[curr_index:] crc = pack('!l', crc32(data[curr_index:next_index - 8])) data = data[:next_index - 8] + crc + data[next_index - 4:] index = next_index - 4 #### acTL #### curr_index = next_index next_index = data.find('tRNS', index) length = (next_index - 8) - (curr_index + 4) size = pack('>I', length) data = data[:curr_index - 4] + size + data[curr_index:] crc = pack('!l', crc32(data[curr_index:next_index - 8])) data = data[:next_index - 8] + crc + data[next_index - 4:] index = next_index - 4 #### tRNS #### curr_index = next_index next_index = data.find('fcTL', index) length = (next_index - 8) - (curr_index + 4) size = pack('>I', length) data = data[:curr_index - 4] + size + data[curr_index:] crc = pack('!l', crc32(data[curr_index:next_index - 8])) data = data[:next_index - 8] + crc + data[next_index - 4:] index = next_index - 4 #### fcTL #### curr_index = next_index next_index = data.find('IDAT', index) length = (next_index - 8) - (curr_index + 4) size = pack('>I', length) data = data[:curr_index - 4] + size + data[curr_index:] crc = pack('!l', crc32(data[curr_index:next_index - 8])) data = data[:next_index - 8] + crc + data[next_index - 4:] index = next_index - 4 #### IDAT #### curr_index = next_index next_index = data.find('fcTL', index) length = (next_index - 8) - (curr_index + 4) size = pack('>I', length) data = data[:curr_index - 4] + size + data[curr_index:] crc = pack('!l', crc32(data[curr_index:next_index - 8])) data = data[:next_index - 8] + crc + data[next_index - 4:] index = next_index - 4 i = 0 while True: #### fcTL / fdAT #### if i % 2 == 0: next_chunk = 'fdAT' else: next_chunk = 'fcTL' curr_index = next_index next_index = data.find(next_chunk, index) if next_index == -1: next_index = data.find('IEND', index) length = (next_index - 8) - (curr_index + 4) size = pack('>I', length) data = data[:curr_index - 4] + size + data[curr_index:] crc = pack('!l', crc32(data[curr_index:next_index - 8])) data = data[:next_index - 8] + crc + data[next_index - 4:] index = next_index - 4 break length = (next_index - 8) - (curr_index + 4) size = pack('>I', length) data = data[:curr_index - 4] + size + data[curr_index:] crc = pack('!l', crc32(data[curr_index:next_index - 8])) data = data[:next_index - 8] + crc + data[next_index - 4:] index = next_index - 4 i += 1 #### IEND #### curr_index = next_index length = (len(data) - 4) - (curr_index + 4) size = pack('>I', length) data = data[:curr_index - 4] + size + data[curr_index:] crc = pack('!l', crc32(data[curr_index:len(data) - 4])) data = data[:len(data) - 4] + crc #### correct fcTL / fdAT sequence #### idat_index = data.find('IDAT') fctl1_index = data.find('fcTL', idat_index) data_head = data[:fctl1_index - 4] iend_index = data.find('IEND') data_tail = data[iend_index - 4:] target_data = data[fctl1_index - 4:iend_index - 4] i = 0 next_index = 4 chunk_data = [''] * 80 while True: if i % 2 == 0: next_chunk = 'fdAT' else: next_chunk = 'fcTL' curr_index = next_index next_index = target_data.find(next_chunk, curr_index) if next_index == -1: seqno = unpack('>I', target_data[curr_index + 4:curr_index + 8])[0] chunk_data[seqno - 1] = target_data[curr_index - 4:] break seqno = unpack('>I', target_data[curr_index + 4:curr_index + 8])[0] chunk_data[seqno - 1] = target_data[curr_index - 4:next_index - 4] curr_index = next_index i += 1 data = data_head + ''.join(chunk_data) + data_tail with open('message_recover.png', 'wb') as f: f.write(data)
復元したAnmated PNGを最初のフレームから見るとフラグになる。
IJCTF{d34f6429957e3ec205d4f140c9b34a33}