这一题很有迷惑性,看似简单的代码逻辑,一眼看到的答案,其实并不是真相,重点在他的反检测。大多数的时候我们通过静态分析(java层还是so层)找到他的加密算法,再逆向还原其算法就能找到最终的答案,但是这道题不是,接下来我们我们会用到frida、AndroidNativeEmu、unidbg、IDA动静态调试。
从代码可以看出,输入的密码调用方法securityCheck(String str),满足true则成功
找到Java_com_yaotong_crackme_MainActivity_securityCheck函数
当我们分别输入 123 和 wojiushidaan 看返回的结果
结论:我们可以很确定v6 = off_628C值就是我们输入的值。
在不考虑风控的前提下,明确了目标值用frida hook,是最快的方式,那就来吧,验证下。
得到值 : aiyou,bucuoo 输入该值验证成功
执行验证结果如图所示:
通过模拟下调用(符号调用),在hook关键参数地址,运行后,获得的正确flag:aiyou,bucuoo,然后我们在输入flag 则显示true,成功!!!
如果不喜欢用frida IDA动态调试也是比较通用直接的,可以一步一步跟踪查看代码的运行逻辑。
在Module list窗口(Debugger->Debugger windows->Module list)中找到libcrackme.so,双击它,f5进入伪代码页面,在目标函数下断点,v6 = off_C0B0E28C;
按下F9,弹出错误警告。
从这一系列的操作我们可以发现,wojiushidaan 这个值,在APP运行后,被重新赋值了。在进行调试的时候报错,说明有反调试存在。
1.端口检测:调试器在进行远程调试时,会占用一些固定的端口号。IDA Pro可以通过读取/proc/net/tcp文件,查找远程调试所用的23946端口,若发现该端口被占用,则说明进程正在被IDA调试
2.关键文件检测:通过修改android_server文件的名称,防止调试器找到并连接该文件
3.进程ID检测:在没有调试时,TracerPid为0;运行调试时,TracerPid会变为调试器的进程ID。通过修改系统调用函数,伪造TracerPid为0,以欺骗调试器
4.Java层反调试:在AndroidManifest.xml中设置android:debuggable="false",并在build.prop中设置ro.debuggable=0,防止应用在调试模式下运行。此外,可以通过检测Debug.isDebuggerConnected()方法的返回值来判断是否被调试
5.自我调试:父进程创建一个子进程,通过子进程调试父进程。这种方式消耗的系统资源较少,且能有效阻止其他进程调试受保护的进程。
我们要在so文件加载之前进行调试。这样就能判断程序大概检测位置,逐渐深入,定位问题的所在。
APK 重新打包成 debuuger 模式
adb shell am start -D -n com.yaotong.crackme/.MainActivity 以调试模式启动app,让程序停在加载so文件之前。
IDA设置如下:
连接jdb后,IDA运行绿色三角按钮,让程序把so文件加载出来。
Ctrl+s 搜索进入我们的目标文件
进入JNI_Onload函数下断点进行调试
当前位置程序并没有奔溃,说明检测点还在后面,然后继续,跳到R7的时候报错了 那么检测点 位置找到了
把反调试的TracerPid 指令37 FF 2F E1 改为 00 00 00 00 就不会再有反调试的防护机制了
再进行正常调试。畅通无阻,顺利看到了off_D860B28C 的值 aiyou,bucuoo
import
logging
import
sys
from
unicorn
import
UC_HOOK_CODE, UC_HOOK_MEM_READ, UC_HOOK_MEM_WRITE
from
unicorn.arm_const
import
*
from
androidemu.emulator
import
Emulator
logging.basicConfig(
stream
=
sys.stdout,
level
=
logging.DEBUG,
format
=
"%(asctime)s %(levelname)7s %(name)34s | %(message)s"
)
logger
=
logging.getLogger(__name__)
emulator
=
Emulator(vfp_inst_set
=
True
)
emulator.load_library(
"../example_binaries/32/libc.so"
, do_init
=
False
)
lib_module
=
emulator.load_library(
"libcrackme.so"
, do_init
=
False
)
target_address
=
0x4450
+
0xa009b000
string_length
=
100
logger.info(
"Loaded modules:"
)
for
module
in
emulator.modules:
logger.info(
"[0x%x] %s"
%
(module.base, module.filename))
def
memory_read_hook(uc, access, address, size, value, user_data):
if
address
=
=
target_address:
current_value
=
uc.mem_read(address, string_length).split(b
'\0'
,
1
)[
0
].decode(
'ascii'
, errors
=
'ignore'
)
print
(f
"【READ】 Address: 0x{address:X}, Current Value: {current_value}"
)
def
memory_write_hook(uc, access, address, size, value, user_data):
if
address
=
=
target_address:
new_value
=
uc.mem_read(address, string_length).split(b
'\0'
,
1
)[
0
].decode(
'ascii'
, errors
=
'ignore'
)
print
(f
"【WRITE】 Address: 0x{address:X}, New Value: {new_value}"
)
emulator.uc.hook_add(
UC_HOOK_MEM_READ,
memory_read_hook,
None
,
target_address,
target_address
+
string_length
)
emulator.uc.hook_add(
UC_HOOK_MEM_WRITE,
memory_write_hook,
None
,
target_address,
target_address
+
string_length
)
result1
=
emulator.call_symbol(
lib_module,
'Java_com_yaotong_crackme_MainActivity_securityCheck'
,
emulator.java_vm.jni_env.address_ptr,
0
,
"wojiushidaan"
,
/
/
123
is_return_jobject
=
False
)
print
(
"jnicheck result : {}"
.
format
(result1))
import
logging
import
sys
from
unicorn
import
UC_HOOK_CODE, UC_HOOK_MEM_READ, UC_HOOK_MEM_WRITE
from
unicorn.arm_const
import
*
from
androidemu.emulator
import
Emulator
logging.basicConfig(
stream
=
sys.stdout,
level
=
logging.DEBUG,
format
=
"%(asctime)s %(levelname)7s %(name)34s | %(message)s"
)
logger
=
logging.getLogger(__name__)
emulator
=
Emulator(vfp_inst_set
=
True
)
emulator.load_library(
"../example_binaries/32/libc.so"
, do_init
=
False
)
lib_module
=
emulator.load_library(
"libcrackme.so"
, do_init
=
False
)
target_address
=
0x4450
+
0xa009b000
string_length
=
100
logger.info(
"Loaded modules:"
)
for
module
in
emulator.modules:
logger.info(
"[0x%x] %s"
%
(module.base, module.filename))
def
memory_read_hook(uc, access, address, size, value, user_data):
if
address
=
=
target_address:
current_value
=
uc.mem_read(address, string_length).split(b
'\0'
,
1
)[
0
].decode(
'ascii'
, errors
=
'ignore'
)
print
(f
"【READ】 Address: 0x{address:X}, Current Value: {current_value}"
)
def
memory_write_hook(uc, access, address, size, value, user_data):
if
address
=
=
target_address:
new_value
=
uc.mem_read(address, string_length).split(b
'\0'
,
1
)[
0
].decode(
'ascii'
, errors
=
'ignore'
)
print
(f
"【WRITE】 Address: 0x{address:X}, New Value: {new_value}"
)
emulator.uc.hook_add(
UC_HOOK_MEM_READ,
memory_read_hook,
None
,
target_address,
target_address
+
string_length
)
emulator.uc.hook_add(
UC_HOOK_MEM_WRITE,
memory_write_hook,
None
,
target_address,
target_address
+
string_length
)
result1
=
emulator.call_symbol(
lib_module,
'Java_com_yaotong_crackme_MainActivity_securityCheck'
,
emulator.java_vm.jni_env.address_ptr,
0
,
"wojiushidaan"
,
/
/
123
is_return_jobject
=
False
)
print
(
"jnicheck result : {}"
.
format
(result1))
function hook_so() {
Java.perform(function(){
var addr
=
Module.findBaseAddress(
"libcrackme.so"
);
var v1
=
addr.add(
0x4450
);
console.log(v1.readCString());
});
}
function main() {
hook_so()
}
setTimeout(main,
4000
)
function hook_so() {
Java.perform(function(){
var addr
=
Module.findBaseAddress(
"libcrackme.so"
);
var v1
=
addr.add(
0x4450
);
console.log(v1.readCString());
});
}
function main() {
hook_so()
}
setTimeout(main,
4000
)
package com.yaotong.crackme;
import
com.github.unidbg.AndroidEmulator;
import
com.github.unidbg.Module;
import
com.github.unidbg.arm.backend.DynarmicFactory;
import
com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import
com.github.unidbg.linux.android.AndroidResolver;
import
com.github.unidbg.linux.android.dvm.AbstractJni;
import
com.github.unidbg.linux.android.dvm.DalvikModule;
import
com.github.unidbg.linux.android.dvm.DvmObject;
import
com.github.unidbg.linux.android.dvm.VM;
import
com.github.unidbg.memory.Memory;
import
com.github.unidbg.pointer.UnidbgPointer;
import
java.nio.charset.Charset;
import
java.io.
File
;
public
class
MainActivity extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private final Memory memory;
MainActivity() {
/
/
创建模拟器
emulator
=
AndroidEmulatorBuilder.for32Bit().addBackendFactory(new DynarmicFactory(true)).build();
/
/
内存
memory
=
emulator.getMemory();
/
/
设置SDK
memory.setLibraryResolver(new AndroidResolver(
23
));
/
/
创建虚拟机
vm
=
emulator.createDalvikVM(new
File
(
"unidbg-android/src/test/java/com/yaotong/crackme/you.apk"
));
/
/
设置jni
vm.setJni(this);
/
/
打印日志
vm.setVerbose(true);
/
/
运行so文件
DalvikModule dalvikModule
=
vm.loadLibrary(new
File
(
"unidbg-android/src/test/java/com/yaotong/crackme/libcrackme.so"
), true);
/
/
module
module
=
dalvikModule.getModule();
/
/
调用JNI——onload
/
/
dalvikModule.callJNI_OnLoad(emulator);
vm.callJNI_OnLoad(emulator, module);
HookAddr();
}
/
*
*
*
打印
Hex
Dump 格式的数据
*
*
@param data 要打印的字节数组
*
/
private static void printHexDump(byte[] data) {
int
bytesPerLine
=
16
;
/
/
每行打印
16
个字节
for
(
int
i
=
0
; i < data.length; i
+
=
bytesPerLine) {
/
/
打印当前行的地址(偏移量)
System.out.printf(
"%08X "
, i);
/
/
打印当前行的十六进制数据
for
(
int
j
=
0
; j < bytesPerLine; j
+
+
) {
if
(i
+
j < data.length) {
System.out.printf(
"%02X "
, data[i
+
j]);
}
else
{
System.out.
print
(
" "
);
/
/
如果剩余字节不足
16
个,填充空格
}
}
System.out.
print
(
" |"
);
/
/
打印当前行的字符内容(ASCII)
for
(
int
j
=
0
; j < bytesPerLine; j
+
+
) {
if
(i
+
j < data.length) {
byte b
=
data[i
+
j];
if
(b >
=
32
&& b <
=
126
) {
System.out.
print
((char) b);
/
/
打印可打印字符
}
else
{
System.out.
print
(
"."
);
/
/
打印不可打印字符
}
}
else
{
System.out.
print
(
" "
);
/
/
填充空格
}
}
System.out.println(
"|"
);
}
}
public static void main(String[] args) {
MainActivity test
=
new MainActivity();
System.out.println(test.getSName());
}
public void HookAddr() {
/
/
目标地址,这里是示例地址
0x628C
0x4450
long
targetAddress
=
module.base
+
0x4450
;
/
/
使用 UnidbgPointer 来获取目标地址的数据
UnidbgPointer pointer
=
UnidbgPointer.pointer(emulator, targetAddress);
/
/
读取目标地址的数据,假设它是一个字符串,长度为
16
字节
byte[] data
=
pointer.getByteArray(
0
,
16
);
/
/
读取
16
个字节
printHexDump(data);
/
/
将读取的字节转换为字符串
/
/
将读取的字节转换为字符串,并指定正确的编码(例如 UTF
-
8
或 GBK)
String value
=
new String(data, Charset.forName(
"GBK"
));
/
/
使用 UTF
-
8
编码
/
/
打印地址和读取的内容
System.out.println(
"Address: "
+
Long
.toHexString(targetAddress));
System.out.println(
"Data at address: "
+
value);
}
/
/
符号调用
public Boolean getSName() {
/
/
创建一个vm对象
DvmObject<?> dvmObject
=
vm.resolveClass(
"com/yaotong/crackme/MainActivity"
).newObject(null);
String
input
=
"123"
;
/
/
byte[] inputByte
=
input
.getBytes(StandardCharsets.UTF_8);
boolean success
=
dvmObject.callJniMethodBoolean(emulator,
"securityCheck(Ljava/lang/string;)Z"
,
input
);
System.out.println(
"[symble] Call the so md5 function result is ==> "
+
success);
return
success;
}
}
package com.yaotong.crackme;
import
com.github.unidbg.AndroidEmulator;
import
com.github.unidbg.Module;
import
com.github.unidbg.arm.backend.DynarmicFactory;
import
com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import
com.github.unidbg.linux.android.AndroidResolver;
import
com.github.unidbg.linux.android.dvm.AbstractJni;
import
com.github.unidbg.linux.android.dvm.DalvikModule;
import
com.github.unidbg.linux.android.dvm.DvmObject;
import
com.github.unidbg.linux.android.dvm.VM;
import
com.github.unidbg.memory.Memory;
import
com.github.unidbg.pointer.UnidbgPointer;
import
java.nio.charset.Charset;
import
java.io.
File
;
public
class
MainActivity extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
[注意]APP应用上架合规检测服务,协助应用顺利上架!