I'm learning buffer overflow. I wrote a small C program:
#include<stdio.h>
#include<string.h>
void vuln();
void win();
void main(int argc, char *argv[]){
vuln();
}
void win (){
printf("Pwned!!");
}
void vuln(){
char buffer[256];
printf("Enter a string: ");
scanf(" %s", buffer);
}
Disassembly of the vuln
function:
gef➤ disas vuln
Dump of assembler code for function vuln:
0x0000555555555179 <+0>: push rbp
0x000055555555517a <+1>: mov rbp,rsp
0x000055555555517d <+4>: sub rsp,0x100
0x0000555555555184 <+11>: lea rdi,[rip+0xe81] # 0x55555555600c
0x000055555555518b <+18>: mov eax,0x0
0x0000555555555190 <+23>: call 0x555555555030 <printf@plt>
0x0000555555555195 <+28>: lea rax,[rbp-0x100]
0x000055555555519c <+35>: mov rsi,rax
0x000055555555519f <+38>: lea rdi,[rip+0xe77] # 0x55555555601d
0x00005555555551a6 <+45>: mov eax,0x0
0x00005555555551ab <+50>: call 0x555555555040 <__isoc99_scanf@plt>
0x00005555555551b0 <+55>: nop
0x00005555555551b1 <+56>: leave
0x00005555555551b2 <+57>: ret
End of assembler dump.
I managed to jump to win
by using the payload:
>>> payload = "A"264 + p64(0x0000555555555161)
The problem is that it didn't print out the string in the function win
. All I get was segmentation fault. Here's what I get:
Program received signal SIGSEGV, Segmentation fault.
0x00007fffffffe158 in ?? ()
[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x7
$rbx : 0x0
$rcx : 0x0
$rdx : 0x0
$rsp : 0x00007fffffffe068 → 0x0000000200000000
$rbp : 0x4141414141414141 ("AAAAAAAA"?)
$rsi : 0x656e7750
$rdi : 0x00005555555592a0 → "Pwned!! string: "
$rip : 0x00007fffffffe158 → 0x00007fffffffe436 → "/media/sf_Code/asm/vuln"
$r8 : 0x00007ffff7fb9500 → 0x00007ffff7fb9500 → [loop detected]
$r9 : 0x7
$r10 : 0x0000555555556004 → 0x00212164656e7750 ("Pwned!!"?)
$r11 : 0x246
$r12 : 0x0000555555555060 → <_start+0> xor ebp, ebp
$r13 : 0x00007fffffffe150 → 0x0000000000000002
$r14 : 0x0
$r15 : 0x0
$eflags: [zero carry PARITY ADJUST sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffe068│+0x0000: 0x0000000200000000 ← $rsp
0x00007fffffffe070│+0x0008: 0x00005555555551c0 → <__libc_csu_init+0> push r15
0x00007fffffffe078│+0x0010: 0x00007ffff7e1ebbb → <__libc_start_main+235> mov edi, eax
0x00007fffffffe080│+0x0018: 0x0000000000000000
0x00007fffffffe088│+0x0020: 0x00007fffffffe158 → 0x00007fffffffe436 → "/media/sf_Code/asm/vuln"
0x00007fffffffe090│+0x0028: 0x0000000200100000
0x00007fffffffe098│+0x0030: 0x0000555555555145 → <main+0> push rbp
0x00007fffffffe0a0│+0x0038: 0x0000000000000000
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x7fffffffe152 add BYTE PTR [rax], al
0x7fffffffe154 add BYTE PTR [rax], al
0x7fffffffe156 add BYTE PTR [rax], al
→ 0x7fffffffe158 ss in al, 0xff
0x7fffffffe15b (bad)
0x7fffffffe15c (bad)
0x7fffffffe15d jg 0x7fffffffe15f
0x7fffffffe15f add BYTE PTR [rsi-0x1c], cl
0x7fffffffe162 (bad)
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "vuln", stopped 0x7fffffffe158 in ?? (), reason: SIGSEGV
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x7fffffffe158 → ss in al, 0xff
How is that the printf
didn't print out the string?
stdout defaults to line-buffered, and the string doesn't end with a newline. If you changed it to puts("Pwned!!");
then stdout
would get flushed before puts
returns.
But with printf
, the data is just sitting there in a stdio buffer until something else prints a newline, or until fflush(stdout)
. exit()
or cleanly returning from main
will cause fflush, but segfaulting will kill the process without ever making a system call to hand that I/O data to the OS.
This is exactly the same problem as Using printf in assembly leads to an empty ouput except that case was using an _exit(2)
system call instead of segfaulting.
If the goal is to force you to get win()
called without breaking later execution, that's another level of challenge.
But if win()
is supposed to represent something like a successful ROP attack that calls system()
or execve
with "/bin/sh"
then win()
is not well-written. execve
will happen on the spot, not at some later time.