Hello World Shellcode – Now for the fun part!

Once I finished module 1 of the SLAE course, it was time to move on to some hello world shellcode.

Hello World Shellcode - Introduction

While creating a shellcode version of my Hello World application will be simple, there are a few differences between the two.

As this code will be "shellcode ready", I will need to avoid all null characters (string terminators) as well as any hardcoded addresses. The reason for the addressing is that any system that supports these instructions can run it.

I will actually cover two different versions of this shellcode as well. The first uses the JMP-CALL-POP technique to get the address for the string to print. The second just pushes all the values directly on to the stack.

Hello World Shellcode - JMP-CALL-POP

First, I’ll start by just sharing my final application code. It is very well commented, but I’ll also explain it a bit further below.

; HelloWorldShellcode.nasm
; Author: Ray Doyle

global _start
section .text
_start:
    jmp short call_shellcode

shellcode:
    ; JMP-CALL-POP allows the application to be written without any hardcoded addresses (unlike 'mov ecx, message')
    ; This works because the "CALL" instruction places the address of the next instruction, in case of a return
    ; In this case the next instruction is the "Hello World!" string
    
    ; Move the pointer to the string into ECX off of the stack
    pop ecx

    ; Move the value 4 into EAX (system call for write)
    ; Zeros out the register and then uses al to avoid nulls in the resulting shellcode
    xor eax, eax
    mov al, 0x4

    ; Move the value 1 into EBX (fd1 = STDOUT)
    xor ebx, ebx
    mov bl, 0x1
    
    ; Move the value 13 into EDX (length of "Hello World!\n1")
    xor edx, edx
    mov dl, 13

    ; Send an 0x80 interrupt to invoke the system call
    int 0x80

    ; Exit the program gracefully
        
    ; Move the value 1 into EAX (system call for exit)
    xor eax, eax
    mov al, 0x1

    ; Zero out the EBX register for a successful exit status
    xor ebx, ebx

    ; Send an 0x80 interrupt to invoke the system call
    int 0x80

call_shellcode:
    call shellcode
    message: db "Hello World!", 0xA

As you can see, there are just a few differences between this and the original application.

The biggest difference is zeroing out the register with xor, and then only moving a parameter into al (the 8-bit register).

The reason for using the 8-bit register is to avoid null bytes, as you can see below.

b8 01 00 00 00          mov    eax,0x1
b0 01                   mov    al,0x1

As far as the JMP-CALL-POP technique, you can understand that through the comments.

Converting to Shellcode

I won't cover execution tracing for this version, as it is very similar to the earlier "Hello World!" example.

First, I ran my compiled assembly to make sure that it worked.

doyler@slae:~/SLAE-Code/Shellcode/HelloWorld$ ./HelloWorldShellcode
Hello World!

For a quick verification, I ran the executable through objdump to verify that there were no null bytes or hard-coded addresses.

doyler@slae:~/SLAE-Code/Shellcode/HelloWorld$ objdump -d HelloWorldShellcode -M intel
HelloWorldShellcode:     file format elf32-i386

Disassembly of section .text:

08048060 <_start>:
8048060:    eb 17                    jmp    8048079 <call_shellcode>

08048062 <shellcode>:
8048062:    31 c0                    xor    eax,eax
8048064:    b0 04                    mov    al,0x4
8048066:    31 db                    xor    ebx,ebx
8048068:    b3 01                    mov    bl,0x1
804806a:    59                       pop    ecx
804806b:    31 d2                    xor    edx,edx
804806d:    b2 0d                    mov    dl,0xd
804806f:    cd 80                    int    0x80
8048071:    31 c0                    xor    eax,eax
8048073:    b0 01                    mov    al,0x1
8048075:    31 db                    xor    ebx,ebx
8048077:    cd 80                    int    0x80

08048079 <call_shellcode>:
8048079:    e8 e4 ff ff ff           call   8048062 <shellcode>

0804807e <message>:
804807e:    48                       dec    eax
804807f:    65                       gs
8048080:    6c                       ins    BYTE PTR es:[edi],dx
8048081:    6c                       ins    BYTE PTR es:[edi],dx
8048082:    6f                       outs   dx,DWORD PTR ds:[esi]
8048083:    20 57 6f                 and    BYTE PTR [edi+0x6f],dl
8048086:    72 6c                    jb     80480f4 <message+0x76>
8048088:    64 21 0a                 and    DWORD PTR fs:[edx],ecx

Next, I used this awesome/crazy one-liner to convert my binary to shellcode.

doyler@slae:~/SLAE-Code/Shellcode/HelloWorld$ 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'
"\xeb\x17\x31\xc0\xb0\x04\x31\xdb\xb3\x01\x59\x31\xd2\xb2\x0d\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xcd\x80\xe8\xe4\xff\xff\xff\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21\x0a"

