-
-
[原创]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
。
最后把对应的参数改掉。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!