首页
社区
课程
招聘
[原创]CTF2018第三题分析(qwertyaa)
发表于: 2018-6-20 19:18 2659

[原创]CTF2018第三题分析(qwertyaa)

2018-6-20 19:18
2659

前面都挺顺利的,最后一步调了一个下午...QAQ

首先main->welcome->test函数内有反调试,会直接通过syscall调用sys_ptrace,一旦发现被调试了程序就会自杀。我每次调试的时候直接用Ctrl+N跳过welcome函数。(为什么pwn里会有这种东西啊)

然后是输入一个长度为6的串(可以试着多输入几个字符,发现多余的会“吐出来”),用来解密连续6个SMC。乍一看密钥好像根本没有提示,很难猜到底是什么字符去异或他们可以正确解密。
注意到异或解密相当于对字符作了一个置换,破解这种最为原始的加密方法的关键是观察不同字符出现的频率。而我们知道绝大多数指令集中出现次数最多字符无疑是0x0,换句话说,密文中出现次数最多的字符就是解开这一段密文的密钥。
同时,需要注意到输入的密码与前一个密钥异或的结果才是正确的密码。
这样我们得到了密码evXnaK(已经找到一个flag想收摊的感觉)。

接下来的代码就非常清晰了,先读入一个字符串,将其作为printf的Format参数传入,然后读入一个长度限制过大的串。
这个程序中的Stack是有保护的,栈的结构大概如下所示:

局部变量
保护码
返回地址
参数

这里保护码是一个随机生成的内容,一旦某个局部变量读入越界,首先被覆盖的就是保护码,如果函数返回时这个信息和一开始随机生成的内容对不上,说明栈已经溢出,程序就会自动结束。
但这没有关系,因为这里printf也有漏洞,栈的结构可进一步表示如下:

printf的局部变量
printf的保护码
printf的返回地址
printf的参数
main的局部变量
main的保护码
main的返回地址
main的参数

printf是不会也没办法(除非增加参数)检查传入参数是真的参数,还是越界访问到的前一个函数的局部变量(甚至是更前面的内容)。
需要注意的是这里Format字符串给我们的长度有限,用%p去取似乎不太够用,这时我们可以用%n$p(其中n是一个数字,表示把这个内容用 第n个参数用%p输出的内容 替换)。
然后我们需要一些信息,比如说栈的位置、libc的位置,这些我们都可以在栈中找到,然后用printf输出。

然后我们用Ctrl+S一查,Stack段似乎不能执行啊。
我一开始的想法是:

/bin/sh
填充
main的保护码
参数(指向/bin/sh)
system函数的地址(原main的返回地址)

醒醒,这是x86_64下的pwn程序啊,参数是用__fastcall而不是__stdcall的,怎么办?
没有关系,这几句话在那么大的libc.so里难道找不出来?
我用IDA的File->Produce File...->Create ASM file...把libc.so(具体的来说应该是libc-2.23.so,不过更高版本除了代码位置不同,里面的代码大同小异)的代码搞出来。
几经搜索,我们找到了一处极其理想的代码:

直接吧参数从栈里拎出来调用有木有,就用它了。
这样的我们要填充的内容可以很快构造出来

/bin/sh
填充
main的保护码
libc-2.23.so模块地址+0x27BF1(原main的返回地址)
填充
参数(指向/bin/sh,在栈中对应位置(rsp+20h))
填充
调用函数(指向system函数)

敲完上述内容的python代码,一刷榜单还没人岂不是美滋滋,然后我就百思不得其解地调试了一整个下午...无一例外报这个错误RuntimeError: Unknown child exit status!;把system改成printf就又可以了,但我要一个Get不了Shell的printf何用!!!然后我一直以为system严苛的检查了栈的前后内容...

最后,我突然想到我还没意识到这道题是个64位下的题目的时候,虽然传入的参数明显不对,但system函数还是可以调用的,我终于意识到问题在于不能把/bin/sh放到栈里去。
那怎么办呢?
/bin/sh这个字符串有点难找,但sh就是另一回事了,打开WinHex在libc-2.23.so里一顿搜索,找到了位于0x11e70的字符串sh
最后把对应的参数改掉。


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2018-6-21 14:48 被qwertyaa编辑 ,原因: 补充
收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//