Assignment N°5 - Shellcode Analyzing / Dissecting
Assignment Goals (SLAE-1530)
-
Take up at least 3 shellcode samples created using Msfpayload for Linux/x86.
-
Use GDB/Ndisasm/Libemu to dissect the functionality of the shellcode.
-
Present your analysis.
Shellcode Candidates
We will use Msfvenom
from Metasploit Framework to generate three different payloads for Linux x86-32.
We can easily enumerate payloads for this architecture and operating system using the following command:
local@user:$ msfvenom -l payloads | grep "linux/x86"
We decided to use the three following payloads:
linux/x86/read_file
linux/x86/chmod
linux/x86/exec
Shellcode N°1 - linux/x86/read_file
(Ndisasm)
Generate Payload
To generate our shellcode, let's first understand which parameters are expected for this payload.
local@user:$ msfvenom -p linux/x86/read_file -a x86 --platform Linux --list-options
Name Current Setting Required Description
---- --------------- -------- -----------
FD 1 yes The file descriptor to write output to
PATH yes The file path to read
By default output file content is appended to File Descriptor 1 (stdout
), we will keep it as is.
We need however to specify the file to be read by the payload. We will choose /etc/passwd
file. This file can be read by any user.
We can now generate our final payload to local disk as binary file payload1.bin
.
local@user:$ msfvenom -p linux/x86/read_file -a x86 --platform Linux PATH=/etc/passwd -f RAW > payload1.bin
Static Analysis (Ndisasm)
Let's use Ndisasm to retrieve as close as possible assembly instructions that compose this payload.
local@user:$ cat payload1.bin | ndisasm -p intel -b32 -
Output Assembly Analysis
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Jump Call Pop Begin.
; #JUMP.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
00000000 EB36 jmp short 0x38 ; Jump to 0x38 offset.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Open File.
; Syscall n°5 (0x5)
; open(const char *filename, int flags, umode_t mode)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
00000002 B805000000 mov eax,0x5 ; syscall_open()
00000007 5B pop ebx ; #POP. "filename" parameter points to `/etc/passwd`
00000008 31C9 xor ecx,ecx ; "flags" equal to zero.
0000000A CD80 int 0x80 ; call syscall.
0000000C 89C3 mov ebx,eax ; save returned value to ebx. returned value is file handle.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Read File.
; Syscall n°3 (0x3)
; read(unsigned int fd, char *buf, size_t count)
; -- REGISTERS --
; ebx = File descriptor obtained via "open" syscall.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
0000000E B803000000 mov eax,0x3 ; syscall_read()
00000013 89E7 mov edi,esp ; copy current stack address to edi register.
00000015 89F9 mov ecx,edi ; copy edi register to ecx register;
; this is where file content will be placed (buff)
00000017 BA00100000 mov edx,0x1000 ; "count" equal to 4096 bytes. Number of bytes to read
0000001C CD80 int 0x80 ; call syscall.
0000001E 89C2 mov edx,eax ; save returned value to ebx. returned value is the number of bytes read.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Write File.
; Syscall n°4 (0x4)
; write(unsigned int fd, const char *buf, size_t count)
; -- REGISTERS --
; ecx = Stack address used to read file content ("buff") via "read" syscall.
; edx = bytes read from "read" syscall.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
00000020 B804000000 mov eax,0x4 ; syscall_write()
00000025 BB01000000 mov ebx,0x1 ; write to file descriptor 0x1 (stdout)
0000002A CD80 int 0x80 ; call syscall.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Exit Gracefully.
; Syscall n°1 (0x1)
; exit(int error_code)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
0000002C B801000000 mov eax,0x1 ; syscall_exit()
00000031 BB00000000 mov ebx,0x0 ; error_code equal to zero.
00000036 CD80 int 0x80 ; call syscall.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; #CALL
; Next address 0x3D is pushed on stack by "call" instruction.
; 0x3D contains target file to read (/etc/passwd)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
00000038 E8C5FFFFFF call 0x2 ; Go to 0x00000002 offset.
0000003D 2F das ; /
0000003E 657463 gs jz 0xa4 ; etc
00000041 2F das ; /
00000042 7061 jo 0xa5 ; pa
00000044 7373 jnc 0xb9 ; ss
00000046 7764 ja 0xac ; wd
00000048 00 db 0x00 ; NULL
Using static analysis was sufficient to understand every payload actions:
- Use Jump Call Pop technique to get address of target file (
/etc/passwd
). - Open the target file. File handle / descriptor is stored in
ebx
. - Read first 4096 Bytes to stack. Bytes read is stored in
edx
. - Write those 4096 Bytes to file descriptor
0x1
. - Exit gracefully.
Shellcode N°2 - linux/x86/chmod
(GDB)
This time, instead of using Ndisasm to conduct a static analysis we will use GDB to understand what next payload is doing.
Generate Payload
To generate our shellcode, let's first understand which parameters are expected for this payload.
local@user:$ msfvenom -p linux/x86/chmod -a x86 --platform Linux --list-options
Name Current Setting Required Description
---- --------------- -------- -----------
FILE /etc/shadow yes Filename to chmod
MODE 0666 yes File mode (octal)
Both required parameters are already set. Since we are going to execute this payload we will set FILE
to a harmless value.
We will create a dummy file:
local@user:$ touch /tmp/test
Then we will generate our payload and set output format to C.
local@user:$ msfvenom -p linux/x86/chmod -a x86 --platform Linux -f C FILE=/tmp/test
unsigned char buf[] =
"\x99\x6a\x0f\x58\x52\xe8\x0a\x00\x00\x00\x2f\x74\x6d\x70\x2f"
"\x74\x65\x73\x74\x00\x5b\x68\xb6\x01\x00\x00\x59\xcd\x80\x6a"
"\x01\x58\xcd\x80";
We will now place this array in our C template for shellcoding.
#include
#include
unsigned char buf[] =
"\x99\x6a\x0f\x58\x52\xe8\x0a\x00\x00\x00\x2f\x74\x6d\x70\x2f"
"\x74\x65\x73\x74\x00\x5b\x68\xb6\x01\x00\x00\x59\xcd\x80\x6a"
"\x01\x58\xcd\x80";
main()
{
printf("Shellcode Length: %d\n", strlen(buf));
int (*ret)() = (int(*)())buf;
ret();
}
We can compile our shellcode using the following command:
local@user:$ gcc shellcode.c -o shellcode -z execstack
Analyzing Shellcode with GDB
local@user:$ gdb ./shellcode
First step is to breakpoint to the shellcode itself.
GDB> b *&buf
Breakpoint 1 at 0x2020
We can now run the program
GDB> r
GDB will break when it reaches our shellcode location.
We can see the assembly code of our shellcode using the disassemble
command.
GDB> disassemble
=> 0x00402020 <+0>: cdq
0x00402021 <+1>: push 0xf
0x00402023 <+3>: pop eax
0x00402024 <+4>: push edx
0x00402025 <+5>: call 0x402034
0x0040202a <+10>: das
0x0040202b <+11>: je 0x40209a
0x0040202d <+13>: jo 0x40205e
0x0040202f <+15>: je 0x402096
0x00402031 <+17>: jae 0x4020a7
0x00402033 <+19>: add BYTE PTR [ebx+0x68],bl
0x00402036 <+22>: mov dh,0x1
0x00402038 <+24>: add BYTE PTR [eax],al
0x0040203a <+26>: pop ecx
0x0040203b <+27>: int 0x80
0x0040203d <+29>: push 0x1
0x0040203f <+31>: pop eax
0x00402040 <+32>: int 0x80
0x00402042 <+34>: add BYTE PTR [eax],al
At first glance it seems that some instruction are broken.
Lets also dump registers values to trace future changes.
GDB> info register
eax 0x402020 0x402020
ecx 0x0 0x0
edx 0x40064a 0x40064a
ebx 0x401fd4 0x401fd4
esp 0xbfffefbc 0xbfffefbc
<...snip...>
It is important to monitor registers between each instructions especially when you don't know what a specific instruction is used for.
Let's continue to next instruction.
GDB> n
Our edx
register is now equal to zero.
GDB> print/x $edx
It seems that cdq
instruction was used to clear the edx
register.
GDB> n
GDB> n
GDB> n
Last three instructions was used to respectively place value 0xf
to eax
register and push a new zero to the top of the stack.
Interestingly, the call instruction attempts to reach an invalid instruction location 0x402034
.
Lets add a new breakpoint to this address and continue execution.
GDB>b *0x402034
GDB>c
esp
register now points to a new location. This is perfectly normal, when call
instruction is called, the next instruction offset is pushed on the top of the stack.
The next instruction after the call
was at offset 0x40202a
. This offset contains what seems to be our target file name. We can confirm that using following commands:
GDB> x/x $esp
0xbfffefb4: 0x0040202a
GDB> x/s *(char**)$esp
or x/s 0x40202a
0x40202a : "/tmp/test"
This trick was used to place our target file name in memory and retrieve it location using the call
instruction. It is a variant of the Jump Call Pop technique but without the Jump.
GDB> n
File name location was placed on ebx
register using the pop
instruction.
GDB> n
GDB> n
Last two instructions was used to place on ecx
the value 0x1b6
We are currently stopped at a syscall call instruction.
=> 0x40203b : int 0x80
Let's dump our registers
GDB> info register
eax 0xf 0xf
ecx 0x1b6 0x1b6
edx 0x0 0x0
ebx 0x40202a 0x40202a
<...snip...>
eax
register contains0xf
which is the syscall number ofchmod(const char *filename, umode_t mode)
.ebx
register contains0x40202a
which is the address of our file name/etc/test
.ecx
register contains0x1b6
which represent the chmod mode666
.
GDB> n
chmod syscall was successfully triggered, we are sure of that because eax
register contains 0x00
which means chmod function succeed.
We can verify if our target file permissions have changed:
GDB> !ls -l /tmp/test
-rw-rw-rw- 1 phrozen phrozen 0 Jun 9 07:48 /tmp/test
GDB> n
GDB> n
Last two instructions was used to place on eax
the value 0x1
GDB> n
We are currently stopped at a syscall call instruction.
=> 0x402040 : int 0x80
eax
register contains0x1
which is the syscall number ofexit(int error_code)
GDB> n
We've now reached the end of our shellcode and gracefully exit our host program.
Using GDB we were able to understand what the actual shellcode was doing, if we had used Ndisasm we would have been confused by the call
instruction.
Shellcode N°3 - linux/x86/exec
(Libemu)
For our last payload we will use Libemu to analyze what our shellcode is doing.
You can find instructions about how to download and install Libemu on their official website.
In our guest machine, Libemu was installed on /opt/libemu
path.
In some Linux distributions you may install Libemu via aptitude:
local@user:$ apt install libemu-dev
Generate Payload
To generate our shellcode, let’s first understand which parameters are expected for this payload.
local@user:$ msfvenom -p linux/x86/exec -a x86 --platform Linux --list-options
Name Current Setting Required Description
---- --------------- -------- -----------
CMD yes The command string to execute
Only one parameter is mandatory, the command to execute. We will set /bin/ls
as command.
local@user:$ msfvenom -p linux/x86/exec -a x86 --platform Linux CMD=/bin/ls -f raw | /opt/libemu/bin/sctest -vvv -Ss 100000
Analyzing Shellcode with Libemu
After few seconds we should see the following output
This screenshot contains the "registry dump" after each instructions. This is very useful to trace what the shellcode is doing instruction by instruction.
At the end of the analysis, when this is possible, Libemu generate some pseudo-code to have a clear idea of what the shellcode is doing without spending to much time in understanding each assembly instructions.
int execve (
const char * dateiname = 0x00416fc0 =>
= "/bin/sh";
const char * argv[] = [
= 0x00416fb0 =>
= 0x00416fc0 =>
= "/bin/sh";
= 0x00416fb4 =>
= 0x00416fc8 =>
= "-c";
= 0x00416fb8 =>
= 0x0041701d =>
= "/bin/ls";
= 0x00000000 =>
none;
];
const char * envp[] = 0x00000000 =>
none;
) = 0;
From above pseudo-code, it is clear that Libemu has detected that the shellcode was using execve
syscall to execute a new command via the /bin/sh
program.
The full command is: /bin/sh -c "/bin/sh -c /bin/ls"
We can even generate a graph of what shellcode is doing in dot format.
local@user:$ msfvenom -p linux/x86/exec -a x86 --platform Linux CMD=/bin/ls -f raw | /opt/libemu/bin/sctest -vvv -Ss 100000 -G exec.dot
We can easily convert the dot file format to any compatible image format using the dot
utility.
local@user:$ dot exec.dot -Tpng -o exec.png
This comes very handy when it comes include further detail in our report analysis.
All content on this website is protected by a disclaimer. Please review it before using our site
June 15, 2020, 11:06 a.m. | By Jean-Pierre LESUEUR