解锁BUG
手机使用的是三星A7000,之前在Android 6.0.1时安装了CROM Service应用并成功解锁,后来回退版本至Android 5.0.2并重新安装了CROM Serice,发现提示已解锁,但是进入BL中显示的是未解锁状态。
APK分析
使用jadx-gui进行分析,在KwbLaunchActivity中存在程序运行的主要逻辑。
kwbso库中的getCustomBinStatusFlag()函数进行一个判断是否已解锁,如果该函数返回值为0进行后续的解锁,否则会判断为已解锁状态。
logcat 分析
由上面的代码可以看出使用了Log.d输出流log信息,可以使用logcat进行过滤判断getCustomBinStatusFlag()函数返回的状态信息。使用adb shell连接,然后执行logcat | grep "LibKwb.getCustomBinStatusFlag()"过滤特定的内容,然后打开应用等待输出内容
1,所以应用只能提示已解锁了,不会进行到解锁的步骤中。
SO库分析
这里使用了Ghidra进行分析,libkwb.so文件加载还需要几个其他的so库文件依赖。所依赖的so文件有:libc.so、libcrypto.so、libdl.so、liblog.so、libm.so、libQSEEComAPI.so、libstdc++.so
除了libQSEEComAPI.so在/system/vendor/lib/中,其他的so库文件都可在/system/lib/中找到
pull到本地,添加到项目中进行分析
Functions中可以找到对应的函数Java_com_sec_android_lib_kwb_LibKwb_getCustomBinStatusFlag
Decompile可以发现只有getRPMBFlag()函数
getRPMBFlag()函数
FUN_0011940()函数
FUN_00011940()函数中先对FUN_00011cc4()函数的返回值进行判断,如果为0就执行FUN_00011dec()函数重赋值给iVar1,然后执行FUN_00011d28()函数
先进入FUN_00011cc4()函数内进行查看
FUN_00011ebc()函数,发现主要是设置uid等权限的
/data/kwb文件的操作
退出,跟进到
FUN_00011c30()函数中
FUN_00011cc4()函数的17行中发现了点希望。
FUN_000120fc()返回值进行判断,如果返回值不为0就说明解锁失败,那就继续跟进FUN_000120fc()吧
DAT_00019890进行比较,如果大于-1就flock(DAT_00019890,8)对于flock搜了下
flock相当于文件锁。
解锁总结
原理是通过加载so库判断是否已解锁,未解锁就会发送解锁请求给服务器,服务器返回解锁token,so库对token进行验证,如果通过就会启动高通的QSEECOM接口进行解锁操作。故此操作不可逆。
另类绕过(不可行)
因为我是Android 5.0.2通过漏洞或者kingoroot等软件可以进行root,所以进入adb shell中使用echo 1 > /data/kwb并使用busybox chattr +i /data/kwb强制锁定该文件,不让他为空。
CROM Serviceapp应用就会看到解锁提示
确认前需要执行busybox chattr -i kwb解锁文件写入操作。
高通接口解锁
网上有篇文章分析了高通的接口以及如何提取的方法。参考链接 mclf loader 提取脚本:
import sys, os, struct
def main():
#Reading the arguments
if len(sys.argv) != 4:
print("USAGE: <TRUSTLET_DIR> <TRUSTLET_NAME> <OUTPUT_FILE_PATH>")
return
trustlet_dir = sys.argv[1]
trustlet_name = sys.argv[2]
output_file_path = sys.argv[3]
bitness = 32
ELF_HEADER_SIZE = 0x34
E_PHNUM_OFFSET = 0x2C
PHDR_SIZE = 0x20
P_FILESZ_OFFSET = 0x10
P_OFFSET_OFFSET = 0x4
#Reading the ELF header from the ".mdt" file
mdt = open(os.path.join(trustlet_dir, "%s.mdt" % trustlet_name), "rb")
elf_header = mdt.read(ELF_HEADER_SIZE)
phnum = struct.unpack("<H", elf_header[E_PHNUM_OFFSET:E_PHNUM_OFFSET+2])[0]
print("[+] Found %d program headers" % phnum)
#Reading each of the program headers and copying the relevant chunk
output_file = open(output_file_path, 'wb')
for i in range(0, phnum):
#Reading the PHDR
print("[+] Reading PHDR %d" % i)
phdr = mdt.read(PHDR_SIZE)
p_filesz = struct.unpack("<I", phdr[P_FILESZ_OFFSET:P_FILESZ_OFFSET+4])[0]
p_offset= struct.unpack("<I", phdr[P_OFFSET_OFFSET:P_OFFSET_OFFSET+4])[0]
print("[+] Size: 0x%08X, Offset: 0x%08X" % (p_filesz, p_offset))
if p_filesz == 0:
print("[+] Empty block, skipping")
continue #There's no backing block
#Copying out the data in the block
block = open(os.path.join(trustlet_dir, "%s.b%02d" % (trustlet_name, i)), 'rb').read()
output_file.seek(p_offset, 0)
output_file.write(block)
mdt.close()
output_file.close()
def parse_DER():
trustlet_dir = sys.argv[1]
trustlet_name = sys.argv[2]
mdt = open(os.path.join(trustlet_dir, "%s.mdt" % trustlet_name), "rb")
certs = mdt.read()
certs_index = []
for i in range(0x34,len(certs)-5):
if certs[i] == '\x30' and certs[i+1] == '\x82' and certs[i+4] == '\x30' and certs[i+5] == '\x82':
certs_index.append(i)
certs_index.append(len(certs))
print(certs_index)
for i in range(len(certs_index)-1):
start = certs_index[i]
end = certs_index[i+1]
certname = "cert_"+str(start)+".der"
print(certname)
open(certname,'wb').write(bytes(certs[start : end]))
if __name__ == "__main__":
main()
parse_DER()
提取出的文件为去符号的elf文件
到此为止吧😂😂😂