Tags: pwn radare2
Rating:
For this challenge we are given an executable which asks for some input on startup. Let's decompile it with r2ghidra-dec
-- a Ghidra decompiler integration into radare.
undefined8 main(void)
{
char *s;
uint32_t var_4h;
var_4h = 0;
sym.imp.setbuf(_reloc.stdout, 0);
sym.imp.setbuf(_reloc.stdin, 0);
sym.imp.setbuf(_reloc.stderr, 0);
sym.imp.puts("Please pour me some coffee:");
sym.imp.gets(&s);
sym.imp.puts("\nThanks!\n");
if (var_4h != 0) {
sym.imp.puts("Oh no, you spilled some coffee on the floor! Use the flag to clean it.");
sym.imp.system("cat flag.txt");
}
return 0;
}
Something to note is that if we were to use Ghidra, it would show something like char s [44];
, but let's find this out using radare and see how these two variables are placed one after the other on the stack.
┌ 158: int main (int argc, char **argv, char **envp);
│ ; var char *s @ rbp-0x30
│ ; var uint32_t var_4h @ rbp-0x4
│ 0x00401156 55 push rbp
│ 0x00401157 4889e5 mov rbp, rsp
│ 0x0040115a 4883ec30 sub rsp, 0x30
│ 0x0040115e c745fc000000. mov dword [var_4h], 0
│ ; <redacted>, Sets up stdio buffers and prompts for input
│ 0x004011ad 488d45d0 lea rax, [s]
│ 0x004011b1 4889c7 mov rdi, rax ; char *s
│ 0x004011b4 b800000000 mov eax, 0
│ 0x004011b9 e8a2feffff call sym.imp.gets ;[3] ; char *gets(char *s)
│ 0x004011be 488d3d5f0e00. lea rdi, str.Thanks ; 0x402024 ; "\nThanks!\n" ; const char *s
│ 0x004011c5 e866feffff call sym.imp.puts ;[2] ; int puts(const char *s)
│ 0x004011ca 837dfc00 cmp dword [var_4h], 0
│ ┌─< 0x004011ce 741d je 0x4011ed
│ │ ; <redacted>, Flag is printed here
│ └─> 0x004011ed b800000000 mov eax, 0
│ 0x004011f2 c9 leave
└ 0x004011f3 c3 ret
At the very start we see the two variables, s
and var_4h
defined by radare with offsets off rbp
and then assigned to in assembly below:
; var char *s @ rbp-0x30
; var uint32_t var_4h @ rbp-0x4
0x0040115e c745fc000000. mov dword [var_4h], 0
0x004011ad 488d45d0 lea rax, [s]
It's clear that the size of them together is 0x30
:
0x0040115a 4883ec30 sub rsp, 0x30
s
is placed at the start with offset rbp - 0x30
and then var_4h
is placed at rbp - 0x4
. This means that the s
buffer which we control is of size 0x30 - 0x4 = 0x2c
or 44
in decimal. Thus, we can overflow this buffer with the vulnerable gets()
call and overwrite var_4h
by sending 44 bytes followed by our new value for var_4h
.
The flag is printed if var_4h
is no longer 0, so overwriting it with anything but null bytes should do the trick. The comparison can be seen here:
0x004011ca 837dfc00 cmp dword [var_4h], 0
The flag we get is: csictf{y0u_ov3rfl0w3d_th@t_c0ff33l1ke@_buff3r}