前言
好久没有写文章了,先废话几句。
前段时间在忙毕设和出题,虽然偶尔也挖挖洞,但毕竟是0day也不好发出来。
于是blog就长草了,想了想这样还是不太好。
当然也有一些其他原因,总之,各种拖着,也没有学习,没有写文章。
恍恍惚惚过了半年也想明白了很多事,开心还是要看自己。
so,just do it.
感谢@overload,@spine,@giglf等师傅们的耐心指导。
正文
pwnhub的一道apk逆向题,apk链接在此。
直接丢进jeb,发现无法反编译。于是用apktools进行解压,发现没有classes.dex文件。
当时以为是动态脱壳,查了一些工具如DexHunter都没有效果。
AndroidManifest.xml内容如下:
可以看到入口为android.app.NativeActivity,说明是ndk程序,但是下面又有MainActivity,可能是在运行过程中动态生成了dex。
反汇编一下libnative.so文件,在左边找到入口函数android_main
大致逻辑如下
v1 = a1;
v71 = _stack_chk_guard;
destLen = 0x100000;
dest = (Bytef *)malloc(0x100000u);
v2 = (const Bytef *)malloc((size_t)off_48BF0);
_aeabi_memcpy((int)v3, (int)aQxe, (int)v2);
首先声明了一段大小的空间,然后将.data7004偏移开始,off_48BF0指向的地址的大小的内容复制到里面。
中间是一波看不懂的操作,大致意思可能是通过api来获取手机震动次数???
接下来是重点
v13 = shaketimes;
if ( (unsigned int)(shaketimes - 1) <= 88 )
{
_R0 = 0x66666667;
v13 = shaketimes;
__asm { SMMUL.W R0, R4, R0 }
v19 = ((signed int)_R0 >> 2) + (_R0 >> 31);
if ( shaketimes - 10 * v19 == 9 )
{
_R1 = 0x66666667;
_R0 = off_48BF0;
__asm { SMMUL.W R1, R0, R1 }
v23 = ((signed int)_R1 >> 2) + (_R1 >> 31);
v24 = (v19 + 1) * v23;
v25 = v19 * v23;
if ( v25 < v24 )
{
v26 = &v3[v25];
do
{
--v23;
*v26++ ^= shaketimes;
}
while ( v23 );
}
if ( shaketimes == 89 )
{
while ( v24 < (signed int)_R0 )
v3[v24++] ^= 0x59u;
}
v13 = shaketimes + 1;
}
}
...
if ( shaketimes == 100 )
{
if ( (signed int)(time(0) - v31) > 9 )
{
_android_log_print(4, "FindMyDex", "OH~ You are too slow. Please try again");
_aeabi_memcpy((int)v3, (int)aQxe, (int)off_48BF0);
v13 = 0;
}
else
{
if ( uncompress(dest, &destLen, v3, (uLong)off_48BF0) )
_android_log_print(5, "FindMyDex", "Dangerous operation detected.");
v27 = open((const char *)&filename, 577, 511);
if ( !v27 )
_android_log_print(5, "FindMyDex", "Something wrong with the permission.");
write(v27, dest, destLen);
close(v27);
这里一开始就难到我了...
_R0 = 0x66666667;
v13 = shaketimes;
__asm { SMMUL.W R0, R4, R0 }
v19 = ((signed int)_R0 >> 2) + (_R0 >> 31);
请教了好基友后才知道这4行的意思是v19=shaketimes/10 (当时我的内心一脸蒙蔽 = = )
于是自己测试一波
#include <stdio.h>
#include <stdint.h>
int main(){
uint64_t s = 0x66666667;
int32_t v2,shakecount;
shakecount=100;
s = (s*shakecount) >> 32;
v2 = ((signed int)s>>2)+((unsigned int)s>>31);
printf("%d\n",v2);
}
结果还真的是除10~
后面就是常规操作了。于是可以写一个脚本dump出dex。
这里又有坑。
一开始是直接打开了libnative.so文件,然后读取data[0x7004:0x7004+0x41BE8]
结果发现libnative.so文件都没有0x7001+0x41BE8这么大。 现在还是不懂..有没有大佬能指导下
后来基友说要用ida python来dump
于是
Python>t=[]
Python>for i in range(0x7004,0x7004+0x41BE8):
Python> t+=[chr(Byte(i))]
Python>
Python>open("data.bin","wb").write("".join(t))
dump出data.bin,然后用python脚本转换出dex。
import zlib
data = open('data.bin','rb').read()
data = list(data)
print '%x'%len(data)
for shakecount in xrange(90):
v19 = shakecount/10
if shakecount%10==9:
v23 = 0x41be8/10
v24 = (v19+1)*v23
v25 = v19*v23
if (v25<v24):
for i in range(v25,v24):
data[i]=chr(shakecount^ord("".join(data[i])))
if shakecount==89:
for i in xrange(v24,0x41be8):
data[i]=chr(89^ord("".join(data[i])))
data2=zlib.decompress("".join(data))
open('3.dex','wb').write(data2)
拿着3.dex就可以在jeb里面反汇编了。
protected void onCreate(Bundle arg4) {
super.onCreate(arg4);
this.setContentView(2130968603);
this.findViewById(2131427415).setOnClickListener(new View$OnClickListener() {
public void onClick(View arg5) {
if(Arrays.equals(MainActivity.a(this.a.getText().toString(), this.c.getString(2131099683)),
MainActivity.i())) {
Toast.makeText(this.b, this.c.getString(2131099685), 1).show();
}
else {
Toast.makeText(this.b, this.c.getString(2131099682), 1).show();
}
}
});
}
主要是对比
MainActivity.a(this.a.getText().toString(), this.c.getString(2131099683))
和MainActivity.i()
MainActivity.i()的是硬编码的,内容为:
MainActivity.m = new byte[]{3, -91, 61, -29, -87, -21, 101, -57, 27, -19, -27, -10, -61, 15, -85, 12, -32, -102, 106, 92, -39, 100, -122, 112, 18, -100, -55, 44, 60, -118, -97, 127, 76, 113, -35, 68, -17, -33, -93, -62, -57, -84, 36, -96, 97, -15, 20, 69};
写个python脚本转换一下:
s = [3, -91, 61, -29, -87, -21, 101, -57, 27, -19, -27, -10, -61, 15, -85, 12, -32, -102,106, 92, -39, 100, -122, 112, 18, -100, -55, 44, 60, -118, -97, 127, 76, 113, -35, 68, -17, -33, -93, -62, -57, -84, 36, -96, 97, -15, 20, 69]
ss=[]
for i in s :
if i>0:
ss+=[chr(i)]
else:
ss+=[chr(256+i)]
result = "".join(ss)
print result
this.c.getString(2131099683)的内容是这么找的。首先计算2131099683的16进制 0x7f060023
然后在res/values/public.xml中找到
最后在res/values/strings.xml找到
this.a.getText().toString()是用户输入的值。
看一下MainActivity.a():
static byte[] a(String arg1, String arg2) {
return MainActivity.b(arg1, arg2);
}
private static byte[] b(String arg7, String arg8) {
byte[] v0_1;
try {
BufferedInputStream v2 = new BufferedInputStream(new ByteArrayInputStream(arg7.getBytes()));
byte[] v1 = new byte[16];
ArrayList v3 = new ArrayList();
Object v4 = a.a(arg8.getBytes());
while(v2.read(v1, 0, 16) != -1) {
v3.add(a.a(v1, 0, v4));
v1 = new byte[16];
}
ByteBuffer v2_1 = ByteBuffer.allocate(v3.size() * 16);
Object[] v3_1 = v3.toArray();
int v4_1 = v3_1.length;
int v1_1;
for(v1_1 = 0; v1_1 < v4_1; ++v1_1) {
v2_1.put(v3_1[v1_1]);
}
v0_1 = v2_1.array();
}
catch(Exception v0) {
v0_1 = new byte[1];
}
return v0_1;
}
后面就看不懂啦,应该是个分组的加密。(大佬们能看出是twofish加密,是一个分组对称的加密算法)
最后用python脚本
from twofish import Twofish
fish = Twofish(b'I have a male fish and a female ')
print fish.decrypt(result[0:16])
可以跑出flag
dump dex还有另外几种方法
1是giglf大佬的方法。
用安卓的调试工具ddms连接手机,然后adb进手机dump出odex
然后用baksmali把odex解出来smali
java ‐jar baksmali.jar de ‐‐classpath‐dir <framework‐dir> classes.dex
再用smali
java ‐jar smali.jar ass out
生成out.dex
自己动手尝试
发现进不了data目录,可能是手机没有root的原因
2是LeadroyaL师傅用ida在remove下断点,然后拖出真正的dex
遗留问题
dump文件data段的时候为什么会缺失。
dex中在加密过程的具体处理
如何用ida下断点
有空root手机后尝试一下dump odex文件
从自己提取data段释放出dex的so是如何编写的
bin爷好!
时间: 2017-06-19 at 21:49 回复