HITB-CTF 2018 有2个安卓题《multicheck》、《kivy_simple》,抽空稍微做了一下,挺简单的。
文件链接:https://github.com/LeadroyaL/attachment_repo/tree/master/hitbctf_2018
一、multi-check 上来先加载了个 so,init_array里有内容,看起来是在读一些数据,也可能把write函数给 hook 掉了,对将来文件名为claz.dex的文件做一些操作,似乎是把全部数据 xor 了233。
之后在 java 层,把assets 里放的claz.dex 拿出来,放到私有目录下,这时候会触发之前写的 hook,将文件内容改变。 对其进行动态 dex 加载,加载完成后删除,并且调用里面的com.a.Check.check(String) 方法。
也就是说,如果你手速足够快,是可以在私有目录下把这个文件复制出来的,就免得手动去解密了,还好手动也不复杂。
拿到解密后的 dex 文件,看一下,这次真的没有什么骚操作了,都是简单运算,送分题,解密代码找不到了。
二、kivy_simple kivy 是跨平台用 python 写 GUI 的东西,同样安卓也有,所以这个题目的文件应该有大量冗余了,找到核心内容即可。
Java 层,太乱了不知道在干嘛,主要是资源文件全找不到,就猜是mp3里有东西,搜了一下发现果然是藏了东西的,tar zxvf解压一下,一堆文件,记得 chmod 一下,不然有文件没权限查看。
再看看shared_library,怕是在里面藏东西,绕了一圈,反正啥也看不懂。。。修改了解释器我估计也看不出来。
在解压出来的文件里,放了个main.pyo,使用 uncompyle反汇编一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 from kivy.uix.popup import Popupfrom kivy.app import Appfrom kivy.uix.label import Labelfrom kivy.uix.textinput import TextInputfrom kivy.uix.button import Buttonfrom kivy.uix.boxlayout import BoxLayoutimport binasciiimport marshalimport zlibclass LoginScreen (BoxLayout ): def __init__ (self, **kwargs ): super (LoginScreen, self).__init__(**kwargs) self.orientation = 'vertical' self.add_widget(Label(text='FLAG' )) self.flag = TextInput(hint_text='FLAG HERE' , multiline=False ) self.add_widget(self.flag) self.hello = Button(text='CHECK' ) self.hello.bind(on_press=self.auth) self.add_widget(self.hello) def check (self ): if self.flag.text == 'HITB{this_is_not_flag}' : return True return False def auth (self, instance ): if self.check(): s = 'Congratulations you got the flag' else : s = 'Wrong answer' popup = Popup(title='result' , content=Label(text=s), auto_dismiss=True ) popup.open () screen = LoginScreen() b64 = 'eJzF1MtOE2EUB/DzTculUKAUKJSr3OqIV0TBGEOMRqIuatJhowsndTrVA+MlnYEYhZXEhQuXLlz4CC58BBc+ggsfwYWPYDznhHN8BJr5Tv7fby6Z8/VrIzj+eDRu0kirVFoARwCPAGI6HOx4EBI6CHy+LHLH1/O4zfd8onQAsEOHg0MHmQcHDt45vmc3B50FyHIQELU8qLZyYutmebIusftm3WQ9Yo/NeskKYh2zPrJ+sfdmRbIBsc9mg2RDYl/NSmTDYt/NymQjYj/NRsnGxH6bVcjGxf6aTZBVxcpObdL6rZlNkU2LXTebsT7qZrP2fk/M5shOie2bzdvzPpgtkC2KfTFbIlsW+2ZWIzst9sPMJzsj9stsheys2B+zc2TnxTxP7YL1UTG7aLZidolsVWzT7LL11jBbI7si1ja7SrYu9sZsw+yjWJaHgHZx4F+j/VnHOao4TCXjvbuBQxqXsV9jgDmNt7CiMURP4zZOaXyA3RrncVTjEpY0djCv8S2Oa3yF/OtC0PldLPN8hkuf4ioO8nxA5zWc1LiITuM97NG4hbMaD3FE4z4W+TEFLhOKD7GL59M6r+OYxjXsperz+YzfvZ00n0rI4tdZxkuTxC8yPr3VTNJYTm139mL5S5BZGidteVTqc4dSMil8V/Qsjnb52vSIzRVdGfKu5E5seHWfu2rw3sj460yjTkwt8oqFYZQ00zQM/3cipSErzQt14/nL1l4Sb0pHXAp3/gENPMQt' eval (marshal.loads(zlib.decompress(binascii.a2b_base64(b64))))class MyApp (App ): def build (self ): return screen app = MyApp() app.run() +++ okay decompyling main.pyo
看起来是制定了那段b64,我对python 不熟,查了一下就是反序列化一个code object,然后 eval 一下。 经过很多很多次测试,终于让这段 python 运行起来了,发现这段代码似乎覆盖了原来的变量screen。
正确使用方式是使用 python 自带的 dis 库
1 2 import dis dis.dis(code_object)
发现确实是这样,然后 dump 出真正的 screen,再对 screen 的 check 方法进行 dis.dis,可以拿到 python 的 bytecode。 之后对这段 bytecode 进行阅读即可,发现是逐字节比较,写段正则什么的处理一下就好。 后来看别人的 writeup,说这段 code_object 其实也可以被反汇编,就不用读汇编了,赞。
在 ap 大佬的帮助下, 成功创建了 pyc 文件,参考这两个链接 https://stackoverflow.com/questions/8627835/generate-pyc-from-python-ast https://www.jianshu.com/p/152820587935 按照顺序写入4byte 的 magic,4byte 的创建时间,和整个code_object即可。
1 2 3 fd.write(py_compile.MAGIC) fd.write('0000') fd.write(zlib.decompress(binascii.a2b_base64(b64)))
然后就可以使用uncompyle来反汇编check.pyc了~
最后 flag 是
1 2 // HITB{SEe!N9_IsN'T_bELIEV1Ng} // HITB{1!F3_1S_&H%r7_v$3_pY7#ON!}