SLAE A.6 - Polymorphic Shellcode
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-1294
A polymorphic version of shellcode uses different instructions while maintaining the original functionality, particularly in order to evade IDS or antivirus systems that may be searching for signatures. This sixth assignment is to take three samples of linux/x86 shellcode from the shell-storm.org database and manually create polymorphic versions of them. It is required that the new versions are no more than 50% larger than the originals.
The following shellcodes were analysed and recreated with different instructions/approaches.
These were chosen because I can reasonably easily test their effects on my Kali VM without destroying it, and the overall size of each is not too large.
nc bind shell
- Original shellcode - 64 bytes (incorrectly marked as 62 bytes)
- My version - 89 bytes (+39%)
This shellcode is provided in nasm format already. Its purpose is to provide a bind shell, not by listening for clients itself but by invoking a local copy of netcat: /bin/nc -lvve/bin/sh -vp13377
. I checked it carefully, built it, and confirmed that it works.
I noticed something a little odd at the start. It clears some registers then pushes the string -vp13377
onto the stack.
This parameter is missing a null terminator. This can cause the wrong port to be selected. Since nc
is parsing everything after -p
as a port number it will stop as soon as it reaches a non-numeric character. Most of the time this is fine. However if the next byte on the top of the stack happens to be a valid ASCII number it will be attached to the number 11337. In this case the port number will overflow:
# nc -lvp 113371
listening on [any] 47835 ...
I consider this a bug in the original source code and I will fix it in my own version by pushing a null to the stack first.
Original | New |
---|---|
We don’t need to zero out edx
at this stage. The goal is to place 0 inside eax
- instead of using xor, subtract it from itself.
Original | New |
---|---|
First a new push eax
is inserted to ensure this argument is properly terminated, as discussed above.
The dword pushes are converted to pushes of two words each. This requires additional opcodes but means those tell-tale constants are broken into smaller, separate chunks in memory.
The pointer is stored in esi
instead of edx
.
Original | New |
---|---|
The original push eax
provides a null terminator. Here we will use push ax
instead, which will still give us two zero bytes on the stack. Unfortunately this requires a two byte op-code where the original was one byte.
The constants are obfuscated the same way as before. This time the pointer is stored in edi
instead of ecx
.
Original | New |
---|---|
The last string is handled much the same, except we remove the middle push to save space. The original pushes the string “/bin//////nc”, probably an attempt to evade AV that is looking for “/bin//nc”. I am doing my own word-splitting evasion here so I’ll take it out.
This pointer is stored in ecx
instead of ebx
, using two steps because we can. The original code had a mov ecx,esp
in an earlier position so I may as well avoid it.
Original | New |
---|---|
Zeroing edx
is deferred to this point. The stack is set up like before except we’re using a different combination and order of registers. Because we didn’t use ebx
already for the path to nc
it must be configured as an extra step.
ecx
is pointed to the structure we just created using xor
and add
instead of a mov
.
Original | New |
---|---|
The syscall number can be split into two steps. I decided to leave the int 0x80
alone. This instruction is hopefully not too suspicious.
This builds and works fine. I test that I can execute it and use the created reverse shell.
iptables –flush shellcode
- Original shellcode - 43 bytes
- My version - 55 bytes (+28%)
Since the source code was given in AT&T format I decided to take the assembled format and disassemble it with ndisasm. The corresponding disassembly is in the GitHub repository.
This one is relatively straightforward and not so different from the nc
shellcode above. The main job it is doing is setting up the parameters for execve
on the stack.
This time I will try to use a different technique—PUSHAD. This single op-code causes eax
, ecx
, edx
, ebx
, esp
, ebp
, esi
, and edi
to be pushed onto the stack all at once in that order. Instead of pushing dword constants onto the stack I can load them carefully into registers, then place them all on the stack all at once.
Original | New |
---|---|
In the first place we still need a zeroed register. I simply choose a different register.
I also push esi
. The path to iptables is going to span four registers—eax
, ecx
, edx
and ebx
. The next register in the pushad operation is esp
and I can’t fiddle with that. I still need a null terminator, so I push it in advance.
Original | New |
---|---|
The -F
parameter is formed using half of the last register to be pushed, edi
. After the pushad completes, esp
will point to this string.
Original | New |
---|---|
All of the strings that would be pushed as literals are lined up in the corresponding registers instead. I haven’t attempted to obfuscate the literals itself in this example. This is perhaps fairly weak evasion but it could be combined with other techniques too if necessary.
Next the argv array must be set up. I will do this by pushing pointers as before, except this time I will be pushing pointers by their relative positions from esp
.
Original | New |
---|---|
Now that argv
is on top of stack, all that remains is to put the correct arguments into registers for the execve
syscall.
Original | New |
---|---|
I use some trivial two-instruction substitutions to make this look different. Ultimately execve
is called with the correct parameters.
I run my modified version and confirm that my iptables rules are flushed.
chmod 666 /etc/shadow shellcode
- Original shellcode - version in comments - 40 bytes
- My version - 57 bytes (+43%)
This shell-storm source file is a little confusing because there are two versions in the same file. I took the assembled version from the comments and disassembled it with ndisasm.
Without going into too much detail, I analysed it and confirmed that it works in a test program. It does the following:
- Push “/etc/shadowS” onto the stack
- Fix the last byte so it’s a null instead of
S
- Set parameter 0666 = 0x1b6 in
ecx
- Point to “/etc/shadow” in
ebx
- Execute the
chmod
system call (15)
The biggest difference in my version is how I load the data onto the stack. I obfuscate the string literals by XORing each dword with the key 0x11223344. This has the additional advantage that I can include the null byte directly in the encoded version and don’t need to fix it up later. The encoded values were calculated with python:
>>> hex(0x53776f64 ^ 0x11223344)
'0x42555c20'
>>> hex(0x6168732f ^ 0x11223344)
'0x704a406b'
>>> hex(0x6374652f ^ 0x11223344)
'0x7256566b'
With that explained, let’s go from top to bottom.
Original | New |
---|---|
In this shellcode ebx
did not need to be cleared at all. However ecx
will need to be zero later so I modify this instruction to do it here.
Original | New |
---|---|
Here we see the encoded strings being decoded and pushed onto the stack. Originally I tried using a small routine for the decoding but with so little data it actually ended up smaller to just repeat the xor
instructions.
Original | New |
---|---|
Use an extra level of indirection that ends up placing esp
in ebx
.
Original | New |
---|---|
This time ecx is already zero because of the changed xor operation at the beginning. The xor encoding means we no longer need to update the byte at esp+0xb
.
Instead of placing 0x1b6 directly in the register we will use double that value, 0x36c, and then use an arithmetic shift right to divide it by 2.
Original | New |
---|---|
The original loads the number 15 into eax
, taking a little care to avoid null bytes in the op-codes. This time we will start with the known value in ecx
and subtract a constant to get back to 15.
Original | New |
---|---|
I have taken a small liberty here. If the chmod
works eax
will contain the return value 0. Therefore we don’t need to clear it. If the chmod
fails - perhaps the program is not running as root - then it may not exit cleanly. If this is important it could be re-inserted for a cost of 2 extra bytes.
This new version works equivalently to the old one. Here it is making my computer insecure.