With the shellcode in hand, I added it to my wrapper C program, to verify execution.

#include<stdio.h>
#include<string.h>

unsigned char code[] = \
"\xeb\x17\x31\xc0\xb0\x04\x31\xdb\xb3\x01\x59\x31\xd2\xb2\x0d\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xcd\x80\xe8\xe4\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();
}

Once I inserted my shellcode, I compiled the application (making sure to disable any stack protection). This worked, and I was left with a shellcode of length 43!

doyler@slae:~/SLAE-Code/Shellcode/HelloWorld$ gcc -o shellcode -fno-stack-protector -z execstack shellcode.c
doyler@slae:~/SLAE-Code/Shellcode/HelloWorld$ ./shellcode
Shellcode Length:  43
Hello World!

Using the Stack

It is also possible to utilize the stack for my message, as opposed to a variable.

In this case, I just need to push the string onto the stack, in little endian order.

The Code

First, I’ll start by just sharing my final application code. It is very well commented, but I’ll also explain it a bit further below.

; HelloWorldStack.nasm
; Author: Ray Doyle

global _start
section .text
_start:
    ; Move the value 4 into EAX (system call for write)
    ; Zeros out the register and then uses al to avoid nulls in the resulting shellcode
    xor eax, eax
    mov al, 0x4

    ; Move the value 1 into EBX (fd1 = STDOUT)
    xor ebx, ebx
    mov bl, 0x1
    
    ; Zero out the EDX register and push it onto the stack (null terminate the output string)
    xor edx, edx
    push edx
    
    ; Push "Hello World\n" onto the stack in reverse order
    ; "\ndlr"
    push 0x0a646c72
    ; "oW o"
    push 0x6f57206f
    ; "lleH"
    push 0x6c6c6548
    
    ; Move the stack pointer (points to the beginning of the string) into ECX
    mov ecx, esp
    
    ; Move the value 12 into EDX (length of "Hello World\n1")
    mov dl, 12
    
    ; Send an 0x80 interrupt to invoke the system call
    int 0x80

    ; Exit the program gracefully
        
    ; Move the value 1 into EAX (system call for exit)
    xor eax, eax
    mov al, 0x1

    ; Zero out the EBX register for a successful exit status
    xor ebx, ebx

    ; Send an 0x80 interrupt to invoke the system call
    int 0x80

This is fairly similar to the JMP-CALL-POP technique, only I push the hard-coded string onto the stack directly.

Converting to Shellcode

First, I compiled my assembly and ran it to verify that it worked.

doyler@slae:~/SLAE-Code/Shellcode/HelloWorld$ ./compile.sh HelloWorldStack
[+] Assembling with Nasm ... 
[+] Linking ...
[+] Done!
doyler@slae:~/SLAE-Code/Shellcode/HelloWorld$ ./HelloWorldStack 
Hello World!

Again, I ran the executable through objdump to verify that there were no null bytes or hard-coded addresses.

doyler@slae:~/SLAE-Code/Shellcode/HelloWorld$ objdump -d HelloWorldStack -M intel

HelloStack:     file format elf32-i386

Disassembly of section .text:

08048060 <_start>:
 8048060:	31 c0                	xor    eax,eax
 8048062:	b0 04                	mov    al,0x4
 8048064:	31 db                	xor    ebx,ebx
 8048066:	b3 01                	mov    bl,0x1
 8048068:	31 d2                	xor    edx,edx
 804806a:	52                   	push   edx
 804806b:	52                   	push   edx
 804806c:	6a 0a                	push   0xa
 804806e:	68 72 6c 64 21       	push   0x21646c72
 8048073:	68 6f 20 57 6f       	push   0x6f57206f
 8048078:	68 48 65 6c 6c       	push   0x6c6c6548
 804807d:	89 e1                	mov    ecx,esp
 804807f:	b2 0d                	mov    dl,0xd
 8048081:	cd 80                	int    0x80
 8048083:	31 c0                	xor    eax,eax
 8048085:	b0 01                	mov    al,0x1
 8048087:	31 db                	xor    ebx,ebx
 8048089:	cd 80                	int    0x80

Next up, I used the same one-liner from before to convert this version to shellcode.

doyler@slae:~/SLAE-Code/Shellcode/HelloWorld$ objdump -d ./HelloWorldStack|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'
"\x31\xc0\xb0\x04\x31\xdb\xb3\x01\x31\xd2\x52\x52\x6a\x0a\x68\x72\x6c\x64\x21\x68\x6f\x20\x57\x6f\x68\x48\x65\x6c\x6c\x89\xe1\xb2\x0d\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xcd\x80"

Finally, I recompiled my shellcode wrapper program, and it worked! The shellcode actually had the same length as the JMP-CALL-POP version, which I thought was funny.

