IJCTF 2021 Writeup

この大会は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}