论坛首页 逆向工程技术区 阅读主题

[原创]Nepctf2025 pwn部分wp

277 浏览 1 回复
#1 楼主 2026-06-01 21:09:20
今年的nep和往年比可以说很简单,但对我来说还是有挑战的 QwQ之前的半个月我对二进制安全有点摆烂,但是nep一下把我的状态打满了 :)本次的成绩为4/5,差一道webpwn 向全局变量出入一个100字节的name,然后列出根目录下的文件检查输入的文件名,如果是flag就要重新输入(注意此时flag被输入了filename)然后开启一个子线程运行work函数先计算filename的md5,然后如果文件存在,就把文件读入内存,并打印name很明显这里存在着非栈上格式化字符串漏洞,通过%p/%x可以将读入内存的文件内容dump出来现在我们需要的是打开flag文件,可是input_filename有过滤线程与进程关系进程(Process) 是资源的集合(有自己独立的内存空间、文件描述符、栈等)线程(Thread) 是进程中的一个执行流(共享内存、文件描述符,但有自己栈、寄存器等)。线程是"轻量级"的进程,多个线程共享一个进程的大部分资源。线程竞争线程竞争(race condition)是指多个线程同时访问共享资源,并试图对其进行读写操作时,由于访问时序不确定,导致程序执行结果不一致或错误的现象通常产生于本题中,work线程和main进程同时访问了filename,分别为读入与打开同时由于work进行了一次md5计算,打开时刻会靠后 我们只需要在输入一个一个文件名后立马输入第二个文件名flag就有可能实现在open前替换filename为flag然后通过格式化字符串将flag打印解码flag程序逻辑很简单先fork,子进程进入死循环,父进程执行沙箱后任意代码执行其中沙箱只允许父进程进行ptrace调用常见的请求宏我们使用的gdb就是依赖于ptrace调用,我们在gdb中可以对被调试的进程进行内存写入,内存查看,寄存器修改等等操作,几乎可以控制子进程的所有行为回到题目,我们看到子进程在fork后立马进入了while(1),并没有开启沙箱,所以思路很简单:父进程附加到子进程上,将shellcode注入子进程,后修改子进程的RIP修改为0xDEADC0DE000,最后脱离子进程,子进程便开始执行父进程注入的shellcode我们可以从栈上获得子进程的进程号,然后attach上去,注意attach需要相对比较长的时间通常情况下可以使用wait调用等到附加成功,但本题只能调用ptrace,我们可以写一个loop强行等待到attach成功然后就是常规的将shellcode写入子进程,最后PTRACE_DETACH脱离子进程自定义的菜单题,实现了一个可以以usr和master身份管理一个记事本的系统其中init中实现了对内容堆块的申请,初始化,并为每个堆块标记了权限其中operate_chunk的结构为chunk[0]被标记为0x10,chunk[1~9]为3,其余为2在usr操作中,可以进行的子操作有usr_write和usr_read其中检查函数对usr_read无检查,usr_write会通过&操作检查权限是否符合3&1=1/2&1=0所以usr_read只能写chunk[1~9]然后根据操作字符串,映射对应的操作码将造作字符串转化为对应的操作码manager_operation中提供了两类操作,分别是manager_write/read与manager_write_usr/manager_read_usrcheck复用了usr中的check,仍然是对read操作无检查对manager_write进行了堆块权限的检查对manager_operate_usr是根据进行的最后一次usr操作的chunk确定,还会额外调用checkvisit进行检查要求进行操作的堆块进行的最后一次usr操作的操作码不能是4(正常来说,必须是能在usr态编辑的chunk才能保证操作码不为4)由于这个系统对read的检查不够严格,我们可以随便read,其中最特殊的chunk便是chunk[0],拥有最特殊的权限0x10, chunk[0]中也包含着堆地址与PIE偏移我们先把exp.py的自定义函数写好先是manager_read(0),获得两个地址 在manager_write_usr中,操作的堆地址便是从*((void* )(chunk[0])+1)中获得如果我们可以控制chunk[0],便可以对任意地址进行单次0xff大小的写入但是想要操作chunk[0],使用usr_write或manager_write是不行的,因为会有严格的权限检查使用manager_write_usr,虽然不会进行权限检查,但是要求被操作的堆块在usr态被标记了非read操作但是我们又无法使用usr_write为chunk[0]打上write操作码1check检查最大的问题就是,完全没有考虑以usr态输入manager相关操作字符串会怎么样?首先是可以通过check检查,因为如果以usr态进入且操作字符串不为usr_write就会返回1在permission_confirm中,并不会检查身份,即使以usr身份进行manager操作,也会返回合法的操作码也就是如果我们以usr身份对chunk[0]进行manager操作,不光不会报错,还可以为chunk[0]添加一个非4的操作码,这样我们就可以用manager_write_usr操作chunk[0]了然后我们可以对任意地址进行写,但是我们只有一次机会,因为把chunk[0]修改后便无法再次直接使用manager_write_usr操作chunk[0]我们无法使用manager_write操作chunk[0]是因为chunk[0]的权限太特殊了,是0x10如果我们把chunk[0]的权限修改为0xf,那么manager_write便可以直接操作chunk[0]:配合manager_write_usr可以实现任意地址写;配合manager_read可以实现任意地址读对got表进行读,获得libc_base,从而获得environ地址对environ进行读,获得stack地址对manager_operat的返回地址进行写,通过ROP进行getshell实际操作中,不知道为什么远程的got表中地址和本地不一致,无法计算libc,转而使用IO指针,才得到libc_base依旧菜单题init中把flag读入bss中,并开启沙箱,白名单留下open,read,writeIDA的反编译没有把异常处理部分展示出menu处的异常处理为visit与leave的异常处理部分其中visit用于创造堆块,编辑堆块,但是create把输入大小与malloc分为两个单独的步骤且size会检查整形溢出,edit不会检查下标在leave中,允许我们将一个chunk的内容复制到leave的栈中,并通过size检测是否栈溢出并抛出异常c++ 异常处理在抛出(throw)异常后,执行两个主要步骤:在 C++ 异常处理中,通过返回地址逆向查找异常处理器:也就是说,触发异常时根据返回地址确定异常抛出于哪个try代码块中,并查看处理器是否能处理此处异常当我们在leave中栈溢出覆盖返回地址,但不改变返回地址,我们就可以调用leave相关异常处理函数le

...(已截断)

---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-287806.htm
#2 2026-06-01 21:09:20
师傅,有原附件吗?time文件有些部分被nop掉了

请登录后参与讨论

立即登录 注册账号