一. 软件
1.jadx或jd-gui,用于反编译源码
jadx可以直接把apk托进去,jd-gui需要编译成jar
jadx使用及下载:http://www.ucbug.com/soft/109512.html
jd-gui使用及下载:http://www.xitongzhijia.net/soft/193069.html
2.dex2jar,用于把dex文件转成jar,配合jd-gui使用
使用方法:将需要反编译的dex文件复制到 dex2jar 解压目录下。
打开命令行进入 d2j-dex2jar.bat 文件所在目录,输入命令 d2j-dex2jar.bat classes.dex
下载地址:https://www.onlinedown.net/soft/989451.htm
3.APK Messenger,用于查看apk是否加固
下载地址:https://www.zdfans.com/html/26667.html
使用方法:直接把apk拖进去
4.BlackDex,脱壳软件
下载地址:32位:https://fsylr.lanzoui.com/ij2zuym3wri,64位:https://fsylr.lanzoui.com/ivqkRym3vlg
使用方法:https://blog.csdn.net/weixin_52369224/article/details/122722510
夜神模拟器安卓5无法脱壳,会提示失败,要安卓7。
5.反射大师,脱壳软件
下载地址:http://www.pc6.com/az/1007465.html
使用方法:https://blog.csdn.net/qq_41855420/article/details/106276824
6.frida,常用hook工具
环境:夜神模拟器
环境搭建:https://www.cnblogs.com/tjp40922/p/11353808.html
官方文档:https://frida.re/docs/
githup:https://github.com/frida/frida/releases
简单使用:https://zhuanlan.zhihu.com/p/73008784
测试app:https://github.com/ctfs/write-ups-2015/blob/master/seccon-quals-ctf-2015/binary/reverse-engineering-android-apk-1/rps.apk
案例脚本,沙雅好地方:
Java.perform(function () {//类的全限定类名,如果是内部类中间用$,如a$bvar MainActivity = Java.use('com.cmstop.ctmediacloud.util.SignUtil');MainActivity.getSignStr.overload('java.util.Map','java.lang.String','java.lang.String').implementation = function (map,str1,str2) {//send(map);var keyset = map.keySet();var it = keyset.iterator();var result=''while(it.hasNext()){var keystr = it.next().toString();var valuestr = map.get(keystr).toString();result += keystr+':'+valuestr+'#';}send(result)send(str1);send(str2);//调用当前类的方法var res=this.getSignStr(map,str1,str2);send(res)return res;};});
逆向函数追踪:https://blog.csdn.net/weixin_30099269/article/details/117303411
启动时加载脚本:frida -U -f com.example.king.testappsflyer —no-pause -l raptor_frida_android_trace.js
- 夜神模拟器
端口被占用:netstat -ano | findstr “5037”查找进程号,taskkill -f -pid 进程号
开放端口:adb forward tcp:27042 tcp:27042
打开多个模拟器报错:adb -s 模拟器ip和端口 shell
连接模拟器:adb connect 127.0.0.1:62001
- ida,版本6.8,反编译so
https://www.32r.com/soft/5237.html
ida基本用法:https://zhuanlan.zhihu.com/p/48776320
- gda
java反编译工具,跟jadx差不多,有时可以反编译jadx无法反编译的代码
https://www.pcsoft.com.cn/soft/210021.html
- AndroidKiller
反编译重打包工具
下载地址:https://down.52pojie.cn/Tools/Android_Tools/
软件中的Apktool的版本太低可能导致apk的反编译失败,需要下载新版本Apktool.jar
替换掉D:\long\AndroidKiller_v1.3.1\bin\apktool\apktool目录下的ShakaApktool.jar
- Postern
下载:https://apkpure.com/cn/postern/com.tunnelworkshop.postern/download?from=details,需要代理
配合charles一起使用vpn抓包:https://blog.csdn.net/wuleixxh/article/details/123985910
- Inspeckage
xpose的一个模块
基本使用:https://www.jianshu.com/p/86010faaa2a8
下载地址:
二.常见问题
- 用fiddler抓不到包
https://mp.weixin.qq.com/s/yCSVUCd1iORCTmwF189Egg
如果上面方法无法解决就用抓包精灵
- 用fiddler,app提示无网络
https://mp.weixin.qq.com/s/2PngAUmhOLxnURq_nGvEAQ
或者电脑安装HTTP Debugger Pro抓全局流量
- hook运行脚本错误
(1)frida.ProcessNotFoundError: unable to find process with name ‘com.example.seccon2015.rock_paper_scissors’
找不到对应的进程name,frida-ps -R打印所有进程,对照改成对应name
(2)frida.InvalidArgumentError: device not found
把get_usb_device函数改成get_remote_device
- 安装frida依赖等了很久然后报错
https://blog.csdn.net/italan/article/details/122881957
- charles在安卓7安装证书还是抓不了包
https://blog.csdn.net/weixin_44112152/article/details/119296952
- adb server is out of date. killing
如果端口本身就是被adb.exe占用,大概率就是adb版本太老了
- 反编译出现defpackage包,hook时报包找不到的错
defpackage是混淆自动生成的,hook时可以忽略,如反编译a类路径defpackage.a,直接hook a类
- 手机配置v2后无法抓包
手机只能配置一个代理,要抓境外app需要配置charles配置代理
charles配置代理:
proxy-》external proxy setings-》web proxy、secure web proxy勾上-》配置电脑的代理ip和端口-》
127.0.0.1、10809-》ok-》手机正常配置就可以连境外网了
- frida工具地址hook函数内部调用的函数找不到
直接根据调用找到的地址是错误的,需要点击函数到函数定义的地方,这个地址才是真正的地址
- JustTrustMe模块失效
JustTrustMe hook的类名是写死的,如果app混淆了网络框架就无法hook到
https://bbs.pediy.com/thread-254114.htm
三.混淆
混淆的概念:将Android项目进行打包之时,可以将项目里的包名、类名、变量名进行更改,使得代码不容易泄露,类似于对其apk中的文件加密
四.常见加密算法
- 消息摘要算法
java实现方式:网上笔记https://blog.csdn.net/ly20116/article/details/51025437
python实现方式:
# HmacSHA256,其他sha同理# 加密,hmac.new(密码, 字符串.encode('utf-8'), hashlib.sha256).hexdigest()hmac.new(b'200008', 'aaaaaaaa'.encode('utf-8'), hashlib.sha256).hexdigest()# base64# 解码,base64.b64decode(加密字符串).decode()base64.b64decode('MTExYWFh').decode()# 编码,base64.b64encode(字符串.encode()).decode()base64.b64encode('111aaaa'.encode()).decode()# md5# 加密,hashlib.md5(字符串.encode('utf-8')).hexdigest()hashlib.md5('aaaaa'.encode('utf-8')).hexdigest()# sha256,其他sha同理# 加密,hashlib.sha256(字符串.encode('utf-8')).hexdigest()hashlib.sha256('aaaaa'.encode('utf-8')).hexdigest()
如果java执行digest前执行多次update,python对应要按前后顺序拼接字符串
- 对称加密算法
DES
// 明文String plainText = "abcd";// 提供原始秘钥:长度64位,8字节String originKey = "12345678";// 根据给定的字节数组构建一个秘钥,一般hook SecretKeySpec构造方法就能看到密钥// DES3用法跟DES一样,只是DES关键字变成DES3,密匙长度是24位,DES是8位SecretKeySpec key = new SecretKeySpec(originKey.getBytes(), "DES");// 加密// 1.获取加密算法工具类Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");// 2.对工具类对象进行初始化,// mode:加密/解密模式,Cipher.ENCRYPT_MODE:1,加密// key:对原始秘钥处理之后的秘钥cipher.init(Cipher.ENCRYPT_MODE, key);// 3.用加密工具类对象对明文进行加密byte[] encipherByte = cipher.doFinal(plainText.getBytes());// 防止乱码,使用Base64编码String encode = Base64.encodeBase64String(encipherByte);// 解密,Cipher.DECRYPT_MODE:2,解密// 2.对工具类对象进行初始化cipher.init(Cipher.DECRYPT_MODE, key);// 3.用加密工具类对象对密文进行解密byte[] decode = Base64.decodeBase64(encode);byte[] decipherByte = cipher.doFinal(decode);String decipherText = new String(decipherByte);
from Crypto.Cipher import DES3import base64'''python3.7里的Crypto模块已经不是以前的“pycrypto”,而是“pycryptodome”。直接下载库会报No module named 'Crypto'。需要下载pycryptodome,去python的安装目录把“Crypto"文件夹改成”crypto"'''class EncryptDate:def __init__(self, key,iv):self.key = keyself.iv = iv'''KEY的值必须是16位或者24位DES用法跟DES3,只是把关键字替换成DES,并且加密的key只能为8位,必须是字节数据'''self.length = DES3.block_size # 初始化数据块大小self.aes = DES3.new(key=self.key.encode(), mode=DES3.MODE_CBC,iv=self.iv.encode()) # 初始化AES,ECB模式的实例# 截断函数,去除填充的字符self.unpad = lambda date: date[0:-ord(date[-1])]def pad(self, text):"""#填充函数,使被加密数据的字节码长度是block_size的整数倍"""count = len(text.encode('utf-8'))add = self.length - (count % self.length)entext = text + (chr(add) * add)return entextdef encrypt(self, encrData): # 加密函数res = self.aes.encrypt(self.pad(encrData).encode("utf8"))msg = str(base64.b64encode(res), encoding="utf8")# msg = res.hex()print(msg)return msgdef decrypt(self, decrData): # 解密函数res = base64.decodebytes(decrData.encode("utf8"))# res = bytes.fromhex(decrData)des=DES3.new(key=self.key.encode(), mode=DES3.MODE_CBC, iv=self.iv.encode())msg =des.decrypt(res).decode("utf8")return self.unpad(msg)if __name__ == '__main__':cr = EncryptDate('snxt01234567890123456789','01234567')cr_res = cr.encrypt('{\n "agentId": "128811",\n "company": "",\n "shipId": "4304288748955"\n}')de_res = cr.decrypt(cr_res)print(de_res)
AES
// 密匙.getBytes()SecretKeySpec key = new SecretKeySpec("1234567890abcdef1234567890abcdef".getBytes(), "AES");// iv偏移AlgorithmParameterSpec iv = new IvParameterSpec("1234567890abcdef".getBytes());// 加密方式Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding");// 1:加密,2:解密aes.init(1, key, iv);// 要加密的字符串.getBytes()byte[] bres = aes.doFinal("a12345678".getBytes("UTF-8"));// 获得加密后的字符串String encode=Base64.encodeBase64String(bres);
from Crypto.Cipher import AESfrom base64 import b64decode, b64encodeBLOCK_SIZE = AES.block_size# CBC AES PKCS7Padding填充模式pad = lambda s: s + (BLOCK_SIZE - len(s.encode()) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(s.encode()) % BLOCK_SIZE)# 去除补位unpad = lambda s: s[:-ord(s[len(s) - 1:])]class AESCipher:def __init__(self, secretkey: str):self.key = secretkey # 密钥self.iv = secretkey[0:16] # 偏移量def encrypt(self, text):"""加密 :先补位,再AES加密,后base64编码:param text: 需加密的明文:return:"""# text = pad(text) 包pycrypto的写法,加密函数可以接受str也可以接受bytesstext = pad(text).encode() # 包pycryptodome 的加密函数不接受strcipher = AES.new(key=self.key.encode(), mode=AES.MODE_CBC, IV=self.iv.encode())encrypted_text = cipher.encrypt(text)# 进行64位的编码,返回得到加密后的bytes,decode成字符串return b64encode(encrypted_text).decode('utf-8')def decrypt(self, encrypted_text):"""解密 :偏移量为key[0:16];先base64解,再AES解密,后取消补位:param encrypted_text : 已经加密的密文:return:"""encrypted_text = b64decode(encrypted_text)cipher = AES.new(key=self.key.encode(), mode=AES.MODE_CBC, IV=self.iv.encode())decrypted_text = cipher.decrypt(encrypted_text)return unpad(decrypted_text).decode('utf-8')key='1234567890abcdef'aes=AESCipher(key)s='aaabbb'encode=aes.encrypt(s)print(encode)decode=aes.decrypt(encode)print(decode)
AES/CBC/NoPadding 128位模式加密:https://blog.csdn.net/weixin_42068117/article/details/80084034
- 非对称加密算法
RSA
RSA使用公匙加密,私匙解密
在线生成公匙私匙:http://web.chacuo.net/netrsakeypair
Java实现:一般出现Cipher.getInstance(“RSA”)、Cipher.getInstance(“RSA/None/NoPadding”, “BC”)
类似的代码就是RSA加密
import base64from Crypto.Cipher import PKCS1_v1_5 as Cipher_pksc1_v1_5from Crypto.PublicKey import RSAdef _encrpt(string, public_key):rsakey = RSA.importKey(public_key) # 读取公钥cipher = Cipher_pksc1_v1_5.new(rsakey)encrypt_text = cipher.encrypt(string.encode()) # 1.对账号密码组成的字符串加密cipher_text_tmp = base64.b64encode(encrypt_text) # 2.对加密后的字符串base64加密return cipher_text_tmp.decode()def gen_body(pwd, public_key=None):# 对应的公钥if not public_key: public_key = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCtbf6RRHN/oKCTZYuZiMzEE3vVmDe8A6z0dDPLIgPz9m29TVlm/usVOEY4bnE85+d5XyGhb3GaYqbDKrlubsfeomczTCsSVRVDNWMPGIGs0rr3SWmLUvib/LsDia+IxOvAwOJijWt/w49iS0ZIFBH9dhS9+n3sG+yWm9yKIr6DnwIDAQAB'key = '-----BEGIN PUBLIC KEY-----\n' + public_key + '\n-----END PUBLIC KEY-----'encrypt_res = _encrpt(pwd, key)return encrypt_resif __name__ == '__main__':# 要加密的密码print(gen_body("1234asdf"))
python rsa加密报ValueError: Plaintext is too long,是密文长度过长,rsa有长度限制
rsa分段加密:https://www.jianshu.com/p/857d21eb6ae1
RSA加密为什么每次加密的结果不一样:https://blog.csdn.net/heqiang2015/article/details/83586937
Java使用sha256的digest方法返回的字节数组作为AES加密的键python还原:
python3和java字节的取值范围不同:
Python3: 0~256
java: -127~128
'''java中key,iv等参数对应的类型是字节数组,需要进行转换才能够在python上是使用'''# java的字节key_arr = [52, 49, 69, 107, -62, 34, -94, -83, -69, 34, 76, -31, 16, -26, 49, 75, -43, 84, 125, -65, -38, 38, 94, 114, 11, 127, 53, -83, -46, 27, -70, 119]# 转为python字节key_byte = bytes(i % 256 for i in key_arr)# 偏移值也要使用转换后的字节iv_arr=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]# 如果写成b'0000000000000000'会导致偏移值对不上iv_byte=bytes(i % 256 for i in iv_arr)# python字节转java字节b_arr=[9, 139, 198, 98]j_arr=[int(i) - 256 if int(i) > 127 else int(i) for i in b_arr]
五.软件常用方法
- jadx
导出源码到android studio分析:
(1)反混淆
工具-》反混淆,jadx会自动在混淆的方法上写原方法名,hook也要原方法名
(2)导出源码
文件-》导出为gradle
(3)导入项目
android studio新建空项目-》删除src目录-》复制导出的src目录
- ida
VIEW模式下search-》text,搜索关键字
汇编代码模式下,选中函数名-》快捷键x,可以查找函数的引用
六.frida
- frida hook 绕过app代理检测
java的System.getProperty方法有两个重载一个String参数和两个String参数
传入http.proxyHost或https.proxyHost可以获取代理ip,没有代理就返回null,直接hook返回null就可以绕过
Java.perform(function () {Java.use('java.lang.System').getProperty.overload('java.lang.String').implementation = function (s1) {send(s1)var res=this.getProperty(s1)send(res)// proxyHost获取代理ip,proxyPort获取代理端口if(s1=='http.proxyHost' || s1=='http.proxyPort' ||s1=='https.proxyHost' || s1=='https.proxyPort'){return null;}return res;}Java.use('java.lang.System').getProperty.overload('java.lang.String','java.lang.String').implementation = function (s1,s2) {send(s1)send(s2)var res=this.getProperty(s1,s2)send(res)if(s1=='http.proxyHost' || s1=='http.proxyPort' ||s1=='https.proxyHost' || s1=='https.proxyPort'){return null;}return res;}});
- 命令行启动脚本
spawn模式:frida -U -f com.kevin.android -l demo1.js —no-pause
attach模式:frida -U com.kevin.android -l hook.js —no-pause
七. 抓包
- 证书双向验证
(1)客户端验证
使用JustTrustMe
(2)客户端验证
需要导入客户端证书到抓包软件
解压app证书一般在/asset或/res/raw,后缀名bks、p12
证书格式互转工具:https://sourceforge.net/projects/portecle/,运行protecle.jar
反编译搜索KeyStore.getInstance一般可以找到初始化的地方
java.security.KeyStore类的load方法参数1是证书文件流,参数2是密码
- frida hook抓包
脚本1:https://github.com/siyujie/OkHttpLogger-Frida
手机开启xpose环境可能会导致脚本启动失败
抓不到包或hold命令报某个类不存在:
可能是网络框架被混淆
find命令 查看相关框架-》复制对应的代码替换掉js的代码
八. smali
网上笔记:http://code.newban.cn/169.html
java和smali互转软件:javatosmali https://www.onlinedown.net/soft/1209079.htm
九. 脱壳修复
frida、xpose无法附加,或app启动停在首页没反应一般是加壳的检测,需要脱壳再修复
- 腾讯御安全·
app:p图抠图.1.0.0,https://m.cr173.com/mipx/1428482.html
https://www.bilibili.com/video/av424184178/
(1) 反射大师脱壳,删掉几百kb的,留下真正的dex
(2) 反编译原app的classes.dex,打开com.wrapper.proxyapplication.WrapperProxyApplication类
(3) 复制className的值(com.pikachu.utils.App)
(4) 反编译AndroidManifest.xml,把application的name属性改成复制的className的值
(5) 用mt管理器修复反射大师脱下的dex,需要会员,然后替换掉app的classes.dex
(6) 删掉assets下的0OO00l111l1l、o0oooOO0ooOo.dat、tosversion,lib下所有的libshell-super.2019.so、
libshella-4.1.0.30.so,可省略,如果不删会显示为伪xxx,不影响
(7) mt管理器去除签名校验,然后app就是未加固状态
- 梆梆加固免费版
app:二级建造师11.1,https://www.wandoujia.com/apps/6603110
https://www.bilibili.com/video/av594316433/
(1) 反射大师脱壳,删掉几百kb的,留下真正的dex
(2) 反编译原app的classes.dex,打开com.SecShell.SecShell.H类
(3) 复制APPNAME的值(com.ggeye.kaoshi.jianzaotwo.MainApplication)
(4) 反编译AndroidManifest.xml,把application的name属性改成复制的className的值,
appComponentFactory的值留空
(5) 用mt管理器修复反射大师脱下的dex,需要会员,然后替换掉app的classes.dex
(7) mt管理器去除签名校验,然后app就是伪梆梆加固,不影响,删掉加固残留后就是未加固
如果脱壳出来有多个dex并且都是需要的:
陕西和教育5.5.3:https://www.onlinedown.net/soft/10063188.htm
https://www.bilibili.com/video/BV1AP4y1J7Jx?spm_id_from=333.999.0.0
(1)脱壳不删除dex
(2)一样
(3)一样
(4)一样,appComponentFactory不改,只改application的name属性,删掉provider标签全部
(5)把原app的classes.dex复制出来名字不变,删掉com.SecShell.SecShell
(6)原app的dex名字不变,脱壳出来的dex名字依次为classes2.dex、classes3.dex、…
(7)把dex全部放到app里,删掉lib目录下名字带有SecShell字眼的lib、assets文件夹下classes0.jar、
meta-data文件夹删掉
(8)去除签名校验
十. 手机刷机
- pixel
刷机:https://blog.csdn.net/qq_41274349/article/details/123593822
https://blog.csdn.net/weixin_46932303/article/details/122767718
脚本停在waiting for any device,需要更新谷歌驱动:https://developer.android.google.cn/studio/run/win-usb.html?hl=zh-cn
十一. so
- 根据地址打印对应数据
案例app:自毁程序密码,https://blog.csdn.net/zhu6201976/article/details/114701170
打印对应地址数据
Java.perform(function () {var so=Module.findBaseAddress("libcrackme.so")console.log(so)var sAd=Memory.readUtf8String(Memory.readPointer(so.add(0x628c)))send(sAd)});
- 案例实战
(1)案例1
https://blog.csdn.net/qq_38851536/article/details/117418582
Unidbg:https://github.com/zhkl0228/unidbg
findhash:https://github.com/Pr0214/findhash
findhash需要在ida7.5使用,ida7.5:http://www.downyi.com/downinfo/99245.html
十二. 反爬
- root检测
(1)面具过root检测
测试app:西宁晚报,https://www.nongjia888.com/app/com.founder.xining
方法:http://www.romleyuan.com/news/readnews?newsid=2982
