Avoiding NULL
The presence of NULL characters in payloads can be detrimental as they can prematurely terminate string-based operations, leading to unintended consequences or truncation of data.
Analysing Shellcode
Using the workflow we developed we can compile our shellcode into a .bin
/raw file. We can take this raw file and analyse with a tool in my github repository here. You will need to install the keystone and capstone engines and the rich framework using pip
:
pip.exe install keystone-engine
pip.exe install capstone
pip.exe install rich
We can compile our MessageBox shellcode from the previous section:
nasm -f bin -o messagebox.bin messagebox.asm
We can then use the Bad Character tool to check for null bytes:
python.exe .\bad-char-check.py --raw ..\x64\messagebox.bin
--badchars "0x00" --scroll 10 --platform x64
We can scroll through the shellcode until we find that there is two NULL bytes at 0x10d8
:

We can see the offending shellcode in our assembly:
call_loadlibrarya_user32.dll:
mov rax, 0x6c6c ; PUSH user32.dll
Removing NULL Bytes
Various techniques exist to eliminate NULL bytes from shellcode, often requiring imaginative approaches to ensure that the payload remains free of these characters.
The shellcode below presents a way in which we can acheive the same objective but avoid NULL bytes:
call_loadlibrarya_user32.dll:
mov rax, 0x4141adad ; PUSH user32.dll
mov rcx, 0x41414141 ; RCX = 0x41414141
sub rax, rcx ; RAX = 0x6c6c
After testing the .bin
file again, we see that the NULL character has been eliminated:

As we work through eliminating the null bytes it would be prudent to insert a breakpoint and test the shellcode to ensure that we acheive the same outcome:
call_loadlibrarya_user32.dll:
mov rax, 0x4141adad ; PUSH user32.dll
mov rcx, 0x41414141 ; RCX = 0x41414141
sub rax, rcx ; RAX = 0x6c6c
push rax ;
mov rax, 0x642e323372657375 ;
push rax ;
mov rcx, rsp ; RCX = lpLibFileName = user32.dll
sub rsp, 0x2c ; Allocate stack space for the function call
; (+ allignment)
int3 ; our breakpoint
call [rbp-0x30] ; CALL LoadLibraryA
add rsp, 0x2c ; Clean up allocated space
add rsp, 0x10 ; Clean up user32.dll on stack
The breakpoint has been entered at line 11
. If we recompile this and debug it in Windbg Preview, we can hit the breakpoint. We can use the da @rcx
command to show that the lpLibFileName
is still correct:
0:000> g
(a24.1618): Break instruction exception - code 80000003 (first chance)
x64+0x10f8:
00007ff6`46c810f8 cc int 3
0:000> da @rcx
00000038`cd6ffc3c "user32.dll"
Don't forget to remove any int3
instructions when you want to use your shellcode for real.
Bad Characters
This technique can be used to eliminate bad characters too, but sometimes instruction mnemonics include bad characters.
Let us imagine that 0x31
is a bad character:
python.exe .\bad-char-check.py --raw ..\x64\x64.bin --badchars "0x00 0x31"
--scroll 10 --platform x64

We can replace the xor rcx, rcx
with:
find_kernel32:
;xor rcx, rcx ; removed
mov rcx, -0x01 ;
inc rcx ; RCX = 0
We can debug in Windbg Preview and we find that rcx
is zero:
0:000> g
(2134.b6c): Break instruction exception - code 80000003 (first chance)
x64+0x1016:
00007ff6`f5f51016 cc int 3
0:000> r rcx
rcx=0000000000000000
There are many different ways to replace bad characters in your shellcode and sometimes it might be more beneficial to change your shellcode rather than rely upon decoding shellcode in memory. This is particularly true if the memory in which your shellcode has been copied does not have write permissions.
Exercises
Work through the MessageBox shellcode and eliminate NULL bytes.
Give yourself a handful of bad characters and eliminate them from your shellcode.
Last updated