频道栏目
首页 > 资讯 > 系统安全 > 正文

Android SO静态库自动化逆向分析

17-10-03        来源:[db:作者]  
收藏   我要投稿

Android SO静态库自动化逆向分析。历久从事Android SO静态库阐发时,时常会做一些重复性较高的事情。比方,SO库中的Java_com_xxx_yyy()等一体系与Java层桥接的办法,逆向它们时,平日必要做以下事情:

IDA Pro载入SO,完成第一次的反编译。

导入jni.h头文件,引入JNINativeInterface与JNIInvokeInterface布局体信息。

设置Java_com_xxx_yyy()范例办法的前两个参数为JNIEnv* env与jobject thiz。

假如有F5插件,则停止一次Force call type。

......。

将这些事情主动化,可以或许大大的进步逆向阐发的事情效力。基于IDA Pro供给的剧本与插件体系,可以或许很便利的完成以上前3项事情。上面,咱们一步步来打造一个SO主动化逆向阐发的对象。

目的细化

在开端完成一个对象前,必要将这些必要办理的成绩停止一次量化阐发。

起首,若何定位必要处置的SO库办法?因为Java_com_xxx_yyy()范例办法与Java层停止桥接,在java层代码中一定会有它的申明。一切的这些办法在Java代码中会有一个native属性,只必要遍历Java层的代码,获得一切的native办法便可。

其次,分歧的办法有分歧的参数范例,署名的分歧,该若何处置?为了让对象完成起来过于繁杂,咱们只处置Java中内置的数据范例,自界说的数据范例同一应用jobject停止处置与表现。

末了,就是将获得到的Java层的一切native办法信息与IDA Pro中的响应的办法停止逐一的对应,并停止办法的主动化范例处置,这就必要用到IDA Pro的剧本功效。

功效完成

明白了以上的3个步调后,上面来着手逐一的完成它。

剖析native办法

为了疾速的剖析native办法,我开端想到的是应用grep敕令(体系为macOS)。起首,应用JD-GUI反编译APK,导出一切的Java源文件,而后在敕令行下履行:

$ grep ' native ' -r ./java_dir -h public native String stringFromJNI();

或许履行以下敕令:

$ grep -Eo '^( |public|private|protected).* native .*;' -r ./java_dir -h public native String stringFromJNI();

不错,都可以或许准确获得到native办法,固然输入的后面会有一个JD-GUI反编译带的空格。

为了让Windows的用户,即使在不装置Mingw或其他的Linux模仿情况的情况下,也可以或许准确的获得到办法,我照样决议应用Python来写一个天生办法署名信息的剧本。就即名叫make_sig.py好了。

Python的便捷,让我可以或许很便利地在敕令行下测试re模块的正则表达式,以下:

$ python

Python 2.7.10 (default, Feb 7 2017, 00:08:15)

[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.34)] on darwin

Type "help", "copyright", "credits" or "license" for more information.

>>> import re

>>> l = " public static native long nativeLoadMaster(String paramString, byte[] paramArrayOfByte1, String[] paramArrayOfString, byte[] paramArrayOfByte2);"

>>> rr = re.match('^( |public|private|protected).* native (.*) (.*)[(](.*)[)];', l)

>>> print "{}".format(rr.group(0))

public static native long nativeLoadMaster(String paramString, byte[] paramArrayOfByte1, String[] paramArrayOfString, byte[] paramArrayOfByte2);

>>> print "{}".format(rr.group(1))

>>> print "{}".format(rr.group(2))

long

>>> print "{}".format(rr.group(3))

nativeLoadMaster

>>> print "{}".format(rr.group(4))

String paramString, byte[] paramArrayOfByte1, String[] paramArrayOfString, byte[] paramArrayOfByte2

OK,正则表达式弄对了!可以或许准确的剖析一条native办法的一切信息:前往值、办法名、署名。我这里不盘算睁开若何编写正则表达式,因为我感到很多人应当会了,假如你对付正则表达式不太熟,倡议你到这个链接疾速的进修一下:https://github.com/zeeshanu/learn-regex 。

上面的代码片段是剖析一个目次下一切的文件,找到native办法并保留到指定的文件中:

def make_sig_file(java_src_dir, sig_file):

f = file(sig_file, 'w+')

for parent, dirnames, filenames in os.walk(java_src_dir):

for filename in filenames:

#print "file: " + os.path.join(parent, filename)

filepath = os.path.join(parent, filename)

with open(filepath) as o:

content = o.read()

