Gallopsled / pwntools

CTF framework and exploit development library
http://pwntools.com
Other
12.15k stars 1.72k forks source link

How to handle consecutive read()s when interacting with processes? #1116

Closed integeruser closed 6 years ago

integeruser commented 6 years ago

Consider the following C program, containing two consecutive calls to read() (as in countless CTF challenges):

root@dec7debf5e1f:/tmp# cat test.c
#include <stdio.h>
#include <unistd.h>

void getdata()
{
    char buf[1024] = {0};

    int n = read(fileno(stdin), buf, 1024);
    printf("read: '%s' (%d bytes)\n", buf, n);
}

int main(int argc, char const *argv[])
{
    getdata();
    getdata();
    return 0;
}

I want to interact with this program in such a way that it reads the string AAAA in the first call to read() and reads BBBB in the second call. With any terminal emulator, I can do this by pressing Ctrl+D (which should send an EOF signal) after typing each of the two strings:

root@dec7debf5e1f:/tmp# gcc -o test test.c
root@dec7debf5e1f:/tmp# ./test
AAAAread: 'AAAA' (4 bytes)
BBBBread: 'BBBB' (4 bytes)

How can I achieve the same behaviour consistently using pwntools? Two send()s do not always result in two read()s by the receiving program (not even sure if this is a bug or is expected):

root@dec7debf5e1f:/tmp# cat test.py
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from pwn import *

argv = ['./test']
envp = {}

io = process(argv=argv, env=envp)

io.send('AAAA')
io.send('BBBB')

io.interactive()
root@dec7debf5e1f:/tmp# PWNLIB_DEBUG=1 python2 ./test.py
[+] Starting local process './test' env={} : pid 488
[DEBUG] Sent 0x4 bytes:
    'A' * 0x4
[DEBUG] Sent 0x4 bytes:
    'B' * 0x4
[*] Switching to interactive mode
[*] Process './test' stopped with exit code 0 (pid 488)
[DEBUG] Received 0x2e bytes:
    "read: 'AAAA' (4 bytes)\n"
    "read: 'BBBB' (4 bytes)\n"
read: 'AAAA' (4 bytes)
read: 'BBBB' (4 bytes)
[*] Got EOF while reading in interactive
root@dec7debf5e1f:/tmp# PWNLIB_DEBUG=1 python2 ./test.py
[+] Starting local process './test' env={} : pid 493
[DEBUG] Sent 0x4 bytes:
    'A' * 0x4
[DEBUG] Sent 0x4 bytes:
    'B' * 0x4
[*] Switching to interactive mode
[DEBUG] Received 0x1b bytes:
    "read: 'AAAABBBB' (8 bytes)\n"
read: 'AAAABBBB' (8 bytes)
$

(tested using the Dockerfile pwntools/pwntools:stable after updating to pwntools 3.12.0)

I have spent some time reading about PTYs and related but I still have no idea on how to "flush" after a send() (like Ctrl+D) with pwntools. Until now, I worked around this by inserting short sleep()s immediately after sending data, but even so it still does not work consistently and is often source of frustration.

I'd really appreciate if someone could explain what's going on here and which is the correct way to handle these situations.

zachriggle commented 6 years ago

What’s going on is that the send()s both complete before the first read() completes. You’re sending data too fast.

There is no way to guarantee buffer contents. That’s not how the read(2) syscall works.

I would recommend inserting a call like io.recvuntil(“read: “) between the sends, so that you know the target program has processed your data. On Sat, Feb 24, 2018 at 12:28 PM Francesco Cagnin notifications@github.com wrote:

Consider the following C program, containing two consecutive calls to read() (as in countless CTF challenges):

root@dec7debf5e1f:/tmp# cat test.c

include

include

void getdata() { char buf[1024] = {0};

int n = read(fileno(stdin), buf, 1024);
printf("read: '%s' (%d bytes)\n", buf, n);

} int main(int argc, char const *argv[]) { getdata(); getdata(); return 0; }

I want to interact with this program in such a way that it reads the string AAAA in the first call to read() and reads BBBB in the second call. With any terminal emulator, I can do this by pressing Ctrl+D (which should send an EOF signal) after typing each of the two strings:

root@dec7debf5e1f:/tmp# gcc -o test test.c root@dec7debf5e1f:/tmp# ./test AAAAread: 'AAAA' (4 bytes) BBBBread: 'BBBB' (4 bytes)

How can I achieve the same behaviour consistently using pwntools? Two send()s do not always result in two read()s by the receiving program (not even sure if this is a bug or is expected):

root@dec7debf5e1f:/tmp# cat test.py#!/usr/bin/env python2# -- coding: utf-8 --from pwn import *

argv = ['./test'] envp = {}

io = process(argv=argv, env=envp)

io.send('AAAA') io.send('BBBB')

io.interactive()

root@dec7debf5e1f:/tmp# PWNLIB_DEBUG=1 python2 ./test.py [+] Starting local process './test' env={} : pid 488 [DEBUG] Sent 0x4 bytes: 'A' 0x4 [DEBUG] Sent 0x4 bytes: 'B' 0x4 [] Switching to interactive mode [] Process './test' stopped with exit code 0 (pid 488) [DEBUG] Received 0x2e bytes: "read: 'AAAA' (4 bytes)\n" "read: 'BBBB' (4 bytes)\n" read: 'AAAA' (4 bytes) read: 'BBBB' (4 bytes) [*] Got EOF while reading in interactive

root@dec7debf5e1f:/tmp# PWNLIB_DEBUG=1 python2 ./test.py [+] Starting local process './test' env={} : pid 493 [DEBUG] Sent 0x4 bytes: 'A' 0x4 [DEBUG] Sent 0x4 bytes: 'B' 0x4 [*] Switching to interactive mode [DEBUG] Received 0x1b bytes: "read: 'AAAABBBB' (8 bytes)\n" read: 'AAAABBBB' (8 bytes)$

(tested using the Dockerfile pwntools/pwntools:stable after updating to pwntools 3.12.0)

I have spent some time reading about PTYs and related but I still have no idea on how to "flush" after a send() (like Ctrl+D) with pwntools. Until now, I worked around this by inserting short sleep()s immediately after sending data, but even so it still does not work consistently and is often source of frustration.

I'd really appreciate if someone could explain what's going on here and which is the correct way to handle these situations.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/Gallopsled/pwntools/issues/1116, or mute the thread https://github.com/notifications/unsubscribe-auth/AAG0GEST9Nhf_T2v-EcZ_1bLTuu2ow8Lks5tYFTGgaJpZM4SR_bJ .

integeruser commented 6 years ago

Ah, now it's clear. Thanks!