Currently I’m studying SLAE certification of Pentester Academy and I found a really interesting video that explained this technique. I’m going to follow the course instructions step by step and try to explain it here the best as I can. By the way, I totally recommend this course, everything is really well explained and the content is really good.
So, following the SLAE topics, in this second article related with Assembly I am going to explain how to develop a small shellcode that prints “Hello World!” in the screen. In the last blog post, I already showed you how to write a Hello World, but to convert this into a shellcode we have 2 restrictions:
1) We can not have any 0x00 instructions. This are null bytes and usually break our shellcode execution.
2) We can not use any hard coded addresses. Our code should be able to run in different computer and programs, and the addresses need to be generated dynamically.
We start from this code:
; HelloWorld.nasm
; Author: Vivek Ramachandran
; Website: https://www.pentesteracademy.com
; Student: Xavi Bel
global _start
section .text
_start:
; write syscall
mov eax, 0x4
mov ebx, 0x1
mov ecx, message
mov edx, mlen
int 0x80
; exit syscall
mov eax, 0x1
mov ebx, 0x0
int 0x80
section .data
message: db "Hello World!"
mlen equ $-message
So let’s check if our code contains any null values or hard coded addresses. To do this we can use objdump utility:
objdump -d HelloWorldShellcode -M intel
So we have both problems, we have a lot of null bytes and the memory address of the “Hello World!” string.
Let’s start removing the null bytes, to do that we need to modify all the mov instructions that generated null bytes to equivalent instructions.
The way to do this is the following:
When we use an xor operation between the same values, the result is always zero. There is another thing that we need to know, the register EAX, can be segmented in AH(High) and AL(Low), please watch the image below, So we can mov a 4 into AL.
So instead of:
mov eax, 0x4
We can do:
xor eax, eax
mov al, 0x4
We can repeat this process for all the mov instructions that generated a 0x00.
Once we do that, we need to fix the other problem, the hard coded address of the variable message. I will explain what we are going to do: first we are going to move the array declaration inside the data segment, after that we are going to create a jmp to a procedure named call_shellcode, this procedure will contain two lines, the first one is a call shellcode, and the second one is the message, so once the call shellcode instruction will be executed, the direction of message will be saved in ecx register and we fix the original problem.
So, being squematic, the code will have this structure:
jmp short call_shellcode
shellcode:
...
...
pop ecx ; So now the memory address of message is saved in ecx
...
...
call_shellcode:
call shellcode
message: db "Hello World!", 0xA
Doing this we are going to fix the 2 problems and now our shellcode is going to work correctly.
Here is the final code:
; HelloWorldShellcode.nasm
; Author: Vivek Ramachandran
; Website: https://www.pentesteracademy.com
; Student: Xavi Bel
global _start
section .text
_start:
jmp short call_shellcode
shellcode:
pop ecx;
; write syscall
xor eax, eax
mov al, 0x4
xor ebx, ebx
mov bl, 0x1
mov edx, 13
int 0x80
; exit syscall
xor eax, eax
mov al, 0x1
xor ebx, ebx
int 0x80
call_shellcode:
call shellcode
message: db "Hello World!", 0xA
Now we can extract the instructions using objdump:
objdump -d ./HelloWorldShellcode|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
And we can verify, that we have not any null bytes.
Now using this small C program we can verify ir our shellcode works:
#include<stdio.h>
#include<string.h>
unsigned char code[] = \
"\xeb\x18\x59\x31\xc0\xb0\x04\x31\xdb\xb3\x01\xba\x0d\x00\x00\x00\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xcd\x80\xe8\xe3\xff\xff\xff\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21\x0a";
main()
{
printf("Shellcode Length: %d\n", strlen(code));
int (*ret)() = (int(*)())code;
ret();
}
We compile it using:
gcc -fno-stack-protector -z execstack shellcode.c -o shellcode
And it works!
I hope you liked the post, probably I’m going to write a new one soon. See you!