题目出自3w班12月题。对抗字符串混淆
字符串混淆在保护中是很常见的一个手段。熟练使用unicorn能轻松的解决曾经实现起来非常复杂的问题。
AndroidNativeEmu和unidbg都是对unicorn进行了一定程度封装的开源项目,让我们可以很方便的调用jni函数,以及对没有实现的函数进行hook实现处理。并且让我们可以实现so里面模拟调用java函数。下面首先使用AndroidNativeEmu来对抗ollvm的字符串混淆。对so进行修复。让我们可以更加便捷的进行静态分析。
在实现之前,先总结下原理。
ollvm的字符串混淆,我们使用ida的时候。看到的结果如下。
这里相当于是一个解密函数。通过这个函数将会还原成正常的字符串。然后我们看看在哪里调用到了这个解密函数
可以看到是在.init_array中使用解密函数对字符串进行还原。也就是说。当我们执行完.init_array后。就会将正常的字符串写入内存中。这时我们就得到了真正的字符串了。所以根据这个情况。
第一步,我们需要监控内存的读写,然后运行.init_array。这样发生的内存写入时,基本可以确定是字符串还原函数在写入恢复的字符串。
写之前我们先看看unicorn的内存写入的相关注释
这里就知道了。size是写入或读取数据的长度,value是写入或读取的数据。那么开始写我们第一步的代码
输出的结果如下
这里能看到。真实的字符串已经能在回调监控中打印出来了。接下来的第二步,我们需要把所有真实字符串以及写入真实字符串的位置给保存下来。
这样我们就将init_array过程中所有还原出来的真实字符串给保存下来了。最后我们要将我们的真实字符串再写回so中。那么写回到哪里呢?我们监控内存写入时,当时是给什么地址写入数据。我们就写入到哪里。这样我们写入的so就能直接在ida中打开就看到真实字符串了。在写入时需要注意的是。我们保存的address是有一个基址的。也就是说。我们写入时需要减掉基址。才是真正应该写入的位置。
然后我贴上几行打印的内容
第一个offset的偏移是0x4004。那么我们打开ida检查一下。是否对的上
能看到ida里面显示的确实是对的上的。但是这里要留意最左下角的00003004。实际这个才是文件中的真实位置。所以我们这里直接写回so要再将offset-0x1000。下面我们开始将so的数据修改。并且保存成一个新的文件。最后贴上完整代码
那么实际效果怎么样呢。我们打开ida来对比一下修改前和修改后的效果
修改前
修改后
接着再使用unidbg来实现一次字符串的反混淆
按照上面的思路。我们首先设置内存写入的监控,然后加载so。而unidbg在加载的时候。就会自动的执行init和init_array。所以我们接下来看看unidbg是怎么进行内存的写入监控的。搜索一下unidbg源码。就能找到是如何使用的了。我们搜索hook_add_new
这里我们就知道了是设置了一个TraceMemoryHook对象进行监控的。由于我们是需要自己修改里面监控后的逻辑。所以不要变动他原有的。拷贝一份出来。然后删除里面原有的打印逻辑。再加上我们之前逻辑。也就是打印一下写入的数据。将监控到的写入数据和写入地址给保存起来。
然后我们按之前的步骤。使用这个类来hook。并且遍历一下我们保存出来的结果看和AndroidNativeEmu是不是一致的
然后看看打印的结果和之前是不是差不多的
对比AndroidNativeEmu的日志是正确的。那么我们完成最后的步骤。生成新的so文件,下面贴上完整代码
最后ida打开一下生成的so看看结果
最后我们将libcrypt.so以及libcrack.so都直接用unidbg跑一次。然后下面贴上ida查看这两个so的结果
Libcrypt.so的ida结果如下
Libcrack.so的ida结果如下
最后贴上三个例子的下载地址
链接: https://pan.baidu.com/s/1wAgjruBJe-a9T2Y63bKH7w 密码: 4qsj
贴上unidbg的字符串反混淆例子:
https://github.com/dqzg12300/unidbg_tools.git
/
*
Callback function
for
hooking memory (READ, WRITE & FETCH)
@type
: this memory
is
being READ,
or
WRITE
@address
: address where the code
is
being executed
@size
: size of data being read
or
written
@value
: value of data being written to memory,
or
irrelevant
if
type
=
READ.
@user_data
: user data passed to tracing APIs
*
/
/
*
Callback function
for
hooking memory (READ, WRITE & FETCH)
@type
: this memory
is
being READ,
or
WRITE
@address
: address where the code
is
being executed
@size
: size of data being read
or
written
@value
: value of data being written to memory,
or
irrelevant
if
type
=
READ.
@user_data
: user data passed to tracing APIs
*
/
import
logging
import
sys
from
unicorn
import
UC_HOOK_MEM_WRITE
import
struct
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__)
def
hook_mem_write(uc,
type
,address,size,value,userdata):
curdata
=
struct.pack(
"I"
,value)[:size]
print
(curdata)
emulator
=
Emulator(vfp_inst_set
=
True
)
emulator.load_library(
"example_binaries/libc.so"
, do_init
=
False
)
emulator.mu.hook_add(UC_HOOK_MEM_WRITE, hook_mem_write)
lib_module
=
emulator.load_library(
"example_binaries/obf.so"
, do_init
=
True
)
import
logging
import
sys
from
unicorn
import
UC_HOOK_MEM_WRITE
import
struct
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__)
def
hook_mem_write(uc,
type
,address,size,value,userdata):
curdata
=
struct.pack(
"I"
,value)[:size]
print
(curdata)
emulator
=
Emulator(vfp_inst_set
=
True
)
emulator.load_library(
"example_binaries/libc.so"
, do_init
=
False
)
emulator.mu.hook_add(UC_HOOK_MEM_WRITE, hook_mem_write)
lib_module
=
emulator.load_library(
"example_binaries/obf.so"
, do_init
=
True
)
dstr_datas
=
{}
def
hook_mem_write(uc,
type
,address,size,value,userdata):
curdata
=
struct.pack(
"I"
,value)[:size]
dstr_datas[address]
=
curdata
print
(curdata)
dstr_datas
=
{}
def
hook_mem_write(uc,
type
,address,size,value,userdata):
curdata
=
struct.pack(
"I"
,value)[:size]
dstr_datas[address]
=
curdata
print
(curdata)
base_addr
=
lib_module.base
sofile
=
open
(
"example_binaries/obf.so"
,
"rb"
)
sodata
=
sofile.read()
for
address,v
in
dstr_datas.items():
if
address > base_addr
and
address < base_addr
+
lib_module.size:
offset
=
address
-
base_addr
print
(
"address:0x%x data:%s offset:0x%x"
%
(address, v,offset))
base_addr
=
lib_module.base
sofile
=
open
(
"example_binaries/obf.so"
,
"rb"
)
sodata
=
sofile.read()
for
address,v
in
dstr_datas.items():
if
address > base_addr
and
address < base_addr
+
lib_module.size:
offset
=
address
-
base_addr
print
(
"address:0x%x data:%s offset:0x%x"
%
(address, v,offset))
address:
0xcbc6a004
data:b
'j'
offset:
0x4004
address:
0xcbc6a005
data:b
'n'
offset:
0x4005
address:
0xcbc6a006
data:b
'i'
offset:
0x4006
address:
0xcbc6a007
data:b
'\x00'
offset:
0x4007
address:
0xcbc6a008
data:b
'g'
offset:
0x4008
address:
0xcbc6a004
data:b
'j'
offset:
0x4004
address:
0xcbc6a005
data:b
'n'
offset:
0x4005
address:
0xcbc6a006
data:b
'i'
offset:
0x4006
address:
0xcbc6a007
data:b
'\x00'
offset:
0x4007
address:
0xcbc6a008
data:b
'g'
offset:
0x4008
import
logging
import
sys
from
unicorn
import
UC_HOOK_MEM_WRITE
import
struct
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__)
dstr_datas
=
{}
def
hook_mem_write(uc,
type
,address,size,value,userdata):
curdata
=
struct.pack(
"I"
,value)[:size]
dstr_datas[address]
=
curdata
emulator
=
Emulator(vfp_inst_set
=
True
)
emulator.load_library(
"example_binaries/libc.so"
, do_init
=
False
)
emulator.mu.hook_add(UC_HOOK_MEM_WRITE, hook_mem_write)
modulePath
=
"example_binaries/obf.so"
lib_module
=
emulator.load_library(modulePath, do_init
=
True
)
base_addr
=
lib_module.base
sofile
=
open
(
"example_binaries/obf.so"
,
"rb"
)
sodata
=
sofile.read()
sofile.close()
for
address,v
in
dstr_datas.items():
if
address > base_addr
and
address < base_addr
+
lib_module.size:
offset
=
address
-
base_addr
-
0x1000
print
(
"address:0x%x data:%s offset:0x%x"
%
(address, v,offset))
sodata
=
sodata[:offset]
+
v
+
sodata[offset
+
len
(v):]
savepath
=
modulePath
+
".new"
nfile
=
open
(savepath,
"wb"
)
nfile.write(sodata)
nfile.close()
import
logging
import
sys
from
unicorn
import
UC_HOOK_MEM_WRITE
import
struct
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__)
dstr_datas
=
{}
def
hook_mem_write(uc,
type
,address,size,value,userdata):
curdata
=
struct.pack(
"I"
,value)[:size]
dstr_datas[address]
=
curdata
emulator
=
Emulator(vfp_inst_set
=
True
)
emulator.load_library(
"example_binaries/libc.so"
, do_init
=
False
)
emulator.mu.hook_add(UC_HOOK_MEM_WRITE, hook_mem_write)
modulePath
=
"example_binaries/obf.so"
lib_module
=
emulator.load_library(modulePath, do_init
=
True
)
base_addr
=
lib_module.base
sofile
=
open
(
"example_binaries/obf.so"
,
"rb"
)
sodata
=
sofile.read()
sofile.close()
for
address,v
in
dstr_datas.items():
if
address > base_addr
and
address < base_addr
+
lib_module.size:
offset
=
address
-
base_addr
-
0x1000
print
(
"address:0x%x data:%s offset:0x%x"
%
(address, v,offset))
sodata
=
sodata[:offset]
+
v
+
sodata[offset
+
len
(v):]
savepath
=
modulePath
+
".new"
nfile
=
open
(savepath,
"wb"
)
nfile.write(sodata)
nfile.close()
class
DeStrWriteHook implements WriteHook {
private final boolean read;
DeStrWriteHook(boolean read) {
this.read
=
read;
}
PrintStream redirect;
TraceWriteListener traceWriteListener;
/
/
保存的写入数据地址和写入的数据
public
Map
<
Long
,byte[]> dstr_datas
=
new HashMap<
Long
,byte[]>();
/
*
*
*
long
类型转byte[] (大端)
*
@param n
*
@
return
*
/
public static byte[] longToBytesBig(
long
n) {
byte[] b
=
new byte[
8
];
b[
7
]
=
(byte) (n &
0xff
);
b[
6
]
=
(byte) (n >>
8
&
0xff
);
b[
5
]
=
(byte) (n >>
16
&
0xff
);
b[
4
]
=
(byte) (n >>
24
&
0xff
);
b[
3
]
=
(byte) (n >>
32
&
0xff
);
b[
2
]
=
(byte) (n >>
40
&
0xff
);
b[
1
]
=
(byte) (n >>
48
&
0xff
);
b[
0
]
=
(byte) (n >>
56
&
0xff
);
return
b;
}
/
*
*
*
long
类型转byte[] (小端)
*
@param n
*
@
return
*
/
public static byte[] longToBytesLittle(
long
n) {
byte[] b
=
new byte[
8
];
b[
0
]
=
(byte) (n &
0xff
);
b[
1
]
=
(byte) (n >>
8
&
0xff
);
b[
2
]
=
(byte) (n >>
16
&
0xff
);
b[
3
]
=
(byte) (n >>
24
&
0xff
);
b[
4
]
=
(byte) (n >>
32
&
0xff
);
b[
5
]
=
(byte) (n >>
40
&
0xff
);
b[
6
]
=
(byte) (n >>
48
&
0xff
);
b[
7
]
=
(byte) (n >>
56
&
0xff
);
return
b;
}
@Override
public void hook(Backend backend,
long
address,
int
size,
long
value,
Object
user) {
if
(read) {
return
;
}
try
{
Emulator<?> emulator
=
(Emulator<?>) user;
if
(traceWriteListener
=
=
null || traceWriteListener.onWrite(emulator, address, size, value)) {
/
/
将写入的地址和写入的数据保存下来,先转
long
为小端序
byte[] writedata
=
longToBytesLittle(value);
byte[] resizeWriteData
=
new byte[size];
/
/
将指定大小的数据保存
System.arraycopy(writedata,
0
,resizeWriteData,
0
,size);
dstr_datas.put(address,resizeWriteData);
}
} catch (BackendException e) {
throw new IllegalStateException(e);
}
}
}
class
DeStrWriteHook implements WriteHook {
private final boolean read;
DeStrWriteHook(boolean read) {
this.read
=
read;
}
PrintStream redirect;
TraceWriteListener traceWriteListener;
/
/
保存的写入数据地址和写入的数据
public
Map
<
Long
,byte[]> dstr_datas
=
new HashMap<
Long
,byte[]>();
/
*
*
*
long
类型转byte[] (大端)
*
@param n
*
@
return
*
/
public static byte[] longToBytesBig(
long
n) {
byte[] b
=
new byte[
8
];
b[
7
]
=
(byte) (n &
0xff
);
b[
6
]
=
(byte) (n >>
8
&
0xff
);
b[
5
]
=
(byte) (n >>
16
&
0xff
);
b[
4
]
=
(byte) (n >>
24
&
0xff
);
b[
3
]
=
(byte) (n >>
32
&
0xff
);
b[
2
]
=
(byte) (n >>
40
&
0xff
);
b[
1
]
=
(byte) (n >>
48
&
0xff
);
b[
0
]
=
(byte) (n >>
56
&
0xff
);
return
b;
}
/
*
*
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)