Xman冬令营选拔赛(返老还童&孔雀翎)
本帖最后由 whklhh 于 2018-1-8 00:13 编辑搞完领航杯马不停蹄回来搞移动逆向,感谢主办方延长了时间~
逆向类的题目很有意思(°∀°)ノ
脑洞/杂项类的就……(跪
# 返老还童
反编译查看java代码,发现调用了一个WebView,加载了包内的check_flag.html
解包拿出来,查看发现存在混淆的js代码
用脚本反混淆并整理变量,发现最后要有3个结构类似的函数,区别只在于核心的比较地方而已
发现onClick方法中有调用js,查看这个函数名发现是中间那个
```
MainActivity.b.loadUrl("javascript:o0O0oo0o0o0o0oO()");
```
于是针对它分析
```
function o0O0oo0o0o0o0oO() {
const _0x5de4x3 = navigator['userAgent']['split'](';')['map'](_0x5de4x4 => Number(_0x5de4x4));
var _0x5de4x5 = function (x) {
return function (y) {
var gcm = function (a, b) {
return 0 === b ? a : gcm(b, a % b);
};
return x * x - y * y == 56 * (x + y) && 10000 < x * x + x * y + y * y && 10000 > x * x - x * y - y * y && 7 == gcm(x, y);
};
};
document['getElementById']('result')['innerHTML'] = _0x5de4x5(_0x5de4x3 - 2])(_0x5de4x3 - 1]) ? 'flag is XCTF{your input}' : 'try again';
}
```
这里的gcm函数就是辗转相除法求两数的最大公因子
发现取了两个数,判断它们的返回值是否为1
判断方法就在return那里,由两个方程,两个不等式组成
先记录下来
回头继续分析dex的onClick,在调用js上面还调用了a.a,跟过去看
![这里写图片描述](http://img.blog.csdn.net/20180107200955137?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2hrbGhoaGg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
这里取了输入内容,以`-`分隔,一共4组,每组4个字符
下面对字符范围进行了约束,懒得看了233
关键地方在这
![这里写图片描述](http://img.blog.csdn.net/20180107201127718?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2hrbGhoaGg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
一大坨方程限定4个数字串~
最后a和b就是送入js的两个数
解方程,我用Z3
~~Z3求解器,还您一身轻松(<ゝω·)~~
首先反求a和b
```
from z3 import *
def gcm(a, b):
if a >= b:
if a % b == 0:
return b
else:
return gcm(b, a - b)
else:
return gcm(b, a)
x = Int('x')
y = Int('y')
s = Solver()
s.add(x>0, y>0, x<256, y<256)
s.add(x*x - y*y == 56*(x+y), 10000<x*x+x*y+y*y, 10000>x*x-x*y-y*y)
result = []
while(s.check()==sat):
m = s.model()
x1 = m.as_long()
y1 = m.as_long()
if(gcm(x1, y1)== 7):
result.append((x1, y1))
s.add(x!=x1)
print(result)
```
因为最大公因子这个限定不好加给z3,所以我在求解出结果以后再检查最大公因子,满足则记录,不满足则跳过进行下一个
得到若干解
>result = [(91, 35), (105, 49), (119, 63), (133, 77), (147, 91), (161, 105), (175, 119), (189, 133), (203, 147), (217, 161), (231, 175), (245, 189)]
再一个一个把方程拖下来输入进行求解
```
for i in result:
x2 = i ^ 55
y2 = i + 4^113
a =
b =
c =
d =
s = Solver()
# 限定可见字符
for i in a,b,c,d:
for j in i:
s.add(j>32)
# 一大坨方程
s.add(
a == d - 3,
a == d + 1,
d % 2 == 0,
d == a + 8,
d == a - 2,
d == (a ^ 18),
a * 2 == a - 8,
d == a,
d == x2,
12 + b == b,
c * 2 == b-11,
b == y2,
b + c == 187,
b + c == 210,
b ^ b == 47,
b ^ b == 15,
c ^ b== 5,
)
# 求解
if(s.check()==sat):
m = s.model()
for i ina,b,c,d:
for j in range(4):
print(chr(m].as_long()), end='')
print('-', end='')
print()
else:
print(unsat)
```
结果最后只有第一个result出了可见字符解╮(╯_╰)╭
>d2lu-bmVy-Y7hp-bgtl-
提交完成
# 孔雀翎
回来的时候已经有提示了,让试试反射和Xposed……
这俩我都不会呀~不过之前在52上看到过教程,应该可以试试吧(结果最后也没用上因为我并不会(望天
先反编译查看代码,发现在onClick中取得输入后送入一个反射来的方法进行运算,最后与另一个常量比较
![这里写图片描述](http://img.blog.csdn.net/20180107191024812?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2hrbGhoaGg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
这个v3就是反射来的方法,用invoke的方式调用,v0即输入就是参数
d.c通过对常数参数做一堆运算返回一个方法
那么关键就是这个方法是啥了
首先试了一下JEB2动态调试,然而单步跟到invoke以后就提示没法继续跟进去了
只好继续查找
浪费了俩小时也没找到什么能动态调试/dump方法代码的东西
最后无奈的试试IDA动态调试,没想到还真跟进去了
一下就看到了这里v3反射的是a.a.c.d方法
比对了一下smali确认无误后进行静态分析
![这里写图片描述](http://img.blog.csdn.net/20180107191412715?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2hrbGhoaGg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
……
(╯‵□′)╯︵┻━┻
这个方法里面又进行了5次反射,还好没有别的东西
不会Xp有啥办法尼,硬着头皮怼吧
一一跟进去并记录如下
```
a.a.c.d
->a.a.c.b(byte[], byte[])
->a.a.c.b(byte[])
->a.a.c.b(string)
->a.a.c.a(String)
->a.a.b.b(byte[])
```
还好这5个方法里没有更复杂的东西╮(╯_╰)╭连库函数都要反射的话我就没辙了
用Xposed应该可以Hook所有方法,把调用过的每个方法名都记录下来。思路是有,奈何Xposed不会写2333等跟着dalao们学习一个
于是挨个静态分析,结合JEB2的动态调试查看变量
PS: IDA能跟到反射方法里去,但是本地变量监视器实现的太差,除了字符串啥都看不到
而JEB2虽然能看到几乎所有类型的本地变量,但是跟不进反射方法(不过可以在反射的方法内下断来调试)
##### a.a.c.b(byte[] onClick, byte[]v6)
AES加密,key为"imaeskeyimaeskey"
刚开始看到代码只是显示secretKey,还不能确定是什么加密方法,结果看到这个密钥就不用想了233
padding没找到是什么,测试的时候正向输了一个十六个字节的字符串来测试
最后解密出来看到padding是\r
##### a.a.c.b(byte[])
转大写HEX
##### a.a.c.b(String)
将HEX的每个值通过一个字典转换
动态查看字典后发现就是0-16的4位bin显示
如E->1110
##### a.a.c.a(String)
压缩,将连续的若干1或0压缩成数目
如11100011010->332111
注:开头若为0则舍去
##### a.a.b.b(byte[])
改了Table的b64,Table通过构造方法生成,动态dump可得
于是按照流程写出脚本:
```
from Cryptodome.Cipher import AES
from base64 import *
key = b"imaeskeyimaeskey"
aes = AES.new(key, AES.MODE_ECB)
table = ['!', '(', '&', '*', '~', ';', ')', ':', '+', 'I', 'J', 'K', '%', 'M', 'F', 'D', '_', 'W', 'H', 'a', '@', 'G', 'Q', 'R', 'S', 'T', 'N', 'O', '$', 'b', 'c', 'd', '^', 'f', 'g', 'h', 'P', 'L', 'e', 'i', 'j', 'k', 'l', 'm', 'n', 'A', 'B', 'C', '#', 'E', 'o', 'p', 'X', 'V', 'U', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', '?']
# Reverse
s = "!_~&!#+(!_@&!_$*!_+(!_~(!#_(!^~)!_+&!_~((!~(!^~*!^~~!^~~!_~*!^+(!^@(!_+(!^^(!_~*(!~(!_~(!_%*!_~&!_~&!^S(!_~((#~((!~&!^%&!_%&!_+(!^~&!#+((_~(!_~(!_+(!_+;!_+(!^~(!_@&!^~:!^~(!_~(!^~(!_~*!^~;!^+(!_~&!^~(!_~&!^@&!^~(!_~(!^_(!#%(!_~&!^~&!^_&!_+&!__&!_+(!_~(!__~(^~)!_~?"
# b64解密
flag1 = b""
for i in range(len(s)//4):
p = 0
q = 0
for j in s:
q = q << 6
q |= table.index(j)
for j in range(3):
p = p << 8
p |= q&0xf
q = q >> 8
flag1 += p.to_bytes(3, 'little')
print(flag1)
# 解压缩
flag2 = ""
f = 1
for i in flag1:
while(i):
flag2 += str(f)
i -= 1
f ^= 1
print(flag2)
# 将BIN转回HEX
flag3 = ""
for i in range(len(flag2)//4):
flag3 += hex(int(flag2, 2))
print(flag3)
# 解码
flag4 = bytes.fromhex(flag3)
# 解密
flag = aes.decrypt(flag4)
print(flag)
``` 不错,做出孔雀翎的选手都应该晋级了吧。
这次的题目是我们部门的三位小伙伴出的,周末大家盯了两天,看到有人做出来时,甚感欣慰。
纯粹移动的CTF比赛,出题十分纠结,逆向分析不可避免的占据主要地位。为了避免题目的单调,我们选择了DEX、Native、JavaScript各一道,然后凑了两道脑洞题。原计划是出一道Mono的,考虑到难度和时间,只能作罢。
手势密码比较容易,跟着画一遍就出来了,当然,对iOS用户应该不太友好。
二维码确实超出了我们的预期,出题的思路是图片中的每种元素利用一遍,即二维码、文字、表情红字、附加数据,加上AES密钥长度固定(128、192、256bit),就可以过掉第一层AES,剩下的套路一样。最后我们一致认为,虽然QR那两个字是鼠标画的,但表情包大多数风格如此,这个线索不太好发现。
JavaScript不用多说。
孔雀翎在设计的时候,如何迫使参赛选手必须了解反射,并且必须开发诸如Xposed或者Frida的插件才能完成,是题目想要达到的一个目的。因此,基本算法完成后,我们上了ZKM,因为最近在破解BurpSuite的时候遇到过,看着蛋疼,如果写程序处理,但其实没啥难度。
万箭穿心是有点蛋疼,没有任何加料和猥琐技巧,只是挑选了大家不熟悉的浮点运算完成,跟上出题者的思路需要一定的时间,如果比赛的时间能够再延长一些,我相信大家都能够做出来。
最后,感谢所有参赛的同学和队伍,陪我们折腾了一个周末。 本帖最后由 whklhh 于 2018-1-9 01:54 编辑
TheCjw 发表于 2018-1-8 23:31
不错,做出孔雀翎的选手都应该晋级了吧。
这次的题目是我们部门的三位小伙伴出的,周末大家盯了两天,看 ...
APK结合js的题目我是头一次见,当然ctf里移动安全的题目相对而言本来也不算太多...看起来业界经常结合js开发?学到了新知识2333
孔雀翎我大概猜到应该要用Xposed来Hook方法记录方法名了,但是常见的教程都是注入、改参数啥的,我本身很少用java就比较为难 希望冬令营讲解的时候能多教教Xp~
话说ZKM貌似就是添了个构造方法来加密字符串吗?对方法的混淆好像没怎么见..去看了一下算法发现就是简单的循环异或,比赛的时候太困了不想动脑直接动态调出来的(望天
手势密码完全没get到,我倒是把它转成坐标画了几条线出来,然后没反应过来是1-9的键盘OTZ那个图形没啥特点就放弃了。不过幸好没想到,不然我肯定懒得去怼孔雀翎 就错过好多东西啦
万箭穿心完全懵逼_(:з」∠)_0xA0B5ED8D那几个常数查了一下是clinpack来的吗?浮点数运算是sub_25824等IDA没反编译出来的函数么?ARM汇编很不熟悉 惭愧
又去动态跟了一下,25824跳到后一个函数TEQ再MOVEQ以后就报BUS ERROR了……是反调试吗_(:з」∠)_
总之感谢梆梆安全的大佬们认真用心出的题目~我会一一钻研学习的~{:301_975:} {:1_921:}第一时间支持楼主的逆向文章 chenjingyes 发表于 2018-1-8 00:04
第一时间支持楼主的逆向文章
{:301_1004:}谢谢谢谢 萌新感觉很惭愧 不错不错,出类拔萃:lol 谢谢大佬的教程 谢谢分享 感谢分享,楼主辛苦了 路过,支持下楼主 楼主江苏的吗?领航杯。 ZjyCjy 发表于 2018-1-8 22:03
楼主江苏的吗?领航杯。
是啊是啊。
页:
[1]
2