Address
304 North Cardinal St.
Dorchester Center, MA 02124
Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM
Address
304 North Cardinal St.
Dorchester Center, MA 02124
Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM
Once I finished module 1 of the SLAE course, it was time to move on to some hello world shellcode.
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.
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.
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!
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.
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.
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!
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
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.
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 OSCE, OSCP, eCPPT, GXPN, eWPT, eWPTX, SLAE, eMAPT, Security+, ICAgile CP, ITIL v3 Foundation, and even a sabermetrics certification!
He currently serves as a Senior Staff Adversarial Engineer for Avalara, and his previous position was a Principal Penetration Testing Consultant for Secureworks.
This page contains links to products that I may receive compensation from at no additional cost to you. View my Affiliate Disclosure page here. As an Amazon Associate, I earn from qualifying purchases.