for m in re.finditer('( |public|private|protected).* native (.*) (.*)[(](.*)[)];', content):

rr = re.match('package (.*?);.*?class ([^\s]+)', content, re.S)

pkg_name = rr.group(1)

class_name = rr.group(2)

func_name = m.group(3)

print 'func_name:', func_name

print 'pkg_name:', pkg_name

print 'class_name:', class_name

full_func_name = 'Java_' + pkg_name + '_' + class_name + '_' + func_name

full_func_name = full_func_name.replace('.', '_')

#print 'full_func_name:', full_func_name

full_method_sig = m.group(0)

full_method_sig = full_method_sig.replace(func_name, full_func_name).strip()

#print full_method_sig

f.write(full_method_sig + '\n')

f.close()

这段代码不必要太多的说明,os.walk会遍历一个目次中一切文件信息,对付目次中的第一个文件,应用open关上后,挪用re.finditer来婚配native办法,打到就把它写入到sig_file指定的文件名中。

更多的代码参看makesig.py的文件内容,对付很多人,你只必要晓得履行

make_sig.py xxx_out method_sig.txt

就能够或许天生methodsig.txt办法署名文件了。xxx_out为JD-GUI导出的APK的Java源码目次。

Java数据范例处置

好了,到如今曾经取到了一切的native办法信息,如今必要对这些办法的署名停止处置。

一切的native办法支撑的数据范例在jni.h头文件中都有界说,该文件可以或许在Android NDK随意率性体系版本的include目次下找到。在文件的开首就有这么一段:

typedef uint8_t jboolean; /* unsigned 8 bits */

typedef int8_t jbyte; /* signed 8 bits */

typedef uint16_t jchar; /* unsigned 16 bits */

typedef int16_t jshort; /* signed 16 bits */

typedef int32_t jint; /* signed 32 bits */

typedef int64_t jlong; /* signed 64 bits */

typedef float jfloat; /* 32-bit IEEE 754 */

typedef double jdouble; /* 64-bit IEEE 754 */

既然末了的处置代码应用Python来写,咱也不含糊,先弄一个jni_types以下:

jni_types = {

'boolean' : 'jboolean',

'byte' : 'jbyte',

'char' : 'jchar',

'short' : 'jshort',

'int' : 'jint',

'long' : 'jlong',

'float' : 'jfloat',

'double' : 'jdouble',

'string' : 'jstring',

'object' : 'jobject',

'void' : 'void'

}

而后,写一个Java层办法范例转换成JNI范例的办法,代码以下:

def get_jnitype(java_type):

postfix = ''

jtype = java_type.lower()

if jtype.endswith('[]'):

postfix = 'Array'

jtype = jtype[:-2]

tp = ''

if jtype not in jni_types:

tp = 'jobject'

else:

tp = jni_types[jtype] + postfix

return tp

小小的测试一下:

def test_jnitype():

print get_jnitype('int')

print get_jnitype('Int')

print get_jnitype('long')

print get_jnitype('Long')

print get_jnitype('void')

print get_jnitype('String')

print get_jnitype('String[]')

print get_jnitype('boolean')

print get_jnitype('ArrayList')

print get_jnitype('Object[]')

print get_jnitype('byte[]')

print get_jnitype('FileEntry')

输入以下:

jint

jint

jlong

jlong

void

jstring

jstringArray

jboolean

jobject

jobjectArray

jbyteArray

jobject

稳!单个办法的署名剖析没成绩了,那将全部办法的范例转化为JNI接收的范例也没多大成绩,代码以下:

def get_args_type(java_args):

if len(java_args) == 0:

return 'JNIEnv* env, jobject thiz'

jargs = java_args.lower()

args = jargs.split(', ')

#print 'arg count:', len(args)

full_arg = 'JNIEnv* env, jobject thiz, '

i = 1

for java_arg in args:

java_type = java_arg.split(' ')[0]

full_arg += get_jnitype(java_type)

full_arg += ' arg'

full_arg += str(i)

full_arg += ', '

i += 1

return full_arg[:-2]

末了是编写get_jni_sig办法,完成一个Java的native办法署名转成IDA Pro能接收的署名信息。详细看代码,这里就不占篇幅了。

主动化设置办法信息

前两步没成绩,到这里成绩就不大了。上面是写IDAPython代码,来完成一个jni_helper.py剧本对象。

起首是IDA Pro阐发SO时刻,并不会主动的导入JNINativeInterface与JNIInvokeInterface布局体信息。这就必要本身来完成为了。

