文章目录
- 前言
- Pull APK
- 根据包名列表
- 根据手机路径
- 逆向APK
- 自动化反编译
- findstr检索…
- 总结
前言
日常工作过程中,经常会遇到发现新的攻击模式的情况下,需要全量排查手机上所有 APP 的代码是否存在该类代码缺陷。对于复杂的攻击模式而言,往往需要动用强大静态分析的工具(如 soot 框架或 Codeql),但对于简单的攻击模式而言(比如仅仅是定位某些关键词、关键函数),则通过快速检索即可解决。
但是问题来了,如何在没有 APP 源码的情况下,快速获得所有目标 APP 的代码并进行快速检索?答案当然是借助 jadx 工具对 APK 进行自动化反编译。这涉及两个需要解决的问题:
- 以 shell 权限(无需 root)批量从目标手机拉取 APK 文件;
- 将拉取到的多个 APK 文件批量、自动化地进行反编译并获得 java 文件;
本文将记录下如何通过编写自定义的 Python 脚本来解决上述场景遇到的测试需求和实现难点。
Pull APK
先来解决第一个问题:批量从目标手机拉取 APK 文件。
根据包名列表
以下代码是根据 package.txt 文件列出的包名,自动拉取对应的 apk 文件到本地指定路径:
def pullAPK_by_PackageList():
"""
根据指定的包名列表,批量拉取手机中的APK文件到本地路径
:return: null
"""
pkgList = []
print(Fore.BLUE + "[*]Start pull apk…")
with open('packageList.txt', 'r', encoding='utf-8') as f:
for line in f.readlines():
packageName = line.strip('\n') # 去除文本中的换行符
pkgList.append(packageName)
try:
pathCmd = "adb shell pm path " + packageName
result = os.popen(pathCmd).read().strip('\n') # 去除末尾的换行符,"package:/system/priv-app/aaa.apk"
pkgPath = result.split(":")[1] # 截取返回结果中的路径,去除头部多余的"package:"
pullCmd = "adb pull " + pkgPath + " D:/tmp/Tr0e/TestApk/" + packageName + ".apk"
os.system(pullCmd)
print(Fore.GREEN + "[+]Success pull: " + packageName)
except Exception as e:
print(Fore.RED + "[-]%s" % e)
print(Fore.RED + "[-]Pull {0} fail, please check packageName.".format(packageName))
print(Fore.BLUE + "[*]Done.Enjoy it!")
使用 Android 模拟器来做试验, package.txt 列出的想要 pull 的应用如下:
com.android.email
com.android.calendar
com.android.contacts
代码运行效果如下:
根据手机路径
以上的场景局限于在想要 pull 特定的应用列表且已知其 packageName,实际上要批量快速获取手机上所有 APK,那么应该 pull 特定的路径下的整个文件夹,比如:
pathList = ["system/priv-app", "system/app", "hw_product/app"]
代码如下所示:
def pullAPK_by_SystemPath():
"""
根据手机的app path路径,批量拉取手机中的APK文件到本地路径
:return: null
"""
pathList = ["system/priv-app", "system/app"]
print(Fore.BLUE + "[*]Start pull apk…")
start = time.time()
for path in pathList:
pullCmd = "adb pull " + path + " D:/tmp/Tr0e/pullApk/" + path.replace("/", "_")
os.system(pullCmd)
print(Fore.GREEN + "[+]Success pull: " + path)
end = time.time()
print(Fore.BLUE + "[*]Done.Totally time is " + str(end - start) + "s.Enjoy it!")
代码运行效果如下所示:
以上便解决了批量拉取手机上的 APK 文件的问题。
逆向APK
接下来便可以对 pull 下来的 apk 文件进行自动化的反编译了,以便对 apk 文件的源码进行检索。
自动化反编译
反编译借助的是 jadx-1.4.5.zip 反编译神器,请自行下载并解压缩到本地文件夹:
批量反编译的代码如下所示:
apk_list = [] # 递归查询指定文件夹后获得的所有apk文件的路径列表
def apkReverse():
"""
借助jadx工具,批量反编译指定文件夹下的所有APK(支持文件夹嵌套),输出到outputPath
:return: null
"""
apkPath = os.walk("D:/tmp/Android/TestApk/")
toolPath = "D:/Security/Mobile/jadx/jadx-1.4.4/bin/jadx"
outputPath = "D:/tmp/Android/Result/"
find_apk("D:/tmp/Android/TestApk")
apkTotalNum = len(apk_list)
num = 1
start = time.time()
print(Fore.BLUE + "[*]Start reverse apk…")
for path, dir_list, file_list in apkPath: # 反编译apkPath文件夹下所有的apk文件
for file_name in file_list:
if file_name.endswith('.apk'):
print(Fore.GREEN + "*****************************************")
print("[" + str(num) + "/" + str(apkTotalNum) + "]" + "正在反编译的APK:" + file_name)
path_apk = os.path.join(path, file_name)
command = toolPath + " -d " + outputPath + file_name + " -j 4 " + path_apk
os.system(command)
num = num + 1
end = time.time()
print(Fore.GREEN + "[*]Done.Totally time is " + str(end - start) + "s.Enjoy it!")
def find_apk(file_path):
"""
递归查询file_path文件夹下的apk文件
:param file_path: 目标文件夹,如D:/tmp/Android,请留意最后不要加"/"
:return: 目标文件夹下所有apk文件的列表
"""
if os.path.isfile(file_path):
if str(file_path).endswith(".apk"):
apk_list.append(file_path)
else:
for file_ls in os.listdir(file_path):
find_apk(str(file_path) + "/" + str(file_ls))
为了方便演示,指定待反编译的 APK 文件夹存放如下文件(可以看到,存在子文件夹、非 apk 类型的文件):
运行脚本进行自动化反编译,效果如下:
可以看到,程序已经帮我们自动识别出目标文件夹下有哪些 apk 应用并进行了批量反编译,反编译出来的文件结果输出到指定的 outputPath 路径下。
findstr检索…
完成了 apk 文件的批量拉取和自动化反编译,接下来就可以借助 cmd 命令 findstr 在 Windows 设备上对反编译出来的 APP 代码和资源文件进行快速检索了。
比如在指定文件夹下,忽略大小写、递归搜索所有 java 文件下得到关键词:
findstr /I /s "public static" *.java
检索效果如下所示(实际上细心的你一定会发现我下图检索出来的文件夹下是反编译失败的结果……是的没错,jadx 并无法保证一定能反编译成功):
findstr命令参考教程:在Windows中使用Findstr命令搜索文本文件内容。
完整命令帮助如下:
Searches for strings in files.
FINDSTR [/B] [/E] [/L] [/R] [/S] [/I] [/X] [/V] [/N] [/M] [/O] [/P] [/F:file]
[/C:string] [/G:file] [/D:dir list] [/A:color attributes] [/OFF[LINE]]
strings [[drive:][path]filename[ ...]]
/B Matches pattern if at the beginning of a line.
/E Matches pattern if at the end of a line.
/L Uses search strings literally.
/R Uses search strings as regular expressions.
/S Searches for matching files in the current directory and all
subdirectories.
/I Specifies that the search is not to be case-sensitive.
/X Prints lines that match exactly.
/V Prints only lines that do not contain a match.
/N Prints the line number before each line that matches.
/M Prints only the filename if a file contains a match.
/O Prints character offset before each matching line.
/P Skip files with non-printable characters.
/OFF[LINE] Do not skip files with offline attribute set.
/A:attr Specifies color attribute with two hex digits. See "color /?"
/F:file Reads file list from the specified file(/ stands for console).
/C:string Uses specified string as a literal search string.
/G:file Gets search strings from the specified file(/ stands for console).
/D:dir Search a semicolon delimited list of directories
strings Text to be searched for.
[drive:][path]filename
Specifies a file or files to search.
Use spaces to separate multiple search strings unless the argument is prefixed
with /C. For example, 'FINDSTR "hello there" x.y' searches for "hello" or
"there" in file x.y. 'FINDSTR /C:"hello there" x.y' searches for
"hello there" in file x.y.
Regular expression quick reference:
. Wildcard: any character
* Repeat: zero or more occurrences of previous character or class
^ Line position: beginning of line
$ Line position: end of line
[class] Character class: any one character in set
[^class] Inverse class: any one character not in set
[x-y] Range: any characters within the specified range
\x Escape: literal use of metacharacter x
\<xyz Word position: beginning of word
xyz\> Word position: end of word
For full information on FINDSTR regular expressions refer to the online Command
Reference.
总结
工欲善其事,必先利其器。如何将重复的工作通过自动化脚本来完成,是每个安全工程师提高工作效率和漏洞捕获成功率必须面对的问题。同时脚本和工具需要在实战过程中不断改进和优化,最后给各位附上本文的完整代码:apkReverse.py。