feat: MariBank 风控 bypass、澳洲银行 Hook 与 reverse 逆向工作区
新增 MariBank/SeaBank PH Root 与 SHPSSDK bypass、riskToken 净化及 Up/Suncorp/ubank 消息 Hook;整理 reverse/ 脚本与 Frida 工具链,并补充当日工作说明文档。
This commit is contained in:
13
reverse/scripts/_paths.py
Normal file
13
reverse/scripts/_paths.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Shared paths for reverse/ scripts (scripts live in reverse/scripts/)."""
|
||||
from pathlib import Path
|
||||
|
||||
REVERSE_ROOT = Path(__file__).resolve().parent.parent
|
||||
APKS_DIR = REVERSE_ROOT / "apks"
|
||||
EXTRACTED_DIR = REVERSE_ROOT / "extracted"
|
||||
NATIVE_DIR = EXTRACTED_DIR / "native"
|
||||
TMP_DIR = REVERSE_ROOT / "tmp"
|
||||
OUTPUT_DIR = REVERSE_ROOT / "output"
|
||||
LOGS_DIR = REVERSE_ROOT / "logs"
|
||||
|
||||
DEFAULT_APK = APKS_DIR / "seabank_ph_base.apk"
|
||||
20
reverse/scripts/analyze_shps_so.py
Normal file
20
reverse/scripts/analyze_shps_so.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
SO = Path(__file__).resolve().parent.parent / "extracted" / "native" / "libshpssdk_bank.so"
|
||||
ndk = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\ndk\21.4.7075529\toolchains\llvm\prebuilt\windows-x86_64\bin")
|
||||
readelf = ndk / "llvm-readelf.exe"
|
||||
out = subprocess.check_output([str(readelf), "-Ws", str(SO)], universal_newlines=True, errors="replace")
|
||||
print("=== JNI Java_* ===")
|
||||
for line in out.splitlines():
|
||||
if "Java_com_shopee" in line:
|
||||
print(line)
|
||||
print("\n=== risk/root/token strings in .dynsym FUNC ===")
|
||||
data = SO.read_bytes()
|
||||
for m in re.finditer(rb"[\x20-\x7e]{4,}", data):
|
||||
s = m.group().decode("latin1")
|
||||
if any(k in s.lower() for k in ["risk", "root", "hook", "token", "proc/", "magisk", "xposed", "emulator", "assess"]):
|
||||
if len(s) < 120:
|
||||
print(s)
|
||||
5
reverse/scripts/context_wbroot.py
Normal file
5
reverse/scripts/context_wbroot.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
data = open(r"C:\Users\Administrator\Desktop\notiMessage\reverse\extracted\classes6.dex", "rb").read()
|
||||
idx = data.find(b"WBRootDetectionModule")
|
||||
print("offset", idx)
|
||||
print(data[idx-120:idx+200].decode("latin1", "ignore"))
|
||||
45
reverse/scripts/decode_shps_strings.py
Normal file
45
reverse/scripts/decode_shps_strings.py
Normal file
@@ -0,0 +1,45 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Decode SHPSSDK obfuscated hex strings via uvuwwwuwu."""
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
|
||||
|
||||
# sample pairs from SHPSSDK.wwwwvwwwu
|
||||
samples = [
|
||||
("0F580514065B111F0E1A10", "wuvuvvwvv"),
|
||||
("100F3C013C1C19113F18271F3A36371F2C253C403F323C053F353C013F1C19123F1F3C4F", "uvvvuvvvv"),
|
||||
("0E5806170758131F0D1B10", "vuuvwuuvu"),
|
||||
("120E3D023E1C1A113C1A261E3934371C2C263E413E313A053C353F033E1D1A103F1C3C4C", "wwwuwvuvu"),
|
||||
]
|
||||
|
||||
# dump uvuwwwuwu implementation
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp11.dex"
|
||||
tmp.write_bytes(zf.read("classes11.dex"))
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
cap = False
|
||||
print("=== uvuwwwuwu implementation ===")
|
||||
for line in out.splitlines():
|
||||
if "uvuwwwuwu:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;" in line:
|
||||
cap = True
|
||||
if cap:
|
||||
print(line.encode("ascii", "replace").decode())
|
||||
if cap and "locals :" in line:
|
||||
break
|
||||
|
||||
print("\n=== try XOR decode (common pattern) ===")
|
||||
for hex_str, key in samples:
|
||||
data = bytes.fromhex(hex_str) if all(c in "0123456789ABCDEFabcdef" for c in hex_str) else hex_str.encode()
|
||||
# try simple xor with key bytes cycling
|
||||
kb = key.encode()
|
||||
dec = bytes(b ^ kb[i % len(kb)] for i, b in enumerate(data))
|
||||
try:
|
||||
txt = dec.decode("utf-8")
|
||||
except Exception:
|
||||
txt = dec.decode("latin1", errors="replace")
|
||||
print(hex_str[:20], "...", "->", repr(txt[:80]))
|
||||
100
reverse/scripts/dump_crypto_jni.py
Normal file
100
reverse/scripts/dump_crypto_jni.py
Normal file
@@ -0,0 +1,100 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Dump JNI/native methods for sdkutils crypto + register-related classes."""
|
||||
import re
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
|
||||
OUT = Path(__file__).resolve().parent.parent / "output" / "crypto_jni_targets.txt"
|
||||
|
||||
CLASS_PATTERNS = [
|
||||
rb"Lcom/shopee/bke/lib/jni/[^;]{1,80};",
|
||||
rb"Lcom/shopee/shpssdkbank/wvvvuwwu;",
|
||||
rb"Lcom/shopee/shpssdkbank/uwuvuvvww/vvuuuuvvv;",
|
||||
rb"Lcom/shopee/shpssdkbank/uwuvuvvww/[^;]{1,40};",
|
||||
]
|
||||
|
||||
EXTRA_KEYWORDS = (
|
||||
b"NativeEncrypt",
|
||||
b"CharacterCrypto",
|
||||
b"SecurityMain",
|
||||
b"encrypt",
|
||||
b"decrypt",
|
||||
b"register",
|
||||
)
|
||||
|
||||
|
||||
def dump_class(out: str, target: str) -> List[str]:
|
||||
lines: list[str] = []
|
||||
cap = False
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
cap = True
|
||||
elif cap and line.startswith(" Class descriptor") and target not in line:
|
||||
break
|
||||
if cap:
|
||||
lines.append(line)
|
||||
return lines
|
||||
|
||||
|
||||
def main() -> None:
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
|
||||
classes: set[str] = set()
|
||||
for pat in CLASS_PATTERNS:
|
||||
for m in re.finditer(pat, data):
|
||||
classes.add(m.group().decode()[1:-1].replace("/", "."))
|
||||
|
||||
for kw in EXTRA_KEYWORDS:
|
||||
for m in re.finditer(rb"Lcom/[^;]{0,120};", data):
|
||||
s = m.group()
|
||||
if kw in s or kw in data[data.find(s) : data.find(s) + 4000]:
|
||||
classes.add(s.decode()[1:-1].replace("/", "."))
|
||||
|
||||
# log tags -> classes from crash log
|
||||
for tag in [
|
||||
"com.shopee.bke.lib.jni.utils.f", # SoUtils
|
||||
"com.shopee.bke.lib.jni.uwuwuwuw",
|
||||
"com.shopee.bke.lib.jni.uvuvuvuv",
|
||||
"com.shopee.bke.lib.jni.uvwwwwuv",
|
||||
]:
|
||||
classes.add(tag)
|
||||
|
||||
native_entries: List[str] = []
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for dex_name in zf.namelist():
|
||||
if not dex_name.endswith(".dex"):
|
||||
continue
|
||||
dex = zf.read(dex_name)
|
||||
hits = [c for c in classes if c.replace(".", "/") in dex.decode("latin1", errors="ignore")]
|
||||
if not hits:
|
||||
continue
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp_crypto_jni.dex"
|
||||
tmp.write_bytes(dex)
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
for cls in sorted(hits):
|
||||
block = dump_class(out, "L" + cls.replace(".", "/") + ";")
|
||||
if not block:
|
||||
continue
|
||||
native_entries.append(f"\n=== {dex_name} {cls} ===")
|
||||
for line in block:
|
||||
if any(
|
||||
k in line
|
||||
for k in ("NATIVE", "name :", "type :", "loadLibrary")
|
||||
):
|
||||
native_entries.append(line)
|
||||
|
||||
text = "\n".join(native_entries)
|
||||
OUT.write_text(text, encoding="utf-8")
|
||||
print(text)
|
||||
print(f"\nwritten {OUT}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
31
reverse/scripts/dump_error_handler.py
Normal file
31
reverse/scripts/dump_error_handler.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
|
||||
targets = [
|
||||
"Lcom/shopee/bke/biz/user/errorcodehandler/b;",
|
||||
"Lcom/shopee/bke/biz/user/errorcodehandler/b$a;",
|
||||
"Lcom/shopee/bke/biz/user/errorcodehandler/a;",
|
||||
]
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp6.dex"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
tmp.write_bytes(zf.read("classes6.dex"))
|
||||
out = subprocess.check_output([str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace")
|
||||
for target in targets:
|
||||
print("=" * 60, target)
|
||||
capture = False
|
||||
lines = 0
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
capture = True
|
||||
elif capture and line.startswith(" Class descriptor") and target not in line:
|
||||
break
|
||||
if capture:
|
||||
if "name :" in line or "type :" in line or "Class descriptor" in line:
|
||||
print(line.strip())
|
||||
lines += 1
|
||||
if lines > 200:
|
||||
break
|
||||
20
reverse/scripts/dump_error_iface.py
Normal file
20
reverse/scripts/dump_error_iface.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
|
||||
target = "Lcom/shopee/bke/biz/user/errorcodehandler/b$a;"
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp6.dex"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
tmp.write_bytes(zf.read("classes6.dex"))
|
||||
out = subprocess.check_output([str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace")
|
||||
capture = False
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
capture = True
|
||||
elif capture and line.startswith(" Class descriptor"):
|
||||
break
|
||||
if capture and ("name :" in line or "type :" in line or "Class descriptor" in line):
|
||||
print(line.encode("ascii", "replace").decode())
|
||||
23
reverse/scripts/dump_global_auth.py
Normal file
23
reverse/scripts/dump_global_auth.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp6.dex"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
tmp.write_bytes(zf.read("classes6.dex"))
|
||||
out = subprocess.check_output([str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace")
|
||||
cls = "GlobalAuthErrorImpl"
|
||||
capture = False
|
||||
for line in out.splitlines():
|
||||
if f"Lcom/shopee/bke/digitalbank/container/user/errorcodehandler/{cls};" in line and "Class descriptor" in line:
|
||||
capture = True
|
||||
if capture:
|
||||
print(line)
|
||||
if line.strip() == "" and "Method" not in line and capture:
|
||||
pass
|
||||
if capture and line.startswith(" Class descriptor") and cls not in line:
|
||||
break
|
||||
52
reverse/scripts/dump_key_methods.py
Normal file
52
reverse/scripts/dump_key_methods.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
|
||||
|
||||
methods = [
|
||||
("Lcom/shopee/shpssdkbank/SHPSSDK;", "getRiskToken"),
|
||||
("Lcom/shopee/shpssdkbank/wvvvuwwu;", "vvuwuuvuu"),
|
||||
("Lcom/shopee/shpssdkbank/wvvvuwwu;", "vuwuuwvw"),
|
||||
("Lcom/shopee/shpssdkbank/wvvvuwwu;", "vuwuuuwv"),
|
||||
]
|
||||
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp11.dex"
|
||||
tmp.write_bytes(zf.read("classes11.dex"))
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
|
||||
for cls, method in methods:
|
||||
needle = f"{cls.replace('L', '').replace(';', '').split('/')[-1]}.{method}:"
|
||||
print("\n" + "=" * 70, cls, method)
|
||||
cap = False
|
||||
for line in out.splitlines():
|
||||
if needle in line.replace("com.shopee.shpssdkbank.", ""):
|
||||
cap = True
|
||||
if cap:
|
||||
print(line.encode("ascii", "replace").decode())
|
||||
if line.strip().startswith("catches") or (cap and line.strip() == "locals :"):
|
||||
pass
|
||||
if cap and line.strip() == "" and "positions" in line:
|
||||
break
|
||||
if cap and line.startswith(" name") and method not in line and cap:
|
||||
# next method
|
||||
if methods.index((cls, method)) < len(methods) - 1:
|
||||
break
|
||||
|
||||
# classes8 - sdkutils / crypto
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp8.dex"
|
||||
tmp.write_bytes(zf.read("classes8.dex"))
|
||||
out8 = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
for tag in ["CharacterCrypto", "SoUtils", "SecurityMain", "sdkutils", "encrypt"]:
|
||||
print("\n--- search", tag, "in classes8 ---")
|
||||
for line in out8.splitlines():
|
||||
if tag.lower() in line.lower() and ("Class descriptor" in line or "name :" in line):
|
||||
print(line.encode("ascii", "replace").decode()[:200])
|
||||
62
reverse/scripts/dump_native_bridge.py
Normal file
62
reverse/scripts/dump_native_bridge.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
|
||||
|
||||
targets = [
|
||||
"Lcom/shopee/shpssdkbank/wvvvuwwu;",
|
||||
"Lcom/shopee/shpssdkbank/uwuvuvvww/wvvuuwvwu;",
|
||||
"Lcom/shopee/shpssdkbank/uwuvuvvww/uvwuuuuuw/vvvvuwwvu;",
|
||||
"Lcom/shopee/shpssdkbank/SHPSSDK;",
|
||||
"Lcom/shopee/shpssdkbank/ShpssInstall;",
|
||||
]
|
||||
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for dex_name in zf.namelist():
|
||||
if not dex_name.endswith(".dex"):
|
||||
continue
|
||||
dex = zf.read(dex_name)
|
||||
hit = any(t.replace("L", "").replace(";", "") in dex.decode("latin1", errors="ignore") for t in targets)
|
||||
if not hit:
|
||||
continue
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp_native.dex"
|
||||
tmp.write_bytes(dex)
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
for target in targets:
|
||||
if target.replace("L", "").replace(";", "") not in dex.decode("latin1", errors="ignore"):
|
||||
continue
|
||||
print("\n" + "=" * 70)
|
||||
print(dex_name, target)
|
||||
cap = False
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
cap = True
|
||||
elif cap and line.startswith(" Class descriptor") and target not in line:
|
||||
break
|
||||
if cap:
|
||||
safe = line.encode("ascii", "replace").decode()
|
||||
if any(k in safe for k in ["name", "type", "access", "NATIVE", "Method", "loadLibrary", "register"]):
|
||||
print(safe)
|
||||
|
||||
# find sdkutils / crypto manager classes
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
print("\n--- sdkutils / crypto / dfp classes ---")
|
||||
for pat in [
|
||||
rb"Lcom/[^;]{0,80}sdkutils[^;]{0,20};",
|
||||
rb"Lcom/[^;]{0,80}[Cc]rypto[^;]{0,40};",
|
||||
rb"Lcom/[^;]{0,80}dfp[^;]{0,30};",
|
||||
rb"Lcom/[^;]{0,80}SecurityMain[^;]{0,20};",
|
||||
]:
|
||||
found = set()
|
||||
for m in re.finditer(pat, data):
|
||||
s = m.group().decode()[1:-1].replace("/", ".")
|
||||
if s not in found:
|
||||
found.add(s)
|
||||
print(s)
|
||||
38
reverse/scripts/dump_phone_register_vm.py
Normal file
38
reverse/scripts/dump_phone_register_vm.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
|
||||
targets = [
|
||||
"Lcom/shopee/bke/biz/user/viewmodel/PhoneNumViewModel;",
|
||||
"Lcom/shopee/bke/biz/user/viewmodel/RegisterViewModel;",
|
||||
]
|
||||
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for name in zf.namelist():
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
if b"PhoneNumViewModel" not in data and b"RegisterViewModel" not in data:
|
||||
continue
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp_user.dex"
|
||||
tmp.write_bytes(data)
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
for target in targets:
|
||||
if target.replace("L", "").replace(";", "") not in data.decode("latin1", errors="ignore"):
|
||||
continue
|
||||
print("=" * 60, name, target)
|
||||
cap = False
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
cap = True
|
||||
elif cap and line.startswith(" Class descriptor") and target not in line:
|
||||
break
|
||||
if cap and ("name :" in line or "type :" in line
|
||||
or "Method" in line or "register" in line.lower()):
|
||||
safe = line.strip().encode("ascii", "replace").decode()
|
||||
print(safe)
|
||||
35
reverse/scripts/dump_register_request.py
Normal file
35
reverse/scripts/dump_register_request.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
|
||||
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for m in re.finditer(rb"Lcom/shopee/bke/[^;]{0,80}RegisterRequest[^;]{0,20};", data):
|
||||
print(m.group().decode()[1:-1].replace("/", "."))
|
||||
|
||||
for name in zf.namelist():
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
dex = zf.read(name)
|
||||
if b"RegisterRequest" not in dex:
|
||||
continue
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp_reg.dex"
|
||||
tmp.write_bytes(dex)
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
for target in re.findall(r"Lcom/shopee/bke/[^;]*RegisterRequest[^;]*;", data.decode("latin1", errors="ignore")):
|
||||
cap = False
|
||||
print("\n" + "=" * 60, name, target)
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
cap = True
|
||||
elif cap and line.startswith(" Class descriptor") and target not in line:
|
||||
break
|
||||
if cap:
|
||||
print(line)
|
||||
35
reverse/scripts/dump_register_vm.py
Normal file
35
reverse/scripts/dump_register_vm.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
|
||||
targets = [
|
||||
"Lcom/shopee/bke/biz/user/viewmodel/RegisterViewModel;",
|
||||
"Lcom/shopee/bke/biz/user/ui/PhoneNumActivity;",
|
||||
]
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for name in zf.namelist():
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
if b"RegisterViewModel" not in data:
|
||||
continue
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp_reg.dex"
|
||||
tmp.write_bytes(data)
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
for target in targets:
|
||||
if target.replace("L", "").replace(";", "") not in data.decode("latin1", errors="ignore"):
|
||||
continue
|
||||
print("=" * 60, name, target)
|
||||
cap = False
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
cap = True
|
||||
elif cap and line.startswith(" Class descriptor") and target not in line:
|
||||
break
|
||||
if cap and ("name :" in line or "type :" in line):
|
||||
print(line.strip().encode("ascii", "replace").decode())
|
||||
66
reverse/scripts/dump_risk_register_chain.py
Normal file
66
reverse/scripts/dump_risk_register_chain.py
Normal file
@@ -0,0 +1,66 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Dump riskToken + requestDefense + register crypto call chain."""
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
|
||||
|
||||
TARGETS = [
|
||||
("classes11.dex", "Lcom/shopee/shpssdkbank/SHPSSDK;", "getRiskToken"),
|
||||
("classes11.dex", "Lcom/shopee/shpssdkbank/SHPSSDK;", "requestDefense"),
|
||||
("classes11.dex", "Lcom/shopee/shpssdkbank/uwuvuvvww/vvuuuuvvv;", "wwvuwuwvu"),
|
||||
("classes11.dex", "Lcom/shopee/shpssdkbank/uwuvuvvww/vvuuuuvvv;", "wuvwuvwwu"),
|
||||
("classes11.dex", "Lcom/shopee/shpssdkbank/wvvvuwwu;", "wwvwvwuvv"),
|
||||
("classes11.dex", "Lcom/shopee/shpssdkbank/wvvvuwwu;", "vvuwuuvuu"),
|
||||
]
|
||||
|
||||
def dump_method(out, cls, method):
|
||||
cls_short = cls.replace("L", "").replace(";", "").replace("/", ".")
|
||||
needle = cls_short + "." + method + ":"
|
||||
print("\n" + "=" * 72)
|
||||
print(cls_short, method)
|
||||
cap = False
|
||||
lines = []
|
||||
for line in out.splitlines():
|
||||
if needle in line:
|
||||
cap = True
|
||||
if cap:
|
||||
lines.append(line)
|
||||
if len(lines) > 1 and line.strip().startswith("name :") and method not in line:
|
||||
break
|
||||
for line in lines[:80]:
|
||||
print(line.encode("ascii", "replace").decode())
|
||||
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for dex_name, cls, method in TARGETS:
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "_tmp.dex"
|
||||
tmp.write_bytes(zf.read(dex_name))
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
dump_method(out, cls, method)
|
||||
|
||||
# classes8 CharacterCrypto / SoUtils
|
||||
print("\n" + "=" * 72, "classes8 crypto classes")
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "_tmp8.dex"
|
||||
tmp.write_bytes(zf.read("classes8.dex"))
|
||||
out8 = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp8)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
cap = False
|
||||
for line in out8.splitlines():
|
||||
if "Class descriptor" in line and (
|
||||
"CharacterCrypto" in line or "SoUtils" in line or "sdkutils" in line.lower()
|
||||
):
|
||||
print("\n---", line.strip())
|
||||
cap = True
|
||||
continue
|
||||
if cap:
|
||||
if line.startswith(" Class descriptor") and "CharacterCrypto" not in line:
|
||||
cap = False
|
||||
continue
|
||||
if "name :" in line or "NATIVE" in line or "loadLibrary" in line:
|
||||
print(line.strip()[:180])
|
||||
54
reverse/scripts/dump_safemode_b.py
Normal file
54
reverse/scripts/dump_safemode_b.py
Normal file
@@ -0,0 +1,54 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import subprocess
|
||||
import sys
|
||||
import zipfile
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
TARGET = "com/shopee/bke/lib/safemode/b;"
|
||||
APK = sys.argv[1] if len(sys.argv) > 1 else "reverse/apks/seabank_ph_base.apk"
|
||||
BT = None
|
||||
|
||||
|
||||
def find_build_tools():
|
||||
base = os.environ.get("ANDROID_HOME") or os.path.expanduser(
|
||||
r"~\AppData\Local\Android\Sdk"
|
||||
)
|
||||
tools = os.path.join(base, "build-tools")
|
||||
versions = sorted(os.listdir(tools), reverse=True)
|
||||
return os.path.join(tools, versions[0], "dexdump.exe")
|
||||
|
||||
|
||||
def main():
|
||||
dexdump = find_build_tools()
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
for name in zf.namelist():
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
if TARGET.encode() not in data and b"Lcom/shopee/bke/lib/safemode/b;" not in data:
|
||||
continue
|
||||
print("FOUND in", name)
|
||||
tmp = tempfile.NamedTemporaryFile(suffix=".dex", delete=False)
|
||||
tmp.write(data)
|
||||
tmp.close()
|
||||
try:
|
||||
out = subprocess.check_output(
|
||||
[dexdump, "-f", tmp.name], stderr=subprocess.STDOUT, text=True,
|
||||
errors="ignore"
|
||||
)
|
||||
capture = False
|
||||
for line in out.splitlines():
|
||||
if "Class descriptor : 'Lcom/shopee/bke/lib/safemode/b;'" in line:
|
||||
capture = True
|
||||
elif capture and line.startswith(" Class descriptor"):
|
||||
break
|
||||
if capture:
|
||||
if "name :" in line or "type :" in line or "Class descriptor" in line:
|
||||
print(line)
|
||||
finally:
|
||||
os.unlink(tmp.name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
20
reverse/scripts/dump_shps_bank.py
Normal file
20
reverse/scripts/dump_shps_bank.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
|
||||
target = "Lcom/shopee/shpssdkbank/SHPSSDK;"
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp11.dex"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
tmp.write_bytes(zf.read("classes11.dex"))
|
||||
out = subprocess.check_output([str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace")
|
||||
capture = False
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
capture = True
|
||||
elif capture and line.startswith(" Class descriptor"):
|
||||
break
|
||||
if capture and ("name :" in line or "type :" in line or "access :" in line):
|
||||
print(line.encode("ascii", "replace").decode())
|
||||
21
reverse/scripts/dump_shps_bank_full.py
Normal file
21
reverse/scripts/dump_shps_bank_full.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
|
||||
target = "Lcom/shopee/shpssdkbank/SHPSSDK;"
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp11.dex"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
tmp.write_bytes(zf.read("classes11.dex"))
|
||||
out = subprocess.check_output([str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace")
|
||||
capture = False
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
capture = True
|
||||
elif capture and line.startswith(" Class descriptor"):
|
||||
break
|
||||
if capture:
|
||||
print(line.encode("ascii", "replace").decode())
|
||||
32
reverse/scripts/dump_shps_install.py
Normal file
32
reverse/scripts/dump_shps_install.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
|
||||
targets = [
|
||||
"Lcom/shopee/shpssdkbank/ShpssInstall;",
|
||||
"Lcom/shopee/shpssdkbank/vuvuwwwuw;",
|
||||
"Lcom/shopee/shpssdkbank/vwuuwwvwv;",
|
||||
"Lcom/shopee/shpssdkbank/uvuwwuvwv/uvwwuuvvw;",
|
||||
]
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp11.dex"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
tmp.write_bytes(zf.read("classes11.dex"))
|
||||
out = subprocess.check_output([str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace")
|
||||
for target in targets:
|
||||
print("=" * 60, target)
|
||||
cap = False
|
||||
n = 0
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
cap = True
|
||||
elif cap and line.startswith(" Class descriptor"):
|
||||
break
|
||||
if cap:
|
||||
print(line.encode("ascii", "replace").decode())
|
||||
n += 1
|
||||
if n > 80:
|
||||
print("...truncated...")
|
||||
break
|
||||
20
reverse/scripts/dump_shps_methods.py
Normal file
20
reverse/scripts/dump_shps_methods.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
|
||||
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp11.dex"
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
tmp.write_bytes(zf.read("classes11.dex"))
|
||||
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
for line in out.splitlines():
|
||||
if "SHPSSDK;" in line and any(
|
||||
k in line for k in ("getRisk", "assessRisk", "getRiskToken", "getExtRisk")
|
||||
):
|
||||
print(line.strip())
|
||||
19
reverse/scripts/dump_shps_native_methods.py
Normal file
19
reverse/scripts/dump_shps_native_methods.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp11.dex"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
tmp.write_bytes(zf.read("classes11.dex"))
|
||||
out = subprocess.check_output([str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace")
|
||||
in_shps = False
|
||||
for line in out.splitlines():
|
||||
if "Class descriptor : 'Lcom/shopee/shpssdk" in line:
|
||||
in_shps = "SHPSSDK;" in line or "shpssdkbank" in line
|
||||
if in_shps and line.startswith(" Class descriptor") and "shpssdk" not in line:
|
||||
break
|
||||
if in_shps and ("NATIVE" in line or "name :" in line):
|
||||
print(line.strip())
|
||||
22
reverse/scripts/extract_all_so.py
Normal file
22
reverse/scripts/extract_all_so.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
SPLIT = Path(__file__).resolve().parent.parent / "extracted" / "native" / "split_config.arm64_v8a.apk"
|
||||
if not SPLIT.exists():
|
||||
native_dir = SPLIT.parent
|
||||
candidates = list(native_dir.glob("split_config.arm64*.apk"))
|
||||
if not candidates:
|
||||
print("missing split APK at", SPLIT, file=sys.stderr)
|
||||
print("run: reverse/frida/pull_split_apk.ps1", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
SPLIT = candidates[0]
|
||||
|
||||
OUT = SPLIT.parent
|
||||
with zipfile.ZipFile(str(SPLIT)) as zf:
|
||||
for name in zf.namelist():
|
||||
if name.endswith(".so"):
|
||||
path = OUT / Path(name).name
|
||||
path.write_bytes(zf.read(name))
|
||||
print(path.name, path.stat().st_size)
|
||||
13
reverse/scripts/extract_from_split.py
Normal file
13
reverse/scripts/extract_from_split.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
split = Path(__file__).resolve().parent.parent / "extracted" / "native" / "split_arm64.apk"
|
||||
out = split.parent
|
||||
with zipfile.ZipFile(str(split)) as zf:
|
||||
for name in zf.namelist():
|
||||
if "libshpssdk" in name:
|
||||
dest = out / Path(name).name
|
||||
dest.write_bytes(zf.read(name))
|
||||
print(dest, dest.stat().st_size)
|
||||
14
reverse/scripts/extract_native.py
Normal file
14
reverse/scripts/extract_native.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
OUT = Path(__file__).resolve().parent.parent / "extracted" / "native"
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for name in zf.namelist():
|
||||
if "libshpssdk" in name and name.endswith(".so"):
|
||||
path = OUT / Path(name).name
|
||||
path.write_bytes(zf.read(name))
|
||||
print("extracted", path, path.stat().st_size)
|
||||
14
reverse/scripts/extract_shps_so.py
Normal file
14
reverse/scripts/extract_shps_so.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
OUT = Path(__file__).resolve().parent.parent / "extracted" / "native"
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for name in zf.namelist():
|
||||
if "libshpssdk" in name and name.endswith(".so"):
|
||||
dest = OUT / Path(name).name
|
||||
dest.write_bytes(zf.read(name))
|
||||
print(dest, dest.stat().st_size)
|
||||
13
reverse/scripts/extract_split_so.py
Normal file
13
reverse/scripts/extract_split_so.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
SPLIT = Path(__file__).resolve().parent.parent / "extracted" / "native" / "split_config.arm64_v8a.apk"
|
||||
OUT = SPLIT.parent
|
||||
with zipfile.ZipFile(str(SPLIT)) as zf:
|
||||
for name in zf.namelist():
|
||||
if "libshpssdk" in name and name.endswith(".so"):
|
||||
path = OUT / Path(name).name
|
||||
path.write_bytes(zf.read(name))
|
||||
print("extracted", path, path.stat().st_size)
|
||||
20
reverse/scripts/find_blocked_msg.py
Normal file
20
reverse/scripts/find_blocked_msg.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
import glob
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
root = str(Path(__file__).resolve().parent.parent / "extracted" / "apk_extract")
|
||||
needle = "this service has been temporarily blocked"
|
||||
for fp in glob.glob(os.path.join(root, "**", "en.json"), recursive=True):
|
||||
try:
|
||||
with open(fp, encoding="utf-8") as f:
|
||||
d = json.load(f)
|
||||
if not isinstance(d, dict):
|
||||
continue
|
||||
for k, v in d.items():
|
||||
if isinstance(v, str) and needle in v.lower():
|
||||
print(fp)
|
||||
print(k, "->", v)
|
||||
except Exception:
|
||||
pass
|
||||
18
reverse/scripts/find_crypto_classes.py
Normal file
18
reverse/scripts/find_crypto_classes.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for dex_name in zf.namelist():
|
||||
if not dex_name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(dex_name)
|
||||
if b"CharacterCrypto" not in data and b"SoUtils" not in data:
|
||||
continue
|
||||
print("===", dex_name, "===")
|
||||
for m in re.finditer(rb"L[^;]{0,120};", data):
|
||||
s = m.group().decode("latin1", errors="replace")
|
||||
if "CharacterCrypto" in s or "SoUtils" in s or "SecurityMain" in s:
|
||||
print(s[1:-1].replace("/", "."))
|
||||
22
reverse/scripts/find_detection_classes.py
Normal file
22
reverse/scripts/find_detection_classes.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
for name in sorted(zf.namelist()):
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
found = set()
|
||||
for m in re.finditer(rb"L[a-zA-Z0-9_$/]+;", data):
|
||||
s = m.group().decode()[1:-1].replace("/", ".")
|
||||
low = s.lower()
|
||||
if any(k in low for k in ("rootdetect", "emulatordetect", "safemode", "risk", "integrity", "xposed", "hookdetect")):
|
||||
found.add(s)
|
||||
if "WBRoot" in s or "WBEmulator" in s:
|
||||
found.add(s)
|
||||
if found:
|
||||
print("=== %s ===" % name)
|
||||
for s in sorted(found):
|
||||
print(s)
|
||||
10
reverse/scripts/find_global_auth_dex.py
Normal file
10
reverse/scripts/find_global_auth_dex.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
needle = b"Lcom/shopee/bke/digitalbank/container/user/errorcodehandler/GlobalAuthErrorImpl;"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for name in zf.namelist():
|
||||
if name.endswith(".dex") and needle in zf.read(name):
|
||||
print("found in", name)
|
||||
16
reverse/scripts/find_iv_monitor.py
Normal file
16
reverse/scripts/find_iv_monitor.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
needles = [b"IV_Monitor", b"register scene", b"dfp is empty", b"CharacterCrypto"]
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for n in needles:
|
||||
print(n.decode(), data.count(n))
|
||||
idx = data.find(n)
|
||||
if idx >= 0:
|
||||
ctx = data[max(0, idx - 80): idx + 120]
|
||||
import re
|
||||
for m in re.finditer(rb"Lcom/[^;\x00]{5,120};", ctx):
|
||||
print(" class", m.group().decode()[1:-1].replace("/", "."))
|
||||
30
reverse/scripts/find_load_so_lib.py
Normal file
30
reverse/scripts/find_load_so_lib.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
|
||||
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for dex_name in zf.namelist():
|
||||
if not dex_name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(dex_name)
|
||||
if b"loadSoLibrary" not in data:
|
||||
continue
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp_loadso.dex"
|
||||
tmp.write_bytes(data)
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
current_class = ""
|
||||
for line in out.splitlines():
|
||||
m = re.search(r"Class descriptor\s+:\s+'([^']+)'", line)
|
||||
if m:
|
||||
current_class = m.group(1)
|
||||
if "loadSoLibrary" not in line:
|
||||
continue
|
||||
cls = current_class.replace("L", "").replace(";", "").replace("/", ".")
|
||||
print(f"{dex_name}\t{cls}\t{line.strip()}")
|
||||
19
reverse/scripts/find_native_encrypt.py
Normal file
19
reverse/scripts/find_native_encrypt.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for kw in [b"NativeEncrypt", b"CharacterCrypto", b"IV_Monitor", b"register scene", b"dfp is empty"]:
|
||||
print(kw.decode(), data.count(kw))
|
||||
print("\n--- bke.lib.jni crypto/security ---")
|
||||
seen = set()
|
||||
for m in re.finditer(rb"Lcom/shopee/bke/lib/jni/[^;]{1,120};", data):
|
||||
s = m.group().decode()[1:-1].replace("/", ".")
|
||||
if s in seen:
|
||||
continue
|
||||
if any(x in s.lower() for x in ("crypto", "encrypt", "security", "native", "tee")):
|
||||
seen.add(s)
|
||||
print(s)
|
||||
37
reverse/scripts/find_native_encrypt_class.py
Normal file
37
reverse/scripts/find_native_encrypt_class.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
|
||||
needle = b"NativeEncrypt"
|
||||
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for dex_name in zf.namelist():
|
||||
if not dex_name.endswith(".dex"):
|
||||
continue
|
||||
dex = zf.read(dex_name)
|
||||
if needle not in dex:
|
||||
continue
|
||||
print("===", dex_name, "===")
|
||||
for m in re.finditer(rb"const-string[^/]*// string@[0-9a-f]+", dex):
|
||||
pass
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp_ne.dex"
|
||||
tmp.write_bytes(dex)
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
cap = False
|
||||
for line in out.splitlines():
|
||||
if "NativeEncrypt" in line:
|
||||
print(line.strip())
|
||||
if "Class descriptor" in line:
|
||||
current = line
|
||||
if "NativeEncrypt" in line:
|
||||
# print previous class context
|
||||
idx = out.splitlines().index(line)
|
||||
for prev in out.splitlines()[max(0, idx - 40) : idx + 5]:
|
||||
if "Class descriptor" in prev or "name :" in prev or "NATIVE" in prev:
|
||||
print(prev.strip())
|
||||
21
reverse/scripts/find_native_methods.py
Normal file
21
reverse/scripts/find_native_methods.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
split_apk = Path(__file__).resolve().parent.parent / "extracted" / "native" / "split_arm64.apk"
|
||||
base_apk = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
|
||||
apk = base_apk if base_apk.exists() else None
|
||||
if apk is None:
|
||||
# use device base if needed - skip
|
||||
import sys
|
||||
print("no base apk")
|
||||
sys.exit(0)
|
||||
with zipfile.ZipFile(str(apk)) as zf:
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp11.dex"
|
||||
tmp.write_bytes(zf.read("classes11.dex"))
|
||||
out = subprocess.check_output([str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace")
|
||||
for line in out.splitlines():
|
||||
if "0x0101" in line or "NATIVE" in line:
|
||||
print(line.strip())
|
||||
26
reverse/scripts/find_shps_sig.py
Normal file
26
reverse/scripts/find_shps_sig.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
needles = [
|
||||
b"getRiskTokenAsync",
|
||||
b"getRiskToken",
|
||||
b"getRiskSync",
|
||||
b"getRiskAsync",
|
||||
b"assessRisk",
|
||||
]
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for name in zf.namelist():
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
hits = [n.decode() for n in needles if n in data]
|
||||
if not hits:
|
||||
continue
|
||||
print(name, hits)
|
||||
for m in re.finditer(
|
||||
rb"Lcom/shopee/shpssdk(?:bank)?/SHPSSDK;\.(\w+):\([^)]+\)[^;]+;", data
|
||||
):
|
||||
print(" ", m.group().decode())
|
||||
17
reverse/scripts/find_wb_classes.py
Normal file
17
reverse/scripts/find_wb_classes.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
for name in zf.namelist():
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
if b"WBRootDetectionModule" not in data:
|
||||
continue
|
||||
print("===", name, "===")
|
||||
for m in re.finditer(rb"Lcom/shopee/[a-zA-Z0-9_$/]+;", data):
|
||||
s = m.group().decode()[1:-1].replace("/", ".")
|
||||
if "Root" in s or "Detect" in s or "Emulator" in s or "Safe" in s or "Risk" in s or "WB" in s:
|
||||
print(s)
|
||||
22
reverse/scripts/find_wb_root.py
Normal file
22
reverse/scripts/find_wb_root.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
needles = [b"WBRootDetectionModule", b"WBEmulatorDetectionModule", b"SPSAssessRisk", b"shpssdk"]
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
for name in zf.namelist():
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
hits = [n.decode() for n in needles if n in data]
|
||||
if hits:
|
||||
print(name, hits)
|
||||
for m in re.finditer(rb"L[a-zA-Z0-9_$/]*WBRootDetectionModule;", data):
|
||||
print(" ", m.group().decode()[1:-1].replace("/", "."))
|
||||
for m in re.finditer(rb"L[a-zA-Z0-9_$/]*SPSAssessRisk[^;]*;", data):
|
||||
print(" ", m.group().decode()[1:-1].replace("/", "."))
|
||||
for m in re.finditer(rb"Lcom/shopee/shpssdk[a-zA-Z0-9_$/]+;", data):
|
||||
s = m.group().decode()[1:-1].replace("/", ".")
|
||||
if "shpssdk" in s.lower():
|
||||
print(" ", s)
|
||||
16
reverse/scripts/list_methods_classes6.py
Normal file
16
reverse/scripts/list_methods_classes6.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
|
||||
data = open(r"C:\Users\Administrator\Desktop\notiMessage\reverse\extracted\classes6.dex", "rb").read()
|
||||
# method names in dex are plain utf8 strings
|
||||
methods = set(re.findall(rb"[a-zA-Z][a-zA-Z0-9_]{2,60}", data))
|
||||
interesting = sorted(
|
||||
m.decode("ascii", "ignore")
|
||||
for m in methods
|
||||
if any(k in m.lower() for k in (
|
||||
b"root", b"jail", b"safe", b"xposed", b"frida", b"emulator",
|
||||
b"integrity", b"detect", b"hook", b"debug", b"tamper", b"risk"
|
||||
))
|
||||
)
|
||||
for m in interesting:
|
||||
print(m)
|
||||
17
reverse/scripts/list_safemode.py
Normal file
17
reverse/scripts/list_safemode.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
|
||||
data = open(r"C:\Users\Administrator\Desktop\notiMessage\reverse\extracted\classes6.dex", "rb").read()
|
||||
classes = set()
|
||||
for m in re.finditer(rb"Lcom/shopee/bke/lib/safemode/[a-zA-Z0-9_$/]+;", data):
|
||||
s = m.group().decode()[1:-1].replace("/", ".")
|
||||
if "R$" in s:
|
||||
continue
|
||||
classes.add(s)
|
||||
for c in sorted(classes):
|
||||
print(c)
|
||||
|
||||
print("\n--- interesting strings ---")
|
||||
for pat in [b"isRoot", b"jailbroken", b"rooted", b"SafeMode", b"checkRoot", b"detect", b"xposed", b"frida", b"integrity"]:
|
||||
if pat in data:
|
||||
print(pat.decode(), "YES")
|
||||
9
reverse/scripts/list_sdkutils_jni.py
Normal file
9
reverse/scripts/list_sdkutils_jni.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
so = Path(__file__).resolve().parent.parent / "extracted" / "native" / "libsdkutils.so"
|
||||
data = so.read_bytes()
|
||||
for name in sorted(set(m.group().decode() for m in re.finditer(rb"Java_com_shopee_bke_lib_jni_[A-Za-z0-9_]+", data))):
|
||||
if "utils" in name or "encrypt" in name.lower():
|
||||
print(name)
|
||||
10
reverse/scripts/read_alc.py
Normal file
10
reverse/scripts/read_alc.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
for name in zf.namelist():
|
||||
if "alc" in name.lower() and name.endswith(".json"):
|
||||
print("===", name, "===")
|
||||
print(zf.read(name).decode("utf-8", "ignore")[:2000])
|
||||
print()
|
||||
11
reverse/scripts/scan_1201.py
Normal file
11
reverse/scripts/scan_1201.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
needles = [b"-1201", b"1201", b"ErrorCode", b"error has occurred"]
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for n in needles:
|
||||
idx = data.find(n)
|
||||
if idx >= 0:
|
||||
print(n.decode(), "at", idx, "context:", data[max(0,idx-40):idx+60])
|
||||
20
reverse/scripts/scan_4067_ctx.py
Normal file
20
reverse/scripts/scan_4067_ctx.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
needle = b"4067"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
idx = 0
|
||||
while True:
|
||||
idx = data.find(needle, idx)
|
||||
if idx < 0:
|
||||
break
|
||||
ctx = data[max(0, idx - 80): idx + 120]
|
||||
import re
|
||||
strings = [m.group().decode("latin1") for m in re.finditer(rb"[\x20-\x7e]{3,80}", ctx)]
|
||||
print("--- at", idx, "---")
|
||||
for s in strings:
|
||||
print(" ", s)
|
||||
idx += 1
|
||||
24
reverse/scripts/scan_common_dialog.py
Normal file
24
reverse/scripts/scan_common_dialog.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
targets = [
|
||||
b"Lcom/shopee/bke/lib/commonui/widget/CommonDialog;",
|
||||
b"Lcom/shopee/bke/lib/commonui/widget/CommonDialog$Builder;",
|
||||
]
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for t in targets:
|
||||
print("===", t.decode()[1:-1].replace("/", "."), "===")
|
||||
idx = 0
|
||||
c = 0
|
||||
while c < 5:
|
||||
idx = data.find(t, idx)
|
||||
if idx < 0:
|
||||
break
|
||||
ctx = data[max(0, idx - 100): idx + 300]
|
||||
for m in re.finditer(rb"(show|build|create|setMessage|setContent|display)[a-zA-Z0-9_$<>]*", ctx):
|
||||
print(" ", m.group().decode())
|
||||
idx += 1
|
||||
c += 1
|
||||
40
reverse/scripts/scan_crypto_flow.py
Normal file
40
reverse/scripts/scan_crypto_flow.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
|
||||
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for needle in [b"sdkutils", b"SoUtils", b"CharacterCrypto", b"vvuwuuvuu", b"vuwuuwvw", b"wwvwvwuvv", b"dfp is empty"]:
|
||||
print(needle.decode(), data.count(needle))
|
||||
|
||||
print("\n--- classes referencing sdkutils ---")
|
||||
for m in re.finditer(rb"Lcom/[^;]{0,120};", data):
|
||||
s = m.group()
|
||||
if b"sdkutils" in s.lower() or b"SoUtils" in s or b"Crypto" in s:
|
||||
print(s.decode()[1:-1].replace("/", "."))
|
||||
|
||||
# dump vvuuuuvvv.wwvuwuwvu (getRiskToken core)
|
||||
target = "Lcom/shopee/shpssdkbank/uwuvuvvww/vvuuuuvvv;"
|
||||
for dex_name in zf.namelist():
|
||||
if not dex_name.endswith(".dex"):
|
||||
continue
|
||||
dex = zf.read(dex_name)
|
||||
if target.encode() not in dex:
|
||||
continue
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp_vv.dex"
|
||||
tmp.write_bytes(dex)
|
||||
out = subprocess.check_output([str(DEXDUMP), "-d", str(tmp)], text=True, errors="replace")
|
||||
print("\n===", dex_name, "vvuuuuvvv methods (native only) ===")
|
||||
cap = False
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
cap = True
|
||||
elif cap and line.startswith(" Class descriptor"):
|
||||
break
|
||||
if cap and ("NATIVE" in line or "name :" in line):
|
||||
print(line.encode("ascii", "replace").decode())
|
||||
57
reverse/scripts/scan_crypto_register.py
Normal file
57
reverse/scripts/scan_crypto_register.py
Normal file
@@ -0,0 +1,57 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
|
||||
|
||||
needles = [
|
||||
b"CharacterCrypto",
|
||||
b"sdkutils",
|
||||
b"SoUtils",
|
||||
b"dfp is empty",
|
||||
b"register scene",
|
||||
b"deviceToken",
|
||||
b"getRiskToken",
|
||||
b"encrypt",
|
||||
b"decrypt",
|
||||
b"/uapi/v2/register",
|
||||
]
|
||||
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for n in needles:
|
||||
print(n.decode(), data.count(n))
|
||||
|
||||
print("\n--- CharacterCrypto classes ---")
|
||||
for m in re.finditer(rb"L[^;]{0,100}CharacterCrypto[^;]{0,40};", data):
|
||||
print(m.group().decode()[1:-1].replace("/", "."))
|
||||
|
||||
print("\n--- SoUtils classes ---")
|
||||
for m in re.finditer(rb"L[^;]{0,80}SoUtils[^;]{0,20};", data):
|
||||
print(m.group().decode()[1:-1].replace("/", "."))
|
||||
|
||||
print("\n--- native methods in shpssdkbank ---")
|
||||
for dex_name in zf.namelist():
|
||||
if not dex_name.endswith(".dex"):
|
||||
continue
|
||||
dex = zf.read(dex_name)
|
||||
if b"shpssdkbank" not in dex:
|
||||
continue
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp_shps.dex"
|
||||
tmp.write_bytes(dex)
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
cap = False
|
||||
cls = ""
|
||||
for line in out.splitlines():
|
||||
if "Class descriptor : 'Lcom/shopee/shpssdkbank/" in line:
|
||||
cap = True
|
||||
cls = line.split("'")[1]
|
||||
elif cap and line.startswith(" Class descriptor") and "shpssdkbank" not in line:
|
||||
cap = False
|
||||
if cap and ("0x0101" in line or "NATIVE" in line):
|
||||
print(cls.replace("L", "").replace(";", "").replace("/", "."), line.strip())
|
||||
24
reverse/scripts/scan_detail.py
Normal file
24
reverse/scripts/scan_detail.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os, re, sys
|
||||
|
||||
def scan_dir(base, pat):
|
||||
rx = re.compile(pat, re.I)
|
||||
all_m = set()
|
||||
for dex in sorted(os.listdir(base)):
|
||||
if not dex.endswith('.dex'):
|
||||
continue
|
||||
data = open(os.path.join(base, dex), 'rb').read()
|
||||
strs = set(m.group().decode('ascii', 'ignore') for m in re.finditer(rb'[\x20-\x7e]{5,}', data))
|
||||
all_m |= {s for s in strs if rx.search(s)}
|
||||
return sorted(all_m)
|
||||
|
||||
apps = {
|
||||
'up': (r'(HandlerService|NotificationHandler|showNotification|RemoteMessage|Util\$NotificationType|processPush|handleMessage|up/money/notifications)'),
|
||||
'suncorp': (r'SuncorpMessagingService|onMessageReceived|showNotification|NotificationDetails|pushNotification|FirebaseService'),
|
||||
'ubank': (r'MoEFireBase|MessagingService|onMessageReceived|showNotification|Will try to show|bank86400|MoEngage'),
|
||||
}
|
||||
root = sys.argv[1]
|
||||
for app, pat in apps.items():
|
||||
print('\n====', app, '====')
|
||||
for s in scan_dir(os.path.join(root, app), pat)[:60]:
|
||||
print(s)
|
||||
91
reverse/scripts/scan_dex.py
Normal file
91
reverse/scripts/scan_dex.py
Normal file
@@ -0,0 +1,91 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
PATTERNS = [
|
||||
r'FirebaseMessagingService',
|
||||
r'onMessageReceived',
|
||||
r'NotificationManager',
|
||||
r'NotificationCompat',
|
||||
r'NotificationChannel',
|
||||
r'PushNotification',
|
||||
r'PushMessage',
|
||||
r'Transaction',
|
||||
r'transaction',
|
||||
r'InboxMessage',
|
||||
r'AlertMessage',
|
||||
r'showNotification',
|
||||
r'postNotification',
|
||||
r'NotificationReceiver',
|
||||
r'FCM',
|
||||
r'FirebaseMessaging',
|
||||
r'MessagingService',
|
||||
r'PaymentNotification',
|
||||
r'TransferNotification',
|
||||
r'BankNotification',
|
||||
]
|
||||
|
||||
CLASS_LIKE = re.compile(r'[A-Za-z][\w$/]{3,120}')
|
||||
|
||||
|
||||
def extract_strings(data, min_len=4):
|
||||
out = set()
|
||||
for m in re.finditer(rb'[\x20-\x7e]{' + str(min_len).encode() + rb',}', data):
|
||||
try:
|
||||
out.add(m.group().decode('ascii'))
|
||||
except Exception:
|
||||
pass
|
||||
return out
|
||||
|
||||
|
||||
def scan_file(path):
|
||||
with open(path, 'rb') as f:
|
||||
data = f.read()
|
||||
strings = extract_strings(data, 5)
|
||||
hits = {}
|
||||
for pat in PATTERNS:
|
||||
rx = re.compile(pat, re.I)
|
||||
matched = sorted({s for s in strings if rx.search(s)})
|
||||
if matched:
|
||||
hits[pat] = matched[:40]
|
||||
# interesting fully-qualified class names
|
||||
fqcn = sorted({
|
||||
s for s in strings
|
||||
if ('/' in s or s.startswith('L')) and any(k in s.lower() for k in (
|
||||
'notif', 'push', 'fcm', 'firebase', 'message', 'transaction', 'alert', 'inbox', 'payment', 'transfer'
|
||||
))
|
||||
})
|
||||
return hits, fqcn[:80]
|
||||
|
||||
|
||||
def main(root):
|
||||
for app in sorted(os.listdir(root)):
|
||||
app_dir = os.path.join(root, app)
|
||||
if not os.path.isdir(app_dir):
|
||||
continue
|
||||
print('\n' + '=' * 70)
|
||||
print('APP:', app)
|
||||
print('=' * 70)
|
||||
dex_files = [f for f in os.listdir(app_dir) if f.endswith('.dex')]
|
||||
all_hits = {}
|
||||
all_fqcn = set()
|
||||
for dex in sorted(dex_files):
|
||||
path = os.path.join(app_dir, dex)
|
||||
hits, fqcn = scan_file(path)
|
||||
for k, v in hits.items():
|
||||
all_hits.setdefault(k, set()).update(v)
|
||||
all_fqcn.update(fqcn)
|
||||
for pat in PATTERNS:
|
||||
if pat in all_hits:
|
||||
print('\n[%s]' % pat)
|
||||
for s in sorted(all_hits[pat])[:25]:
|
||||
print(' ', s)
|
||||
print('\n[interesting class-like strings]')
|
||||
for s in sorted(all_fqcn)[:60]:
|
||||
print(' ', s)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
root = sys.argv[1] if len(sys.argv) > 1 else 'extracted'
|
||||
main(root)
|
||||
11
reverse/scripts/scan_error_classes.py
Normal file
11
reverse/scripts/scan_error_classes.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for m in re.finditer(rb"Lcom/shopee/[a-zA-Z0-9_$/]*(error|Error|dialog|Dialog|Risk|risk|Otp|otp|Phone|Register)[a-zA-Z0-9_$/]*;", data):
|
||||
s = m.group().decode()[1:-1].replace("/", ".")
|
||||
if "bke" in s or "seabank" in s or "shpssdk" in s:
|
||||
print(s)
|
||||
28
reverse/scripts/scan_error_handler.py
Normal file
28
reverse/scripts/scan_error_handler.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import zipfile
|
||||
import struct
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
targets = [
|
||||
b"Lcom/shopee/bke/biz/user/errorcodehandler/a;",
|
||||
b"Lcom/shopee/bke/biz/user/errorcodehandler/b;",
|
||||
b"Lcom/shopee/shpssdkbank/SPSAssessRisk;",
|
||||
b"Lcom/shopee/bke/biz/user/rn/helper/ErrorFlowHelper;",
|
||||
]
|
||||
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for t in targets:
|
||||
print("===", t.decode()[1:-1].replace("/", "."), "===")
|
||||
idx = 0
|
||||
while True:
|
||||
idx = data.find(t, idx)
|
||||
if idx < 0:
|
||||
break
|
||||
ctx = data[max(0, idx - 200): idx + 400]
|
||||
# crude string extraction nearby
|
||||
for m in __import__("re").finditer(rb"[\x20-\x7e]{4,80}", ctx):
|
||||
s = m.group().decode()
|
||||
if any(k in s.lower() for k in ["1201", "error", "kill", "finish", "risk", "root", "code", "handle"]):
|
||||
print(" ", s)
|
||||
idx += 1
|
||||
13
reverse/scripts/scan_error_strings.py
Normal file
13
reverse/scripts/scan_error_strings.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
needles = [b"An error has occurred", b"-1201", b"8424 8050", b"Error --", b"killProcess", b"finishAffinity"]
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for n in needles:
|
||||
idx = data.find(n)
|
||||
if idx >= 0:
|
||||
ctx = re.sub(rb"[^\x20-\x7e]+", b" ", data[max(0, idx - 60): idx + len(n) + 80])
|
||||
print(n.decode(), "->", ctx.decode())
|
||||
17
reverse/scripts/scan_json_lib.py
Normal file
17
reverse/scripts/scan_json_lib.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
libs = [
|
||||
b"com/google/gson/Gson",
|
||||
b"com/fasterxml/jackson",
|
||||
b"com/alibaba/fastjson",
|
||||
b"org/json/JSONObject",
|
||||
b"okhttp3/RequestBody",
|
||||
b"retrofit2/",
|
||||
]
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for lib in libs:
|
||||
print(lib.decode(), data.count(lib))
|
||||
20
reverse/scripts/scan_method_hints.py
Normal file
20
reverse/scripts/scan_method_hints.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
METHOD_HINTS = [
|
||||
b"isRoot", b"isRooted", b"jailbroken", b"checkRoot", b"detectRoot",
|
||||
b"detectXposed", b"checkXposed", b"isXposed", b"checkFrida", b"isEmulator",
|
||||
b"checkIntegrity", b"SafeMode", b"needSafeMode", b"enterSafeMode",
|
||||
b"showRoot", b"rooted", b"factory settings",
|
||||
]
|
||||
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
for name in sorted(zf.namelist()):
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
hits = [h.decode("ascii", "ignore") for h in METHOD_HINTS if h in data]
|
||||
if hits:
|
||||
print(name, ":", ", ".join(hits))
|
||||
28
reverse/scripts/scan_needle_context.py
Normal file
28
reverse/scripts/scan_needle_context.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
TARGETS = [b"isXposed", b"isRooted", b"checkRoot", b"isRoot", b"isEmulator", b"jailbroken"]
|
||||
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
for dex_name in ["classes6.dex", "classes9.dex", "classes15.dex", "classes3.dex"]:
|
||||
data = zf.read(dex_name)
|
||||
print("=== %s ===" % dex_name)
|
||||
for needle in TARGETS:
|
||||
if needle not in data:
|
||||
continue
|
||||
idx = 0
|
||||
shown = 0
|
||||
while shown < 8:
|
||||
i = data.find(needle, idx)
|
||||
if i < 0:
|
||||
break
|
||||
s = max(0, i - 80)
|
||||
e = min(len(data), i + 80)
|
||||
chunk = re.sub(rb"[^\x20-\x7e]+", b" ", data[s:e]).decode("ascii", "ignore")
|
||||
if "shopee" in chunk.lower() or "seabank" in chunk.lower() or "bke" in chunk.lower() or "alc" in chunk.lower():
|
||||
print(" ", needle.decode(), "->", chunk.strip())
|
||||
shown += 1
|
||||
idx = i + 1
|
||||
print()
|
||||
28
reverse/scripts/scan_register_api.py
Normal file
28
reverse/scripts/scan_register_api.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
needles = [
|
||||
b"GlobalAuthErrorImpl",
|
||||
b"errorcodehandler",
|
||||
b"sendOtp",
|
||||
b"register",
|
||||
b"verifyMobile",
|
||||
b"mobile/register",
|
||||
b"preRegister",
|
||||
b"riskToken",
|
||||
b"risk_token",
|
||||
b"8424 8050",
|
||||
b"-1201",
|
||||
]
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for n in needles:
|
||||
print(n.decode(), data.count(n))
|
||||
print("\n--- urls ---")
|
||||
for m in re.finditer(rb"https?://[a-zA-Z0-9._/-]{8,120}", data):
|
||||
u = m.group().decode()
|
||||
if "seabank" in u or "register" in u or "otp" in u or "mobile" in u:
|
||||
print(u)
|
||||
24
reverse/scripts/scan_register_class.py
Normal file
24
reverse/scripts/scan_register_class.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
needles = [
|
||||
b"v2/register",
|
||||
b"/register",
|
||||
b"RegisterRequest",
|
||||
b"registerPhone",
|
||||
b"signUp",
|
||||
b"preRegister",
|
||||
b"riskToken",
|
||||
]
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for n in needles:
|
||||
c = data.count(n)
|
||||
if c:
|
||||
print(n.decode(), c)
|
||||
print("\n--- classes near register ---")
|
||||
for m in re.finditer(rb"Lcom/shopee/bke/[^;]{0,120}register[^;]{0,40};", data, re.I):
|
||||
print(m.group().decode()[1:-1].replace("/", "."))
|
||||
15
reverse/scripts/scan_register_endpoint.py
Normal file
15
reverse/scripts/scan_register_endpoint.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
needle = b"uapi/v2/register"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
idx = data.find(needle)
|
||||
print("found at", idx)
|
||||
if idx >= 0:
|
||||
ctx = data[max(0, idx - 400): idx + 400]
|
||||
for m in re.finditer(rb"[\x20-\x7e]{4,120}", ctx):
|
||||
print(" ", m.group().decode("latin1"))
|
||||
29
reverse/scripts/scan_register_paths.py
Normal file
29
reverse/scripts/scan_register_paths.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
needles = [
|
||||
b"mobile",
|
||||
b"register",
|
||||
b"otp",
|
||||
b"signUp",
|
||||
b"signup",
|
||||
b"preCheck",
|
||||
b"checkMobile",
|
||||
b"sendSms",
|
||||
b"verifyPhone",
|
||||
b"4067004",
|
||||
b"4067",
|
||||
]
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for n in needles:
|
||||
if n in data:
|
||||
print("hit", n.decode())
|
||||
print("\n--- api paths ---")
|
||||
for m in re.finditer(rb"/v[0-9]/[a-zA-Z0-9_/-]{6,80}", data):
|
||||
s = m.group().decode()
|
||||
if any(k in s.lower() for k in ("user", "auth", "register", "mobile", "otp", "sign", "risk", "phone")):
|
||||
print(s)
|
||||
24
reverse/scripts/scan_risk_fields.py
Normal file
24
reverse/scripts/scan_risk_fields.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for needle in [b"riskToken", b"risk_token", b"deviceToken", b"secToken", b"shpsToken", b"mobileNo", b"phoneNo"]:
|
||||
print(needle.decode(), data.count(needle))
|
||||
print("\n--- context riskToken ---")
|
||||
idx = 0
|
||||
while True:
|
||||
idx = data.find(b"riskToken", idx)
|
||||
if idx < 0:
|
||||
break
|
||||
ctx = data[max(0, idx - 60): idx + 120]
|
||||
for m in re.finditer(rb"[\x20-\x7e]{3,60}", ctx):
|
||||
s = m.group().decode("latin1")
|
||||
if any(k in s.lower() for k in ["risk", "token", "mobile", "phone", "register", "device"]):
|
||||
print(" ", s)
|
||||
idx += 1
|
||||
if idx > 5000000:
|
||||
break
|
||||
19
reverse/scripts/scan_risk_token_ctx.py
Normal file
19
reverse/scripts/scan_risk_token_ctx.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
needle = b"riskToken"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for name in zf.namelist():
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
idx = data.find(needle)
|
||||
if idx < 0:
|
||||
continue
|
||||
ctx = data[max(0, idx - 300): idx + 300]
|
||||
print("===", name, "===")
|
||||
import re
|
||||
for m in re.finditer(rb"[\x20-\x7e]{4,100}", ctx):
|
||||
print(" ", m.group().decode("latin1"))
|
||||
42
reverse/scripts/scan_root.py
Normal file
42
reverse/scripts/scan_root.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
KEYS = [
|
||||
b"rooted", b"jailbroken", b"RootBeer", b"isRoot", b"checkRoot", b"detectRoot",
|
||||
b"SafetyNet", b"PlayIntegrity", b"magisk", b"/su", b"tamper", b"safemode",
|
||||
b"SafeMode", b"xposed", b"lsposed", b"frida", b"emulator", b"debuggable",
|
||||
b"Integrity", b"jailbreak", b"factory settings", b"RiskDevice", b"DeviceRisk",
|
||||
b"root device", b"seabank", b"SeaBank", b"MariBank", b"alc", b"ALC",
|
||||
]
|
||||
|
||||
|
||||
def scan_apk(apk_path):
|
||||
with zipfile.ZipFile(apk_path) as zf:
|
||||
for name in sorted(zf.namelist()):
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
print("=== %s (%d bytes) ===" % (name, len(data)))
|
||||
hits = set()
|
||||
for key in KEYS:
|
||||
start = 0
|
||||
while True:
|
||||
idx = data.find(key, start)
|
||||
if idx < 0:
|
||||
break
|
||||
s = max(0, idx - 40)
|
||||
e = min(len(data), idx + len(key) + 60)
|
||||
chunk = re.sub(rb"[^\x20-\x7e]+", b" ", data[s:e])
|
||||
chunk = chunk.decode("ascii", "ignore").strip()
|
||||
if len(chunk) > 8:
|
||||
hits.add(chunk)
|
||||
start = idx + 1
|
||||
for hit in sorted(hits):
|
||||
print(" ", hit)
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
scan_apk(sys.argv[1] if len(sys.argv) > 1 else "reverse/apks/seabank_ph_base.apk")
|
||||
19
reverse/scripts/scan_root_dialog.py
Normal file
19
reverse/scripts/scan_root_dialog.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
needles = [
|
||||
b"rooted or jailbroken",
|
||||
b"factory settings",
|
||||
b"cannot be accessed",
|
||||
b"bke_toast_not_support_root",
|
||||
b"not_support_root",
|
||||
]
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
for name in zf.namelist():
|
||||
if not (name.endswith(".dex") or name.endswith(".xml") or name.endswith(".json")):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
for n in needles:
|
||||
if n in data:
|
||||
print(name, n.decode())
|
||||
20
reverse/scripts/scan_root_string_ctx.py
Normal file
20
reverse/scripts/scan_root_string_ctx.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
for name in zf.namelist():
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
if b"bke_toast_not_support_root" not in data:
|
||||
continue
|
||||
print("===", name, "===")
|
||||
idx = data.find(b"bke_toast_not_support_root")
|
||||
ctx = re.sub(rb"[^\x20-\x7e]+", b" ", data[max(0, idx - 120): idx + 200])
|
||||
print(ctx.decode())
|
||||
for m in re.finditer(rb"Lcom/shopee/bke[^;]{0,120};", data[max(0, idx - 800): idx + 800]):
|
||||
s = m.group().decode()[1:-1].replace("/", ".")
|
||||
if "dialog" in s.lower() or "root" in s.lower() or "safemode" in s.lower() or "risk" in s.lower() or "toast" in s.lower():
|
||||
print(" ", s)
|
||||
44
reverse/scripts/scan_security_classes.py
Normal file
44
reverse/scripts/scan_security_classes.py
Normal file
@@ -0,0 +1,44 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Scan dex class descriptors for Shopee/SeaBank security SDK."""
|
||||
import re
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
TARGETS = (
|
||||
"safemode",
|
||||
"SafeMode",
|
||||
"alc/",
|
||||
"ALC",
|
||||
"integrity",
|
||||
"rooted",
|
||||
"jailbroken",
|
||||
"RootBeer",
|
||||
"xposed",
|
||||
"frida",
|
||||
"isRoot",
|
||||
"detectRoot",
|
||||
)
|
||||
|
||||
|
||||
def scan(apk_path):
|
||||
with zipfile.ZipFile(apk_path) as zf:
|
||||
for name in sorted(zf.namelist()):
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
classes = set(re.findall(rb"L[a-zA-Z0-9_$/]+;", data))
|
||||
hits = []
|
||||
for raw in classes:
|
||||
s = raw.decode("ascii", "ignore")
|
||||
low = s.lower()
|
||||
if any(t.lower() in low for t in TARGETS):
|
||||
hits.append(s[1:-1].replace("/", "."))
|
||||
if hits:
|
||||
print("=== %s ===" % name)
|
||||
for h in sorted(set(hits)):
|
||||
print(h)
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
scan(sys.argv[1])
|
||||
19
reverse/scripts/scan_shps_classes.py
Normal file
19
reverse/scripts/scan_shps_classes.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = zf.read("classes11.dex")
|
||||
for pat in [b"loadLibrary", b"libshpssdk", b"JNI_OnLoad", b"RegisterNatives", b"native "]:
|
||||
print(pat.decode(), data.count(pat))
|
||||
print("\n--- classes with shpssdkbank ---")
|
||||
for m in re.finditer(rb"Lcom/shopee/shpssdkbank/[a-zA-Z0-9_$;/]+;", data):
|
||||
s = m.group().decode()
|
||||
if "uvu" in s or "SPS" in s or "SHPS" in s or "Native" in s:
|
||||
if s not in []:
|
||||
pass
|
||||
classes = sorted(set(m.group().decode()[1:-1].replace("/", ".") for m in re.finditer(rb"Lcom/shopee/shpssdkbank/[a-zA-Z0-9_$]+;", data)))
|
||||
for c in classes[:60]:
|
||||
print(c)
|
||||
31
reverse/scripts/scan_shps_methods.py
Normal file
31
reverse/scripts/scan_shps_methods.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
classes = [
|
||||
b"Lcom/shopee/shpssdk/SPSRiskTokenCallback;",
|
||||
b"Lcom/shopee/shpssdkbank/SPSRiskTokenCallback;",
|
||||
b"Lcom/shopee/shpssdk/SPSResultCallback;",
|
||||
b"Lcom/shopee/shpssdkbank/SPSResultCallback;",
|
||||
b"Lcom/shopee/shpssdk/SHPSSDK;",
|
||||
b"Lcom/shopee/shpssdkbank/SPSAssessRisk;",
|
||||
]
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for c in classes:
|
||||
name = c.decode()[1:-1].replace("/", ".")
|
||||
print("===", name, "===")
|
||||
idx = 0
|
||||
while True:
|
||||
idx = data.find(c, idx)
|
||||
if idx < 0:
|
||||
break
|
||||
ctx = data[max(0, idx - 150): idx + 400]
|
||||
for m in re.finditer(rb"[a-zA-Z][a-zA-Z0-9_$]{2,40}", ctx):
|
||||
s = m.group().decode()
|
||||
if any(k in s.lower() for k in ["token", "risk", "result", "callback", "assess", "get", "on"]):
|
||||
if len(s) > 4:
|
||||
print(" ", s)
|
||||
idx += 1
|
||||
break
|
||||
41
reverse/scripts/scan_shps_native.py
Normal file
41
reverse/scripts/scan_shps_native.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
|
||||
targets = [
|
||||
"Lcom/shopee/shpssdkbank/SHPSSDK;",
|
||||
"Lcom/shopee/shpssdk/SHPSSDK;",
|
||||
]
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for dex_name in ["classes11.dex", "classes10.dex", "classes6.dex"]:
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp_scan.dex"
|
||||
tmp.write_bytes(zf.read(dex_name))
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
for target in targets:
|
||||
capture = False
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
capture = True
|
||||
print("===", dex_name, target, "===")
|
||||
elif capture and line.startswith(" Class descriptor"):
|
||||
break
|
||||
if capture and ("native" in line.lower() or "loadLibrary" in line
|
||||
or "System" in line and "load" in line):
|
||||
print(line.strip())
|
||||
if capture and "name :" in line and "type :" in line:
|
||||
pass
|
||||
if capture and "access : 0x0101" in line or (
|
||||
capture and "NATIVE" in line):
|
||||
print(line.strip())
|
||||
|
||||
# also grep dex binary for loadLibrary strings near shpssdk
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = zf.read("classes11.dex")
|
||||
for m in re.finditer(rb"libshpssdk[^\x00]{0,40}", data):
|
||||
print("str", m.group().decode("latin1"))
|
||||
23
reverse/scripts/scan_shps_token.py
Normal file
23
reverse/scripts/scan_shps_token.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
needles = [
|
||||
b"SPSRiskTokenCallback",
|
||||
b"SPSResultCallback",
|
||||
b"SPSCallback",
|
||||
b"getRiskToken",
|
||||
b"riskToken",
|
||||
b"RiskToken",
|
||||
b"assessRisk",
|
||||
b"AssessRisk",
|
||||
]
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
for name in zf.namelist():
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
hits = [n.decode() for n in needles if n in data]
|
||||
if hits:
|
||||
print(name, hits)
|
||||
19
reverse/scripts/scan_so_jni.py
Normal file
19
reverse/scripts/scan_so_jni.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
native = Path(__file__).resolve().parent.parent / "extracted" / "native"
|
||||
needles = [b"uvwuvwuv", b"NativeEncrypt", b"encryptByRSA", b"aesEncrypt"]
|
||||
for so in sorted(native.glob("lib*.so")):
|
||||
data = so.read_bytes()
|
||||
hits = []
|
||||
for n in needles:
|
||||
if n in data:
|
||||
hits.append(n.decode())
|
||||
if not hits:
|
||||
continue
|
||||
print("\n===", so.name, hits, "===")
|
||||
for m in sorted(set(re.findall(rb"Java_com_shopee_bke_[A-Za-z0-9_]+", data))):
|
||||
s = m.decode()
|
||||
if any(k in s.lower() for k in ("encrypt", "utils", "crypto", "jni")):
|
||||
print(" ", s)
|
||||
36
reverse/scripts/scan_target.py
Normal file
36
reverse/scripts/scan_target.py
Normal file
@@ -0,0 +1,36 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os, re, sys
|
||||
|
||||
def extract_strings(data, min_len=5):
|
||||
out = set()
|
||||
for m in re.finditer(rb'[\x20-\x7e]{' + str(min_len).encode() + rb',}', data):
|
||||
out.add(m.group().decode('ascii', 'ignore'))
|
||||
return out
|
||||
|
||||
def scan_app(app_dir, filters):
|
||||
strings = set()
|
||||
for name in os.listdir(app_dir):
|
||||
if not name.endswith('.dex'):
|
||||
continue
|
||||
with open(os.path.join(app_dir, name), 'rb') as f:
|
||||
strings |= extract_strings(f.read())
|
||||
print('\n===', os.path.basename(app_dir), '===')
|
||||
for label, rx in filters:
|
||||
matched = sorted({s for s in strings if re.search(rx, s, re.I)})
|
||||
print('\n[%s] count=%d' % (label, len(matched)))
|
||||
for s in matched[:50]:
|
||||
print(' ', s)
|
||||
|
||||
filters = [
|
||||
('up notifications', r'au\.com\.up\.money\.notifications|Lau/com/up/money/notifications'),
|
||||
('suncorp messaging', r'au\.com\.suncorp\.marketplace.*(Messaging|Firebase|Notification|Push)'),
|
||||
('ubank messaging', r'au\.com\.bank86400|bank86400|86400.*(Messaging|Firebase|Notification|Push|MoEngage)'),
|
||||
('ubank onMessage', r'onMessageReceived|Will try to show push|MoEngage'),
|
||||
('custom FCM services', r'MessagingService;|HandlerService|FirebaseService'),
|
||||
]
|
||||
|
||||
root = sys.argv[1]
|
||||
for app in sorted(os.listdir(root)):
|
||||
p = os.path.join(root, app)
|
||||
if os.path.isdir(p):
|
||||
scan_app(p, filters)
|
||||
19
reverse/scripts/strings_shps_so.py
Normal file
19
reverse/scripts/strings_shps_so.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
SO = Path(__file__).resolve().parent.parent / "extracted" / "native" / "libshpssdk_bank.so"
|
||||
data = SO.read_bytes()
|
||||
seen = set()
|
||||
for m in re.finditer(rb"[\x20-\x7e]{3,}", data):
|
||||
s = m.group().decode("latin1")
|
||||
if s in seen or len(s) > 200:
|
||||
continue
|
||||
low = s.lower()
|
||||
if any(k in low for k in [
|
||||
"proc", "root", "hook", "xposed", "magisk", "frida", "emulator",
|
||||
"risk", "token", "su", "debug", "maps", "version", "selinux",
|
||||
"shpssdk", "detect", "jail", "integrity"
|
||||
]):
|
||||
seen.add(s)
|
||||
print(s)
|
||||
Reference in New Issue
Block a user