doyler@slae:~/SLAE-Code/Shellcode/HelloWorld$ gcc -o shellcode -fno-stack-protector -z execstack shellcode.c 
doyler@slae:~/SLAE-Code/Shellcode/HelloWorld$ ./shellcode 
Shellcode Length:  43
Hello World!

Tracing the Execution

Finally, I used GDB to trace the program's execution, and view the stack.

doyler@slae:~/SLAE-Code/Shellcode/HelloWorld$ gdb -q shellcode
Reading symbols from /home/doyler/SLAE-Code/Shellcode/HelloWorld/shellcode...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) print/x &code
$1 = 0x804a040
(gdb) break *0x804a040
Breakpoint 1 at 0x804a040
(gdb) r
Starting program: /home/doyler/SLAE-Code/Shellcode/HelloWorld/shellcode 
Shellcode Length:  43

Breakpoint 1, 0x0804a040 in code ()
(gdb) disassemble 
Dump of assembler code for function code:
=> 0x0804a040 <+0>:	xor    eax,eax
   0x0804a042 <+2>:	mov    al,0x4
   0x0804a044 <+4>:	xor    ebx,ebx
   0x0804a046 <+6>:	mov    bl,0x1
End of assembler dump.
(gdb) define hook-stop
Type commands for definition of "hook-stop".
End with a line saying just "end".
>disassemble 
>x/20cb $esp
>end

... snip ...

(gdb) stepi
Dump of assembler code for function code:
=> 0x0804a04e <+14>:	push   0x21646c72
   0x0804a053 <+19>:	push   0x6f57206f
   0x0804a058 <+24>:	push   0x6c6c6548
   0x0804a05d <+29>:	mov    ecx,esp
End of assembler dump.
0xbffff2e0:	10 '\n'	0 '\000'	0 '\000'	0 '\000'	0 '\000'	0 '\000'	0 '\000'	0 '\000'
0xbffff2e8:	0 '\000'	0 '\000'	0 '\000'	0 '\000'	48 '0'	-124 '\204'	4 '\004'	8 '\b'
0xbffff2f0:	16 '\020'	-123 '\205'	4 '\004'	8 '\b'
0x0804a04e in code ()
(gdb) stepi
Dump of assembler code for function code:
   0x0804a04e <+14>:	push   0x21646c72
=> 0x0804a053 <+19>:	push   0x6f57206f
   0x0804a058 <+24>:	push   0x6c6c6548
   0x0804a05d <+29>:	mov    ecx,esp
End of assembler dump.
0xbffff2dc:	114 'r'	108 'l'	100 'd'	33 '!'	10 '\n'	0 '\000'	0 '\000'	0 '\000'
0xbffff2e4:	0 '\000'	0 '\000'	0 '\000'	0 '\000'	0 '\000'	0 '\000'	0 '\000'	0 '\000'
0xbffff2ec:	48 '0'	-124 '\204'	4 '\004'	8 '\b'
0x0804a053 in code ()
(gdb) stepi
Dump of assembler code for function code:
   0x0804a04e <+14>:	push   0x21646c72
   0x0804a053 <+19>:	push   0x6f57206f
=> 0x0804a058 <+24>:	push   0x6c6c6548
   0x0804a05d <+29>:	mov    ecx,esp
End of assembler dump.
0xbffff2d8:	111 'o'	32 ' '	87 'W'	111 'o'	114 'r'	108 'l'	100 'd'	33 '!'
0xbffff2e0:	10 '\n'	0 '\000'	0 '\000'	0 '\000'	0 '\000'	0 '\000'	0 '\000'	0 '\000'
0xbffff2e8:	0 '\000'	0 '\000'	0 '\000'	0 '\000'
0x0804a058 in code ()

... snip ...

(gdb) c
Continuing.
Hello World!
[Inferior 1 (process 26783) exited normally]
Error while running hook_stop:
No frame selected.

As you can see, the stack actually has the "Hello World!" string in the proper order

Hello World Shellcode - Conclusion

It is awesome to finally be working on shellcode, and I'm really looking forward to the rest of this course.

I've still got a few more non-exam related posts on the way (maybe 5), but let me know if there is an assembly overload!

Finally, you can find the code and updates in my GitHub repository.

doyler on Githubdoyler on Twitter
doyler
Ray Doyle is an avid pentester/security enthusiast/beer connoisseur who has worked in IT for almost 16 years now. From building machines and the software on them, to breaking into them and tearing it all down; he's done it all. To show for it, he has obtained an OSCP, eCPPT, eWPT, eWPTX, eMAPT, Security+, ICAgile CP, ITIL v3 Foundation, and even a sabermetrics certification!

He currently serves as a Senior Penetration Testing Consultant for Secureworks. His previous position was a Senior Penetration Tester for a major financial institution.

When he's not figuring out what cert to get next (currently GXPN) or side project to work on, he enjoys playing video games, traveling, and watching sports.

Leave a Comment

Filed under Security Not Included

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.