一. 软件

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
案例脚本,沙雅好地方:

  1. Java.perform(function () {
  2. //类的全限定类名,如果是内部类中间用$,如a$b
  3. var MainActivity = Java.use('com.cmstop.ctmediacloud.util.SignUtil');
  4. MainActivity.getSignStr.overload('java.util.Map','java.lang.String','java.lang.String').implementation = function (map,str1,str2) {
  5. //send(map);
  6. var keyset = map.keySet();
  7. var it = keyset.iterator();
  8. var result=''
  9. while(it.hasNext()){
  10. var keystr = it.next().toString();
  11. var valuestr = map.get(keystr).toString();
  12. result += keystr+':'+valuestr+'#';
  13. }
  14. send(result)
  15. send(str1);
  16. send(str2);
  17. //调用当前类的方法
  18. var res=this.getSignStr(map,str1,str2);
  19. send(res)
  20. return res;
  21. };
  22. });

逆向函数追踪:https://blog.csdn.net/weixin_30099269/article/details/117303411
启动时加载脚本:frida -U -f com.example.king.testappsflyer —no-pause -l raptor_frida_android_trace.js

  1. 夜神模拟器

端口被占用:netstat -ano | findstr “5037”查找进程号,taskkill -f -pid 进程号
开放端口:adb forward tcp:27042 tcp:27042
打开多个模拟器报错:adb -s 模拟器ip和端口 shell
连接模拟器:adb connect 127.0.0.1:62001

  1. ida,版本6.8,反编译so

https://www.32r.com/soft/5237.html
ida基本用法:https://zhuanlan.zhihu.com/p/48776320

  1. gda

java反编译工具,跟jadx差不多,有时可以反编译jadx无法反编译的代码
https://www.pcsoft.com.cn/soft/210021.html

  1. AndroidKiller

反编译重打包工具
下载地址:https://down.52pojie.cn/Tools/Android_Tools/
软件中的Apktool的版本太低可能导致apk的反编译失败,需要下载新版本Apktool.jar
替换掉D:\long\AndroidKiller_v1.3.1\bin\apktool\apktool目录下的ShakaApktool.jar

  1. Postern

下载:https://apkpure.com/cn/postern/com.tunnelworkshop.postern/download?from=details,需要代理
配合charles一起使用vpn抓包:https://blog.csdn.net/wuleixxh/article/details/123985910

  1. Inspeckage

xpose的一个模块
基本使用:https://www.jianshu.com/p/86010faaa2a8
下载地址:

二.常见问题

  1. 用fiddler抓不到包

https://mp.weixin.qq.com/s/yCSVUCd1iORCTmwF189Egg
如果上面方法无法解决就用抓包精灵

  1. 用fiddler,app提示无网络

https://mp.weixin.qq.com/s/2PngAUmhOLxnURq_nGvEAQ
或者电脑安装HTTP Debugger Pro抓全局流量

  1. 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

  1. 安装frida依赖等了很久然后报错

https://blog.csdn.net/italan/article/details/122881957

  1. charles在安卓7安装证书还是抓不了包

https://blog.csdn.net/weixin_44112152/article/details/119296952

  1. adb server is out of date. killing

如果端口本身就是被adb.exe占用,大概率就是adb版本太老了

  1. 反编译出现defpackage包,hook时报包找不到的错

defpackage是混淆自动生成的,hook时可以忽略,如反编译a类路径defpackage.a,直接hook a类

  1. 手机配置v2后无法抓包

手机只能配置一个代理,要抓境外app需要配置charles配置代理
charles配置代理:
proxy-》external proxy setings-》web proxy、secure web proxy勾上-》配置电脑的代理ip和端口-》
127.0.0.1、10809-》ok-》手机正常配置就可以连境外网了

  1. frida工具地址hook函数内部调用的函数找不到

直接根据调用找到的地址是错误的,需要点击函数到函数定义的地方,这个地址才是真正的地址

  1. JustTrustMe模块失效

JustTrustMe hook的类名是写死的,如果app混淆了网络框架就无法hook到
https://bbs.pediy.com/thread-254114.htm

三.混淆

混淆的概念:将Android项目进行打包之时,可以将项目里的包名、类名、变量名进行更改,使得代码不容易泄露,类似于对其apk中的文件加密

四.常见加密算法

  1. 消息摘要算法

