hitbctf 2018 android writeup

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
# 2018.06.05 16:58:01 CST
#Embedded file name: /home/ubuntu/kivyproject/.buildozer/android/app/main.py
from kivy.uix.popup import Popup
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
import binascii
import marshal
import zlib

class 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
# decompiled 1 files: 1 okay, 0 failed, 0 verify failed
# 2018.06.05 16:58:01 CST

看起来是制定了那段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!}