拖进jadx java层逻辑简单,只需要关注native层stringFromJNI。
使用IDA静态分析libhello-jni.so,两个跳转让我直接懵逼,都不知道跳哪去了。
尝试用IDA调试,直接挂,有点绝望。
掏出frida,先跑了下frida-trace,结果正确的序列号弹出了“输入错误”,打不出正确的调用路径。让我直接放弃frida,绝望++。
想起之前瞥过一眼的unidbg-v0.9.3,感觉这题用它正合适。
先试着使用unidbg主动调用stringFromJNI:
能够运行和输出正确结果,感觉找对了路。
尝试使用IDA连unidbg动态调试:
emulator.attach(DebuggerType.ANDROID_SERVER_V7);
IDA附加进程后,简单跟踪了几步后,要么跑飞要么卡死...
emulator.traceCode();
traceLog看下发现ARM指令和Thumb指令来回切换。
想恢复IDA的指令静态分析,无奈找不到替代Alt+G(Change Segment Register Value)的api函数,放弃。
看着traceCode的效果:
失望,对于合格的TraceLog来说应该有寄存器的变化。
还是自己写个吧
我这东拼西凑的代码,能用就行。看着日志效果感觉还不错
正确的序列号 name:ed8b9244350d3644 serial:7C9815255BFE832D3F93140B
生成日志trace_log_true.txt,37MB的日志23w多行,直接去分析太耗时间了。
试着控制最小变量来找差异,把序列号最后一位改成C:
name:ed8b9244350d3644 serial:7C9815255BFE832D3F93140C
生成日志trace_log_flase_serial.txt,与正确的大小和行数差不多。
用BeyondCompare比较下,忽略掉push pop nop b 指令行,减少干扰。
看着这差异行并不是很多,才477个差异部分,感觉自己又可以了。
根据习惯倒着浏览差异处
0x400076a4的r4寄存器存着输入的序列号最后一位,感觉找到了关键位置。
搜索 "0x400076a4" "0x400076a8" trace_log_true.txt (匹配60次)
搜索 "0x400076a4" "0x400076a8" trace_log_flase_serial.txt (匹配60次)
看着这熟悉的序列号,算法大概是通过name算出serial后再与输入的进行异或对比。
使用 name:KCTF serial:7C9815255BFE832D3F93140B 生成trace_log_false_name_kctf.txt
搜索 "0x400076a8" "0x400076a8" trace_log_false_name_kctf.txt (匹配60次) 还好相同,说明没有少跑逻辑。
推测r5异或前就是正确的序列号
17726331DA0FE737149C8202
public void Btn1_Click(View view) {
String
str
;
String
input
=
this.text.getText().toString();
String input2
=
this.text2.getText().toString();
if
(
input
=
=
null ||
input
.isEmpty()) {
str
=
"name为空"
;
}
else
if
(input2
=
=
null || input2.isEmpty()) {
str
=
"serial为空"
;
}
else
{
System.loadLibrary(
"hello-jni"
);
str
=
stringFromJNI(
input
, input2);
}
AlertDialog.Builder builder
=
new AlertDialog.Builder(mContext);
builder.setTitle("");
builder.setMessage(
str
);
builder.show();
}
public void Btn1_Click(View view) {
String
str
;
String
input
=
this.text.getText().toString();
String input2
=
this.text2.getText().toString();
if
(
input
=
=
null ||
input
.isEmpty()) {
str
=
"name为空"
;
}
else
if
(input2
=
=
null || input2.isEmpty()) {
str
=
"serial为空"
;
}
else
{
System.loadLibrary(
"hello-jni"
);
str
=
stringFromJNI(
input
, input2);
}
AlertDialog.Builder builder
=
new AlertDialog.Builder(mContext);
builder.setTitle("");
builder.setMessage(
str
);
builder.show();
}
package com.kctf;
import
capstone.Capstone;
import
com.github.unidbg.AndroidEmulator;
import
com.github.unidbg.Emulator;
import
com.github.unidbg.Module;
import
com.github.unidbg.Symbol;
import
com.github.unidbg.arm.ARM;
import
com.github.unidbg.arm.HookStatus;
import
com.github.unidbg.arm.backend.Backend;
import
com.github.unidbg.arm.backend.CodeHook;
import
com.github.unidbg.arm.backend.DebugHook;
import
com.github.unidbg.arm.context.Arm32RegisterContext;
import
com.github.unidbg.arm.context.RegisterContext;
import
com.github.unidbg.debugger.DebuggerType;
import
com.github.unidbg.hook.HookContext;
import
com.github.unidbg.hook.ReplaceCallback;
import
com.github.unidbg.hook.hookzz.
*
;
import
com.github.unidbg.hook.xhook.IxHook;
import
com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import
com.github.unidbg.linux.android.AndroidResolver;
import
com.github.unidbg.linux.android.XHookImpl;
import
com.github.unidbg.linux.android.dvm.AbstractJni;
import
com.github.unidbg.linux.android.dvm.DalvikModule;
import
com.github.unidbg.linux.android.dvm.DvmClass;
import
com.github.unidbg.linux.android.dvm.VM;
import
com.github.unidbg.linux.android.dvm.array.ByteArray;
import
com.github.unidbg.memory.Memory;
import
com.github.unidbg.utils.Inspector;
import
com.sun.jna.Pointer;
import
unicorn.ArmConst;
import
java.io.
*
;
import
java.nio.charset.StandardCharsets;
import
java.util.
*
;
public
class
hellojni extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private final DvmClass hellojniUtils;
private final boolean logging;
private final
Set
<Integer> trace_addr;
private final
Map
<Integer, String> insn_map;
private final
Map
<Integer, Integer> insn_size;
private String lastContent;
private
int
[] lastReg;
hellojni(boolean logging) {
lastContent
=
"";
lastReg
=
null;
trace_addr
=
new TreeSet<Integer>();
insn_map
=
new TreeMap<Integer, String>();
insn_size
=
new TreeMap<>();
this.logging
=
logging;
emulator
=
AndroidEmulatorBuilder.for32Bit().setProcessName(
"com.example.hellojni"
).build();
/
/
创建模拟器实例,要模拟
32
位或者
64
位,在这里区分
final Memory memory
=
emulator.getMemory();
/
/
模拟器的内存操作接口
memory.setLibraryResolver(new AndroidResolver(
23
));
/
/
设置系统类库解析
vm
=
emulator.createDalvikVM(null);
/
/
创建Android虚拟机
vm.setJni(this);
vm.setVerbose(logging);
/
/
设置是否打印Jni调用细节
DalvikModule dm
=
vm.loadLibrary(new
File
(
"unidbg-android/src/test/resources/example_binaries/libhello-jni.so"
), true);
/
/
加载libhello
-
jni.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
dm.callJNI_OnLoad(emulator);
/
/
手动执行JNI_OnLoad函数
module
=
dm.getModule();
/
/
加载好的libhello
-
jni.so对应为一个模块
/
/
com_example_hellojni_HelloJni_
hellojniUtils
=
vm.resolveClass(
"com/example/hellojni/HelloJni"
);
}
void destroy() throws IOException {
Object
[] itr
=
trace_addr.toArray();
File
f
=
new
File
(
"E:/out.txt"
);
FileWriter fw
=
new FileWriter(f);
for
(
Object
i : itr) {
/
/
System.out.println(String.
format
(
"set_color(0x%x, CIC_FUNC, 0xd3d3d3) %s"
, (Integer) i, insn_map.get(i)));
/
/
fw.write(insn_map.get(i));
fw.write(String.
format
(
"setreg(0x%x, %d)\n"
, i, insn_size.get(i)));
}
fw.close();
emulator.close();
if
(logging) {
System.out.println(
"destroy"
);
}
}
public static void main(String[] args) throws Exception {
com.kctf.hellojni test
=
new com.kctf.hellojni(true);
String data
=
test.stringFromJNI(
"ed8b9244350d3644"
,
"7C9815255BFE832D3F93140B"
);
System.out.println(data);
test.destroy();
}
public static
int
[] getReg(String reg) {
int
[] ret
=
new
int
[
1
];
switch (reg) {
case
"r0"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R0;
break
;
case
"r1"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R1;
break
;
case
"r2"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R2;
break
;
case
"r3"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R3;
break
;
case
"r4"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R4;
break
;
case
"r5"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R5;
break
;
case
"r6"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R6;
break
;
case
"r7"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R7;
break
;
case
"r8"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R8;
break
;
case
"r9"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R9;
break
;
case
"r10"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R10;
break
;
case
"r11"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R11;
break
;
case
"r12"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R12;
break
;
case
"pc"
: ret[
0
]
=
ArmConst.UC_ARM_REG_PC;
break
;
case
"sp"
: ret[
0
]
=
ArmConst.UC_ARM_REG_SP;
break
;
case
"lr"
: ret[
0
]
=
ArmConst.UC_ARM_REG_LR;
break
;
case
"sb"
: ret[
0
]
=
ArmConst.UC_ARM_REG_SB;
break
;
default:
return
null;
}
return
ret;
}
String getMemStr(Backend backend,
int
addr) {
StringBuilder builder
=
new StringBuilder();
a:
do {
try
{
byte[] bytes
=
backend.mem_read(addr,
1
);
for
(
int
i
=
0
; i < bytes.length; i
+
+
) {
int
fb
=
bytes[i];
if
(fb <
=
31
|| fb >
=
127
)
break
a;
builder.append(String.
format
(
"%c"
,fb));
}
addr
+
+
;
} catch (Exception e) {
return
"";
}
}
while
(true);
return
builder.toString();
}
String stringFromJNI(String name, String serial) {
if
(false)
{
Object
ret
=
hellojniUtils.callStaticJniMethodObject(emulator,
"stringFromJNI(Ljava/lang/String;Ljava/lang/String)Ljava/lang/String"
, name, serial);
/
/
执行Jni方法
return
ret.toString();
}
/
/
so function address
final
Set
<Integer> st
=
new TreeSet<Integer>() {{
Integer[] a
=
new Integer[]{
0xff5
,
0x1035
,
0x10e5
,
0x1421
,
0x1438
,
0x14dc
,
0x152c
,
0x1634
,
0x16c0
,
0x1724
,
0x1854
,
0x1914
,
0x19c4
,
0x1a48
,
0x1aa4
,
0x1ad0
,
0x1b98
,
0x1fa4
,
0x2350
,
0x2394
,
0x24b4
,
0x2540
,
0x28c8
};
for
(Integer b : a) {
add(b);
}
}};
emulator.getBackend().hook_add_new(new DebugHook() {
@Override
public void onBreak(Backend backend,
long
address,
int
size,
Object
user) {
}
@Override
public void hook(Backend backend,
long
address,
int
size,
Object
user) {
/
/
打印当前地址。这里要把unidbg使用的基址给去掉。
int
addr
=
(
int
) address
-
0x40000000
;
trace_addr.add(addr);
if
(st.contains(addr
+
1
)) {
/
/
System.out.println(String.
format
(
"0x%x"
, addr));
}
/
/
if
(false) {
Capstone.CsInsn[] insns
=
emulator.printAssemble(System.out, address, size);
/
/
ARM.showRegs(emulator, ARM.getRegArgs(emulator));
}
if
(true) {
ByteArrayOutputStream baos
=
new ByteArrayOutputStream();
PrintStream ps
=
null;
try
{
ps
=
new PrintStream(baos, true,
"utf-8"
);
} catch (IOException e) {
}
Capstone.CsInsn[] insns
=
emulator.printAssemble(ps, address, size);
String content
=
new String(baos.toByteArray(), StandardCharsets.UTF_8);
content
=
content.replace(
'\n'
,
' '
);
ps.close();
String regStr
=
"";
for
(
int
i
=
0
; i < insns.length; i
+
+
) {
regStr
=
insns[i].opStr.split(
","
)[
0
];
}
int
[] regs
=
getReg(regStr);
if
(regs !
=
null) {
content
=
content
+
" >>> "
+
regStr
+
"="
+
"0x%x"
;
}
if
(lastReg
=
=
null) {
System.out.
print
(lastContent);
ARM.showRegs(emulator, null);
}
else
{
int
value
=
backend.reg_read(lastReg[
0
]).intValue();
String memStr
=
getMemStr(backend, value);
if
(!memStr.equals("")) {
memStr
=
"\""
+
memStr
+
"\""
;
}
System.out.println(String.
format
(lastContent, value)
+
" "
+
memStr);
}
lastContent
=
content;
lastReg
=
regs;
if
(!insn_map.containsKey(addr)) {
insn_map.put(addr, content);
insn_size.put(addr, (
int
) insns[
0
].size);
}
}
}
},
0x40000ff4
,
0x400076BC
, null);
/
/
emulator.traceCode();
/
/
emulator.traceWrite();
/
/
emulator.traceRead();
/
/
emulator.attach(DebuggerType.ANDROID_SERVER_V7);
/
/
附加IDA android_server,可输入c命令取消附加继续运行
Object
ret
=
hellojniUtils.callStaticJniMethodObject(emulator,
"stringFromJNI(Ljava/lang/String;Ljava/lang/String)Ljava/lang/String"
, name, serial);
/
/
执行Jni方法
return
ret.toString();
}
}
package com.kctf;
import
capstone.Capstone;
import
com.github.unidbg.AndroidEmulator;
import
com.github.unidbg.Emulator;
import
com.github.unidbg.Module;
import
com.github.unidbg.Symbol;
import
com.github.unidbg.arm.ARM;
import
com.github.unidbg.arm.HookStatus;
import
com.github.unidbg.arm.backend.Backend;
import
com.github.unidbg.arm.backend.CodeHook;
import
com.github.unidbg.arm.backend.DebugHook;
import
com.github.unidbg.arm.context.Arm32RegisterContext;
import
com.github.unidbg.arm.context.RegisterContext;
import
com.github.unidbg.debugger.DebuggerType;
import
com.github.unidbg.hook.HookContext;
import
com.github.unidbg.hook.ReplaceCallback;
import
com.github.unidbg.hook.hookzz.
*
;
import
com.github.unidbg.hook.xhook.IxHook;
import
com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import
com.github.unidbg.linux.android.AndroidResolver;
import
com.github.unidbg.linux.android.XHookImpl;
import
com.github.unidbg.linux.android.dvm.AbstractJni;
import
com.github.unidbg.linux.android.dvm.DalvikModule;
import
com.github.unidbg.linux.android.dvm.DvmClass;
import
com.github.unidbg.linux.android.dvm.VM;
import
com.github.unidbg.linux.android.dvm.array.ByteArray;
import
com.github.unidbg.memory.Memory;
import
com.github.unidbg.utils.Inspector;
import
com.sun.jna.Pointer;
import
unicorn.ArmConst;
import
java.io.
*
;
import
java.nio.charset.StandardCharsets;
import
java.util.
*
;
public
class
hellojni extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private final DvmClass hellojniUtils;
private final boolean logging;
private final
Set
<Integer> trace_addr;
private final
Map
<Integer, String> insn_map;
private final
Map
<Integer, Integer> insn_size;
private String lastContent;
private
int
[] lastReg;
hellojni(boolean logging) {
lastContent
=
"";
lastReg
=
null;
trace_addr
=
new TreeSet<Integer>();
insn_map
=
new TreeMap<Integer, String>();
insn_size
=
new TreeMap<>();
this.logging
=
logging;
emulator
=
AndroidEmulatorBuilder.for32Bit().setProcessName(
"com.example.hellojni"
).build();
/
/
创建模拟器实例,要模拟
32
位或者
64
位,在这里区分
final Memory memory
=
emulator.getMemory();
/
/
模拟器的内存操作接口
memory.setLibraryResolver(new AndroidResolver(
23
));
/
/
设置系统类库解析
vm
=
emulator.createDalvikVM(null);
/
/
创建Android虚拟机
vm.setJni(this);
vm.setVerbose(logging);
/
/
设置是否打印Jni调用细节
DalvikModule dm
=
vm.loadLibrary(new
File
(
"unidbg-android/src/test/resources/example_binaries/libhello-jni.so"
), true);
/
/
加载libhello
-
jni.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
dm.callJNI_OnLoad(emulator);
/
/
手动执行JNI_OnLoad函数
module
=
dm.getModule();
/
/
加载好的libhello
-
jni.so对应为一个模块
/
/
com_example_hellojni_HelloJni_
hellojniUtils
=
vm.resolveClass(
"com/example/hellojni/HelloJni"
);
}
void destroy() throws IOException {
Object
[] itr
=
trace_addr.toArray();
File
f
=
new
File
(
"E:/out.txt"
);
FileWriter fw
=
new FileWriter(f);
for
(
Object
i : itr) {
/
/
System.out.println(String.
format
(
"set_color(0x%x, CIC_FUNC, 0xd3d3d3) %s"
, (Integer) i, insn_map.get(i)));
/
/
fw.write(insn_map.get(i));
fw.write(String.
format
(
"setreg(0x%x, %d)\n"
, i, insn_size.get(i)));
}
fw.close();
emulator.close();
if
(logging) {
System.out.println(
"destroy"
);
}
}
public static void main(String[] args) throws Exception {
com.kctf.hellojni test
=
new com.kctf.hellojni(true);
String data
=
test.stringFromJNI(
"ed8b9244350d3644"
,
"7C9815255BFE832D3F93140B"
);
System.out.println(data);
test.destroy();
}
public static
int
[] getReg(String reg) {
int
[] ret
=
new
int
[
1
];
switch (reg) {
case
"r0"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R0;
break
;
case
"r1"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R1;
break
;
case
"r2"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R2;
break
;
case
"r3"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R3;
break
;
case
"r4"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R4;
break
;
case
"r5"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R5;
break
;
case
"r6"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R6;
break
;
case
"r7"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R7;
break
;
case
"r8"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R8;
break
;
case
"r9"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R9;
break
;
case
"r10"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R10;
break
;
case
"r11"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R11;
break
;
case
"r12"
: ret[
0
]
=
ArmConst.UC_ARM_REG_R12;
break
;
case
"pc"
: ret[
0
]
=
ArmConst.UC_ARM_REG_PC;
break
;
case
"sp"
: ret[
0
]
=
ArmConst.UC_ARM_REG_SP;
break
;
case
"lr"
: ret[
0
]
=
ArmConst.UC_ARM_REG_LR;
break
;
case
"sb"
: ret[
0
]
=
ArmConst.UC_ARM_REG_SB;
break
;
default:
return
null;
}
return
ret;
}
String getMemStr(Backend backend,
int
addr) {
StringBuilder builder
=
new StringBuilder();
a:
do {
try
{
byte[] bytes
=
backend.mem_read(addr,
1
);
for
(
int
i
=
0
; i < bytes.length; i
+
+
) {
int
fb
=
bytes[i];
if
(fb <
=
31
|| fb >
=
127
)
break
a;
builder.append(String.
format
(
"%c"
,fb));
}
addr
+
+
;
} catch (Exception e) {
return
"";
}
}
while
(true);
return
builder.toString();
}
String stringFromJNI(String name, String serial) {
if
(false)
{
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2021-5-16 15:53
被Wblank编辑
,原因: