2022某线上赛比赛复盘
re
要求掌握的基础知识技能: IDA使用、程序编译框架识别、go语言开发、go编译后程序的特点、aes加解密
用到的工具:ida 7.6 sp1及以上、file命令
首先打开压缩包解压,查看下程序是什么语言、架构的程序。
使用file命令可以查看程序的目标系统名称、文件格式以及部分编译语言信息,从这里可以看出来程序是x64 linux系统上的ELF格式的文件,采用go语言编译,没有调试符号。
IDA 7.6 sp1及以上版本对于go语言的识别和逆向支持的比较完善,低版本IDA需要使用开源的go语言符号解析插件才能支持符号识别。这里用IDA 7.6 sp1打开目标程序。
IDA载入程序,等待IDA分析完毕之后,可以自动识别并展示程序当中的符号信息,之后进入main_main函数,按TAB或F5反编译代码,首先会输出提示信息Input,在linux系统上运行程序验证一下。
接下来调用Scanf等待用户输入数据
对输入的数据长度进行比较,数据长度必须在0到96之间,且必须为16的整数倍,否则会程序打印提示wrong len并退出校验。
之后调用main_Encrypt对输入的数据进行处理,并将返回结果与off_5A2350处的目标数据进行比较,相同则打印Right, the flag is DASCTF{Input},也就是输入的数据为flag,否则不同的话打印wrong。
所以最终要分析的目标函数是main_Encrypt函数。
main_Encrypt函数当中在一个循环当中调用aes_NewCipher函数调用AES加密算法对输入的数据进行加密,其中加密密钥内存地址为off_5A2370。循环次数取决于a2是16的多少倍,并且在循环尾部会对加密密钥的其中一个字节进行自增处理,自增次数为16,每次增加1,也就是每次循环改变密钥的其中一字节,增加16的大小。
但是当用程序当中给出的密钥off_5A2370对密文off_5A2350进行解密时,无法解密得到正确的flag数据,这个比赛时没考虑到go语言编写的程序的函数执行顺序。
具体可参考以下文章,关于go程序的函数执行顺序。https://www.anquanke.com/post/id/218674
根据文章当中描述的,go当中的package init函数会先于main_main函数执行,也就是main_init要在main_main函数之前执行。
main_init在完成一些go标准库的初始化工作之后,会调用main_init_1函数执行用户定义的初始化工作。
其中main_init_1用户自定义的初始化函数当中会对程序当中保存的密钥off_5A2370数据进行取反操作。写段C代码对数据进行取反解码操作。
解码得到真实的AES加密密钥为1234567890abcdef。
拿到真实加密密钥,再对密文off_5A2350进行解密,拿到flag。
exp如下:
1 |
|
pwn
要求掌握的基础知识技能: IDA使用、栈溢出漏洞、linux伪随机数原理
用到的工具:ida 7.6 sp1及以上、docker pwn环境
首先将附件程序解压放到pwn环境当中,检查下程序的保护措施。
可以看到程序没有stack canary保护,可以直接利用栈溢出漏洞覆盖函数返回地址,不需要泄露canary栈溢出时覆盖正确的值。
程序没有开启PIE保护,内存加载基址恒为0x400000。
通过IDA分析该文件,main函数会调用两个函数sub_401296和sub_4013C7。其中sub_401296会使用随机数种子和当前时间的异或值设定rand函数的种子。
sub_4013C7首先调用rand函数生成随机的secret。
sub_4013C7接下来打印菜单内容,并根据用户输入的不同选项进入不同的代码块,输入2会打印当前生成的随机数数据,并在打印secret后重新生成;输入3会退出while循环,进入随机数secret校验部分;输入1会进入用户输入数据代码部分,这里存在栈溢出漏洞,用户输入的数据可以超过实际分配的栈内存大小,可以覆盖当前函数返回地址,在函数返回时控制函数返回地址执行任意代码。
由于v2变量在栈溢出覆盖函数返回地址的时候会导致被覆盖,而函数在返回前会校验v2的值是否与生成的secret相同,因此这里需要预测secret的值。这里就涉及到了一个叫伪随机数原理,即虽然通过srand函数设置了rand函数的种子,能够确保程序在一次执行过程中生成的随机数不同,但当另一个程序设置相同的种子时,这两个程序生成随机数序列将相同,具体可参考链接:https://we.buptmerak.cn/archives/310
因此这里需要爆破随机数种子,通过调用2泄露目标程序生成的第一个随机数,与我们本地爆破生成的随机数比较,相同则说明种子与目标相同,我们可以用这个种子再次生成随机数,该随机数与目标程序生成的相同。
在程序中的main函数上方一个函数sub_401579,可以看到该函数调用了system(“/bin/sh”),该函数为后门函数,通过栈溢出控制函数的返回地址为0x401579即可实现getshell。
exp编写:
首先看下当前函数的栈数据大小为0x70,其中88字节为buf的数据,这些任意填充,之后8字节为v2的数据,需要填写预测的secret的值,剩下还需要填充0x70 + 8 - 88 - 8长度的数据才会到函数返回地址处,其中0x70 + 8的含义是由于函数入栈会调用push rdp保存栈底基址,因此这里需要加8偏移才会到函数返回地址处。
exp当中关于为什么不直接将返回地址覆盖为后门函数地址,而是先找到一个ret地址,然后再执行,这是由于栈对齐问题,具体可参考以下链接:https://mambainveins.com/2021/08/15/2021/2021-08-15-HWS_PWN/
exp:
1 |
|
加了ret指令的exp执行结果,程序正常运行,没有出现崩溃
不加ret指令的执行结果,程序崩溃,原因就是push rbp指令执行时栈地址没有对齐。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!