java实现方式:网上笔记https://blog.csdn.net/ly20116/article/details/51025437
python实现方式:

  1. # HmacSHA256,其他sha同理
  2. # 加密,hmac.new(密码, 字符串.encode('utf-8'), hashlib.sha256).hexdigest()
  3. hmac.new(b'200008', 'aaaaaaaa'.encode('utf-8'), hashlib.sha256).hexdigest()
  4. # base64
  5. # 解码,base64.b64decode(加密字符串).decode()
  6. base64.b64decode('MTExYWFh').decode()
  7. # 编码,base64.b64encode(字符串.encode()).decode()
  8. base64.b64encode('111aaaa'.encode()).decode()
  9. # md5
  10. # 加密,hashlib.md5(字符串.encode('utf-8')).hexdigest()
  11. hashlib.md5('aaaaa'.encode('utf-8')).hexdigest()
  12. # sha256,其他sha同理
  13. # 加密,hashlib.sha256(字符串.encode('utf-8')).hexdigest()
  14. hashlib.sha256('aaaaa'.encode('utf-8')).hexdigest()

如果java执行digest前执行多次update,python对应要按前后顺序拼接字符串

  1. 对称加密算法

DES

  1. // 明文
  2. String plainText = "abcd";
  3. // 提供原始秘钥:长度64位,8字节
  4. String originKey = "12345678";
  5. // 根据给定的字节数组构建一个秘钥,一般hook SecretKeySpec构造方法就能看到密钥
  6. // DES3用法跟DES一样,只是DES关键字变成DES3,密匙长度是24位,DES是8位
  7. SecretKeySpec key = new SecretKeySpec(originKey.getBytes(), "DES");
  8. // 加密
  9. // 1.获取加密算法工具类
  10. Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
  11. // 2.对工具类对象进行初始化,
  12. // mode:加密/解密模式,Cipher.ENCRYPT_MODE:1,加密
  13. // key:对原始秘钥处理之后的秘钥
  14. cipher.init(Cipher.ENCRYPT_MODE, key);
  15. // 3.用加密工具类对象对明文进行加密
  16. byte[] encipherByte = cipher.doFinal(plainText.getBytes());
  17. // 防止乱码,使用Base64编码
  18. String encode = Base64.encodeBase64String(encipherByte);
  19. // 解密,Cipher.DECRYPT_MODE:2,解密
  20. // 2.对工具类对象进行初始化
  21. cipher.init(Cipher.DECRYPT_MODE, key);
  22. // 3.用加密工具类对象对密文进行解密
  23. byte[] decode = Base64.decodeBase64(encode);
  24. byte[] decipherByte = cipher.doFinal(decode);
  25. String decipherText = new String(decipherByte);
  1. from Crypto.Cipher import DES3
  2. import base64
  3. '''
  4. python3.7里的Crypto模块已经不是以前的“pycrypto”,而是“pycryptodome”。
  5. 直接下载库会报No module named 'Crypto'。
  6. 需要下载pycryptodome,去python的安装目录把“Crypto"文件夹改成”crypto"
  7. '''
  8. class EncryptDate:
  9. def __init__(self, key,iv):
  10. self.key = key
  11. self.iv = iv
  12. '''
  13. KEY的值必须是16位或者24位
  14. DES用法跟DES3,只是把关键字替换成DES,并且加密的key只能为8位,必须是字节数据
  15. '''
  16. self.length = DES3.block_size # 初始化数据块大小
  17. self.aes = DES3.new(key=self.key.encode(), mode=DES3.MODE_CBC,iv=self.iv.encode()) # 初始化AES,ECB模式的实例
  18. # 截断函数,去除填充的字符
  19. self.unpad = lambda date: date[0:-ord(date[-1])]
  20. def pad(self, text):
  21. """
  22. #填充函数,使被加密数据的字节码长度是block_size的整数倍
  23. """
  24. count = len(text.encode('utf-8'))
  25. add = self.length - (count % self.length)
  26. entext = text + (chr(add) * add)
  27. return entext
  28. def encrypt(self, encrData): # 加密函数
  29. res = self.aes.encrypt(self.pad(encrData).encode("utf8"))
  30. msg = str(base64.b64encode(res), encoding="utf8")
  31. # msg = res.hex()
  32. print(msg)
  33. return msg
  34. def decrypt(self, decrData): # 解密函数
  35. res = base64.decodebytes(decrData.encode("utf8"))
  36. # res = bytes.fromhex(decrData)
  37. des=DES3.new(key=self.key.encode(), mode=DES3.MODE_CBC, iv=self.iv.encode())
  38. msg =des.decrypt(res).decode("utf8")
  39. return self.unpad(msg)
  40. if __name__ == '__main__':
  41. cr = EncryptDate('snxt01234567890123456789','01234567')
  42. cr_res = cr.encrypt('{\n "agentId": "128811",\n "company": "",\n "shipId": "4304288748955"\n}')
  43. de_res = cr.decrypt(cr_res)
  44. print(de_res)