JNINativeInterface的办法字段忒多,我不盘算本身手写,容易出错还效力低下。我应用IDA Pro的导出功效,点击菜单File->Produce File->DUMP typeinfo to IDC file...,而后一个idc文件,而后复制IDC中的内容,简略改动就完成为了add_jni_struct()办法,代码以下:

def add_jni_struct():

if BADADDR == GetStrucIdByName("JNINativeInterface"):

AddStrucEx(-1, "JNINativeInterface", 0)

id = GetStrucIdByName("JNINativeInterface")

AddStrucMember(id, "reserved0", 0, 0x25500400, 0XFFFFFFFF, 4, 0XFFFFFFFF, 0, 0x000002)

AddStrucMember(id, "reserved1", 0X4, 0x25500400, 0XFFFFFFFF, 4, 0XFFFFFFFF, 0, 0x000002)

......

AddStrucMember(id, "GetDirectBufferAddress", 0X398, 0x25500400, 0XFFFFFFFF, 4, 0XFFFFFFFF, 0, 0x000002)

AddStrucMember(id, "GetDirectBufferCapacity", 0X39C, 0x25500400, 0XFFFFFFFF, 4, 0XFFFFFFFF, 0, 0x000002)

#SetStrucAlign(id, 2)

idc.Eval('SetStrucAlign({}, 2);'.format(id))

if BADADDR == GetStrucIdByName("JNIInvokeInterface"):

AddStrucEx(-1, "JNIInvokeInterface", 0)

id = GetStrucIdByName("JNIInvokeInterface")

AddStrucMember(id, "reserved0", 0, 0x25500400, 0XFFFFFFFF, 4, 0XFFFFFFFF, 0, 0x000002)

AddStrucMember(id, "reserved1", 0X4, 0x25500400, 0XFFFFFFFF, 4, 0XFFFFFFFF, 0, 0x000002)

AddStrucMember(id, "reserved2", 0X8, 0x25500400, 0XFFFFFFFF, 4, 0XFFFFFFFF, 0, 0x000002)

AddStrucMember(id, "DestroyJavaVM", 0XC, 0x25500400, 0XFFFFFFFF, 4, 0XFFFFFFFF, 0, 0x000002)

AddStrucMember(id, "AttachCurrentThread", 0X10, 0x25500400, 0XFFFFFFFF, 4, 0XFFFFFFFF, 0, 0x000002)

AddStrucMember(id, "DetachCurrentThread", 0X14, 0x25500400, 0XFFFFFFFF, 4, 0XFFFFFFFF, 0, 0x000002)

AddStrucMember(id, "GetEnv", 0X18, 0x25500400, 0XFFFFFFFF, 4, 0XFFFFFFFF, 0, 0x000002)

AddStrucMember(id, "AttachCurrentThreadAsDaemon", 0X1C, 0x25500400, 0XFFFFFFFF, 4, 0XFFFFFFFF, 0, 0x000002)

#SetStrucAlign(id, 2)

idc.Eval('SetStrucAlign({}, 2);'.format(id))

# idaapi.run_statements('auto id; id = GetStrucIdByName("JNIInvokeInterface"); SetStrucAlign(id, 2);')

比拟风趣的是,在IDC中有这么一句:

SetStrucAlign(id, 2)

这个SetStrucAlign()办法在IDAPython中并无,要想挪用它,可以或许应用以下办法:

idc.Eval('SetStrucAlign({}, 2);'.format(id))

导入完成后,下一步的事情是获得SO中一切的Java_com_xxx_yyy()范例的办法信息,这个好办,代码以下:

addr = get_code_seg()

symbols = []

for funcea in Functions(SegStart(addr)):

functionName = GetFunctionName(funcea)

symbols.append((functionName, funcea))

symbols如今存放了一切的办法,只必要断定能否以“Java_”开首就能够辨别native办法了。

接着是读取后面天生的办法署名文件,读取它的一切办法署名信息,这里我应用以下办法:

sig_file = AskFile(0, '*.*', 'open sig file')

AskFile()办法会弹出一个文件抉择框来让用户抉择天生的文件,我感到这类交互比间接写死文件门路要优雅,固然这里会让你介入出去,能够会使你烦燥。

咱们传入获得到的第一条Java办法署名给上一步的get_jni_sig()办法,来天生对应的JNI办法署名。末了,挪用SetType()来设置它的办法署名信息。

相关TAG标签
上一篇:硬盘监控和分析工具Smartctl介绍
下一篇:医疗网站安全之如何防御幕后黑客的频繁攻击
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站