AES

  1. // 密匙.getBytes()
  2. SecretKeySpec key = new SecretKeySpec("1234567890abcdef1234567890abcdef".getBytes(), "AES");
  3. // iv偏移
  4. AlgorithmParameterSpec iv = new IvParameterSpec("1234567890abcdef".getBytes());
  5. // 加密方式
  6. Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding");
  7. // 1:加密,2:解密
  8. aes.init(1, key, iv);
  9. // 要加密的字符串.getBytes()
  10. byte[] bres = aes.doFinal("a12345678".getBytes("UTF-8"));
  11. // 获得加密后的字符串
  12. String encode=Base64.encodeBase64String(bres);
  1. from Crypto.Cipher import AES
  2. from base64 import b64decode, b64encode
  3. BLOCK_SIZE = AES.block_size
  4. # CBC AES PKCS7Padding填充模式
  5. pad = lambda s: s + (BLOCK_SIZE - len(s.encode()) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(s.encode()) % BLOCK_SIZE)
  6. # 去除补位
  7. unpad = lambda s: s[:-ord(s[len(s) - 1:])]
  8. class AESCipher:
  9. def __init__(self, secretkey: str):
  10. self.key = secretkey # 密钥
  11. self.iv = secretkey[0:16] # 偏移量
  12. def encrypt(self, text):
  13. """
  14. 加密 :先补位,再AES加密,后base64编码
  15. :param text: 需加密的明文
  16. :return:
  17. """
  18. # text = pad(text) 包pycrypto的写法,加密函数可以接受str也可以接受bytess
  19. text = pad(text).encode() # 包pycryptodome 的加密函数不接受str
  20. cipher = AES.new(key=self.key.encode(), mode=AES.MODE_CBC, IV=self.iv.encode())
  21. encrypted_text = cipher.encrypt(text)
  22. # 进行64位的编码,返回得到加密后的bytes,decode成字符串
  23. return b64encode(encrypted_text).decode('utf-8')
  24. def decrypt(self, encrypted_text):
  25. """
  26. 解密 :偏移量为key[0:16];先base64解,再AES解密,后取消补位
  27. :param encrypted_text : 已经加密的密文
  28. :return:
  29. """
  30. encrypted_text = b64decode(encrypted_text)
  31. cipher = AES.new(key=self.key.encode(), mode=AES.MODE_CBC, IV=self.iv.encode())
  32. decrypted_text = cipher.decrypt(encrypted_text)
  33. return unpad(decrypted_text).decode('utf-8')
  34. key='1234567890abcdef'
  35. aes=AESCipher(key)
  36. s='aaabbb'
  37. encode=aes.encrypt(s)
  38. print(encode)
  39. decode=aes.decrypt(encode)
  40. print(decode)

AES/CBC/NoPadding 128位模式加密:https://blog.csdn.net/weixin_42068117/article/details/80084034

  1. 非对称加密算法

RSA
RSA使用公匙加密,私匙解密
在线生成公匙私匙:http://web.chacuo.net/netrsakeypair
Java实现:一般出现Cipher.getInstance(“RSA”)、Cipher.getInstance(“RSA/None/NoPadding”, “BC”)
类似的代码就是RSA加密

  1. import base64
  2. from Crypto.Cipher import PKCS1_v1_5 as Cipher_pksc1_v1_5
  3. from Crypto.PublicKey import RSA
  4. def _encrpt(string, public_key):
  5. rsakey = RSA.importKey(public_key) # 读取公钥
  6. cipher = Cipher_pksc1_v1_5.new(rsakey)
  7. encrypt_text = cipher.encrypt(string.encode()) # 1.对账号密码组成的字符串加密
  8. cipher_text_tmp = base64.b64encode(encrypt_text) # 2.对加密后的字符串base64加密
  9. return cipher_text_tmp.decode()
  10. def gen_body(pwd, public_key=None):
  11. # 对应的公钥
  12. if not public_key: public_key = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCtbf6RRHN/oKCTZYuZiMzEE3vVmDe8A6z0dDPLIgPz9m29TVlm/usVOEY4bnE85+d5XyGhb3GaYqbDKrlubsfeomczTCsSVRVDNWMPGIGs0rr3SWmLUvib/LsDia+IxOvAwOJijWt/w49iS0ZIFBH9dhS9+n3sG+yWm9yKIr6DnwIDAQAB'
  13. key = '-----BEGIN PUBLIC KEY-----\n' + public_key + '\n-----END PUBLIC KEY-----'
  14. encrypt_res = _encrpt(pwd, key)
  15. return encrypt_res
  16. if __name__ == '__main__':
  17. # 要加密的密码
  18. 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

  1. '''
  2. java中key,iv等参数对应的类型是字节数组,需要进行转换才能够在python上是使用
  3. '''
  4. # java的字节
  5. 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]
  6. # 转为python字节
  7. key_byte = bytes(i % 256 for i in key_arr)
  8. # 偏移值也要使用转换后的字节
  9. iv_arr=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
  10. # 如果写成b'0000000000000000'会导致偏移值对不上
  11. iv_byte=bytes(i % 256 for i in iv_arr)
  12. # python字节转java字节
  13. b_arr=[9, 139, 198, 98]
  14. j_arr=[int(i) - 256 if int(i) > 127 else int(i) for i in b_arr]

五.软件常用方法

  1. jadx

导出源码到android studio分析:
(1)反混淆
工具-》反混淆,jadx会自动在混淆的方法上写原方法名,hook也要原方法名
(2)导出源码
文件-》导出为gradle
(3)导入项目
android studio新建空项目-》删除src目录-》复制导出的src目录

  1. ida

VIEW模式下search-》text,搜索关键字
汇编代码模式下,选中函数名-》快捷键x,可以查找函数的引用

六.frida

  1. frida hook 绕过app代理检测

java的System.getProperty方法有两个重载一个String参数和两个String参数
传入http.proxyHost或https.proxyHost可以获取代理ip,没有代理就返回null,直接hook返回null就可以绕过

  1. Java.perform(function () {
  2. Java.use('java.lang.System').getProperty.overload('java.lang.String').implementation = function (s1) {
  3. send(s1)
  4. var res=this.getProperty(s1)
  5. send(res)
  6. // proxyHost获取代理ip,proxyPort获取代理端口
  7. if(s1=='http.proxyHost' || s1=='http.proxyPort' ||s1=='https.proxyHost' || s1=='https.proxyPort'){
  8. return null;
  9. }
  10. return res;
  11. }
  12. Java.use('java.lang.System').getProperty.overload('java.lang.String','java.lang.String').implementation = function (s1,s2) {
  13. send(s1)
  14. send(s2)
  15. var res=this.getProperty(s1,s2)
  16. send(res)
  17. if(s1=='http.proxyHost' || s1=='http.proxyPort' ||s1=='https.proxyHost' || s1=='https.proxyPort'){
  18. return null;
  19. }
  20. return res;
  21. }
  22. });
  1. 命令行启动脚本

spawn模式:frida -U -f com.kevin.android -l demo1.js —no-pause
attach模式:frida -U com.kevin.android -l hook.js —no-pause

七. 抓包

  1. 证书双向验证

(1)客户端验证
使用JustTrustMe
(2)客户端验证
需要导入客户端证书到抓包软件
解压app证书一般在/asset或/res/raw,后缀名bks、p12
证书格式互转工具:https://sourceforge.net/projects/portecle/,运行protecle.jar
反编译搜索KeyStore.getInstance一般可以找到初始化的地方
java.security.KeyStore类的load方法参数1是证书文件流,参数2是密码

  1. 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启动停在首页没反应一般是加壳的检测,需要脱壳再修复

  1. 腾讯御安全·

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就是未加固状态

  1. 梆梆加固免费版

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)去除签名校验

十. 手机刷机

  1. 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

  1. 根据地址打印对应数据

案例app:自毁程序密码,https://blog.csdn.net/zhu6201976/article/details/114701170
打印对应地址数据

  1. Java.perform(function () {
  2. var so=Module.findBaseAddress("libcrackme.so")
  3. console.log(so)
  4. var sAd=Memory.readUtf8String(Memory.readPointer(so.add(0x628c)))
  5. send(sAd)
  6. });
  1. 案例实战

(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

十二. 反爬

  1. root检测

(1)面具过root检测
测试app:西宁晚报,https://www.nongjia888.com/app/com.founder.xining
方法:http://www.romleyuan.com/news/readnews?newsid=2982