本文的思路是很久以前就有的,但是找不到一个合适的工具“拍摄快照”一直都没有成功实现,直到看到这篇文章才成功实现,感谢该文作者seeflower。如果原作者认为本文章不合适,请联系我删除,谢谢啦~
每次在虚拟机中打开ida,逆向一堆JniOnLoad或者.iniarray段被混淆的代码时。我都想着有没有一种方法,免去这些初始化流程(当然这些初始化操作是厂家的重点防护对象)。
有一天,工作完之后,手头上的jnionload还没有分析完,将VMware中运行的机器设置暂停,明天再分析。突然我想到,既然VMware能通过快照这种方式保存虚拟机的运行时状态,即使关机也可以恢复。那我能不能通过对Android APP”拍摄“一份”快照“(SnapShot),正好停在我想逆向的函数前,修改入参,然后反复运行这份快照,绕过初始化过程、绕过各种检测呢?
思路有了,接下来就是寻找工具,怎么去实现了。
为了能让Unidbg成功载入快照,拍摄内容:
有了目标,现在开始实现。
第一步:装载lldb(相机)
第二步:编写dump脚本
内存段(Segment)、寄存器(Register)的lldb Dump脚本
符号表(SymTab)的lldb Python 脚本:
第三步:按下快门
将脚本导入到lldb:
command script import lldb_dumper.py
command script import lldb_symb.py
附加到目标so:
触发断点以后,通过以下命令得到快照文件:
生成的文件有:
符号表json文件:libxxxxxxx.so_sym.json
dump文件夹:./dump/*
Register json文件:./dump/regs.json
Segment json文件:./dump/segments.json
Section json文件:./dump/sections.json
Unidbg是基于Unicorn增加JNI支持的Unicorn Plus Pro Ultra(就是牛皮哦~),所以Unidbg也通过了一系列Api对寄存器和内存操作:
恢复寄存器状态:
恢复内存状态:
因为执行快照时,并不需要一些内存段,这里过滤一下:
这里内存文件已经zlib压缩,需要解压后再写入内存中。
运行快照:
有时候,会出现Memory unmap的情况,这个时候,在Segments.json,找到对应地址,将name 添加到whileList里面即可。
用unidbg 的libc 替换内存段中的libc,便于调试:
有大佬能帮忙看看这些问题是什么原因么:
$NDK_PATH
/
toolchains
/
llvm
/
prebuilt
/
linux
-
x86_64
/
lib64
/
clang
/
9.0
.
8
/
lib
/
linux
/
aarch64
/
lldb
-
server
adb push lldb
-
server
/
data
/
local
/
tmp
/
adb shell
cd
/
data
/
local
/
tmp
chmod
755
lldb
-
server
.
/
lldb
-
server p
-
-
server
-
-
listen unix
-
abstract:
/
/
/
data
/
local
/
tmp
/
debug.sock
$ lldb
platform select remote
-
android
platform connect unix
-
abstract
-
connect:
/
/
/
data
/
local
/
tmp
/
debug.sock
$NDK_PATH
/
toolchains
/
llvm
/
prebuilt
/
linux
-
x86_64
/
lib64
/
clang
/
9.0
.
8
/
lib
/
linux
/
aarch64
/
lldb
-
server
adb push lldb
-
server
/
data
/
local
/
tmp
/
adb shell
cd
/
data
/
local
/
tmp
chmod
755
lldb
-
server
.
/
lldb
-
server p
-
-
server
-
-
listen unix
-
abstract:
/
/
/
data
/
local
/
tmp
/
debug.sock
$ lldb
platform select remote
-
android
platform connect unix
-
abstract
-
connect:
/
/
/
data
/
local
/
tmp
/
debug.sock
from
asyncio.log
import
logger
from
datetime
import
datetime
import
hashlib
import
json
from
pathlib
import
Path
from
typing
import
TYPE_CHECKING,
Dict
,
List
import
sys
import
zlib
import
os
if
TYPE_CHECKING:
from
lldb
import
*
import
lldb
black_list
=
{
'startswith'
: [
'/dev'
,
'/system/fonts'
,
'/dmabuf'
],
'endswith'
: [
'(deleted)'
,
'.apk'
,
'.odex'
,
'.vdex'
,
'.dex'
,
'.jar'
,
'.art'
,
'.oat'
,
'.art]'
],
'includes'
: [],
}
dump_path
=
Path(
"./dump/"
)
def
__lldb_init_module(debugger:
'SBDebugger'
, internal_dict:
dict
):
debugger.HandleCommand(
'command script add -f lldb_dumper.dumpMem dumpmem'
)
def
dumpMem(debugger:
'SBDebugger'
, command:
str
, exe_ctx:
'SBExecutionContext'
, result:
'SBCommandReturnObject'
, internal_dict:
dict
):
target
=
exe_ctx.GetTarget()
arch_long
=
dump_arch_info(target)
frame
=
exe_ctx.GetFrame()
regs
=
dump_regs(frame)
target
=
exe_ctx.GetTarget()
sections
=
dump_memory_info(target)
max_seg_size
=
64
*
1024
*
1024
process
=
exe_ctx.GetProcess()
segments
=
dump_memory(process, dump_path, black_list, max_seg_size)
(dump_path
/
"regs.json"
).write_text(json.dumps(regs))
(dump_path
/
"sections.json"
).write_text(json.dumps(sections))
(dump_path
/
"segments.json"
).write_text(json.dumps(segments))
def
dump_arch_info(target:
'SBTarget'
):
triple
=
target.GetTriple()
print
(f
'[dump_arch_info] triple => {triple}'
)
arch, vendor, sys, abi
=
triple.split(
'-'
)
if
arch
=
=
'aarch64'
or
arch
=
=
'arm64'
:
return
'arm64le'
elif
arch
=
=
'aarch64_be'
:
return
'arm64be'
elif
arch
=
=
'armeb'
:
return
'armbe'
elif
arch
=
=
'arm'
:
return
'armle'
else
:
return
''
def
dump_regs(frame:
'SBFrame'
):
regs
=
{}
registers
=
None
for
registers
in
frame.GetRegisters():
print
(f
'registers name => {registers.GetName()}'
)
for
register
in
registers:
register_name
=
register.GetName()
register.SetFormat(lldb.eFormatHex)
register_value
=
register.GetValue()
regs[register_name]
=
register_value
print
(f
'regs => {json.dumps(regs, ensure_ascii=False, indent=4)}'
)
return
regs
def
dump_memory_info(target:
'SBTarget'
):
logger.debug(
'start dump_memory_info'
)
sections
=
[]
for
module
in
target.module_iter():
module: SBModule
for
section
in
module.section_iter():
section: SBSection
module_name
=
module.
file
.GetFilename()
start, end, size, name
=
get_section_info(target, section)
section_info
=
{
'module'
: module_name,
'start'
: start,
'end'
: end,
'size'
: size,
'name'
: name,
}
print
(f
'Appending: {name}'
)
sections.append(section_info)
return
sections
def
get_section_info(tar, sec):
name
=
sec.name
if
sec.name
is
not
None
else
""
if
sec.GetParent().name
is
not
None
:
name
=
sec.GetParent().name
+
"."
+
sec.name
module_name
=
sec.addr.module.
file
.GetFilename()
module_name
=
module_name
if
module_name
is
not
None
else
""
long_name
=
module_name
+
"."
+
name
return
sec.GetLoadAddress(tar), (sec.GetLoadAddress(tar)
+
sec.size), sec.size, long_name
def
dump_memory(process:
'SBProcess'
, dump_path: Path, black_list:
Dict
[
str
,
List
[
str
]], max_seg_size:
int
):
logger.debug(
'start dump memory'
)
memory_list
=
[]
mem_info
=
lldb.SBMemoryRegionInfo()
start_addr
=
-
1
next_region_addr
=
0
while
next_region_addr > start_addr:
err
=
process.GetMemoryRegionInfo(next_region_addr, mem_info)
if
not
err.success:
logger.warning(f
'GetMemoryRegionInfo failed, {err}, break'
)
break
next_region_addr
=
mem_info.GetRegionEnd()
if
next_region_addr >
=
sys.maxsize:
logger.info(f
'next_region_addr:0x{next_region_addr:x} >= sys.maxsize, break'
)
break
start
=
mem_info.GetRegionBase()
end
=
mem_info.GetRegionEnd()
region_name
=
'UNKNOWN'
if
mem_info.IsMapped():
name
=
mem_info.GetName()
if
name
is
None
:
name
=
''
mem_info_obj
=
{
'start'
: start,
'end'
: end,
'name'
: name,
'permissions'
: {
'r'
: mem_info.IsReadable(),
'w'
: mem_info.IsWritable(),
'x'
: mem_info.IsExecutable(),
},
'content_file'
: '',
}
memory_list.append(mem_info_obj)
for
seg_info
in
memory_list:
try
:
start_addr
=
seg_info[
'start'
]
end_addr
=
seg_info[
'end'
]
region_name
=
seg_info[
'name'
]
permissions
=
seg_info[
'permissions'
]
if
seg_info[
'permissions'
][
'r'
]
is
False
:
logger.warning(f
'Skip dump {region_name} permissions => {permissions}'
)
continue
predicted_size
=
end_addr
-
start_addr
if
predicted_size > max_seg_size:
logger.warning(f
'Skip dump {region_name} size:0x{predicted_size:x}'
)
continue
skip_dump
=
False
for
rule
in
black_list[
'startswith'
]:
if
region_name.startswith(rule):
skip_dump
=
True
logger.warning(f
'Skip dump {region_name} hit startswith rule:{rule}'
)
if
skip_dump:
continue
for
rule
in
black_list[
'endswith'
]:
if
region_name.endswith(rule):
skip_dump
=
True
logger.warning(f
'Skip dump {region_name} hit endswith rule:{rule}'
)
if
skip_dump:
continue
for
rule
in
black_list[
'includes'
]:
if
rule
in
region_name:
skip_dump
=
True
logger.warning(f
'Skip dump {region_name} hit includes rule:{rule}'
)
if
skip_dump:
continue
ts
=
datetime.now()
err
=
lldb.SBError()
seg_content
=
process.ReadMemory(start_addr, predicted_size, err)
tm
=
(datetime.now()
-
ts).total_seconds()
if
seg_content
is
None
:
logger.debug(f
'Segment empty: @0x{start_addr:016x} {region_name} => {err}'
)
else
:
logger.info(f
'Dumping @0x{start_addr:016x} {tm:.2f}s size:0x{len(seg_content):x}: {region_name} {permissions}'
)
compressed_seg_content
=
zlib.compress(seg_content)
md5_sum
=
hashlib.md5(compressed_seg_content).hexdigest()
+
'.bin'
seg_info[
'content_file'
]
=
md5_sum
(dump_path
/
md5_sum).write_bytes(compressed_seg_content)
except
Exception as e:
logger.error(f
'Exception reading segment {region_name}'
, exc_info
=
e)
return
memory_list
from
asyncio.log
import
logger
from
datetime
import
datetime
import
hashlib
import
json
from
pathlib
import
Path
from
typing
import
TYPE_CHECKING,
Dict
,
List
import
sys
import
zlib
import
os
if
TYPE_CHECKING:
from
lldb
import
*
import
lldb
black_list
=
{
'startswith'
: [
'/dev'
,
'/system/fonts'
,
'/dmabuf'
],
'endswith'
: [
'(deleted)'
,
'.apk'
,
'.odex'
,
'.vdex'
,
'.dex'
,
'.jar'
,
'.art'
,
'.oat'
,
'.art]'
],
'includes'
: [],
}
dump_path
=
Path(
"./dump/"
)
def
__lldb_init_module(debugger:
'SBDebugger'
, internal_dict:
dict
):
debugger.HandleCommand(
'command script add -f lldb_dumper.dumpMem dumpmem'
)
def
dumpMem(debugger:
'SBDebugger'
, command:
str
, exe_ctx:
'SBExecutionContext'
, result:
'SBCommandReturnObject'
, internal_dict:
dict
):
target
=
exe_ctx.GetTarget()
arch_long
=
dump_arch_info(target)
frame
=
exe_ctx.GetFrame()
regs
=
dump_regs(frame)
target
=
exe_ctx.GetTarget()
sections
=
dump_memory_info(target)
max_seg_size
=
64
*
1024
*
1024
process
=
exe_ctx.GetProcess()
segments
=
dump_memory(process, dump_path, black_list, max_seg_size)
(dump_path
/
"regs.json"
).write_text(json.dumps(regs))
(dump_path
/
"sections.json"
).write_text(json.dumps(sections))
(dump_path
/
"segments.json"
).write_text(json.dumps(segments))
def
dump_arch_info(target:
'SBTarget'
):
triple
=
target.GetTriple()
print
(f
'[dump_arch_info] triple => {triple}'
)
arch, vendor, sys, abi
=
triple.split(
'-'
)
if
arch
=
=
'aarch64'
or
arch
=
=
'arm64'
:
return
'arm64le'
elif
arch
=
=
'aarch64_be'
:
return
'arm64be'
elif
arch
=
=
'armeb'
:
return
'armbe'
elif
arch
=
=
'arm'
:
return
'armle'
else
:
return
''
def
dump_regs(frame:
'SBFrame'
):
regs
=
{}
registers
=
None
for
registers
in
frame.GetRegisters():
print
(f
'registers name => {registers.GetName()}'
)
for
register
in
registers:
register_name
=
register.GetName()
register.SetFormat(lldb.eFormatHex)
register_value
=
register.GetValue()
regs[register_name]
=
register_value
print
(f
'regs => {json.dumps(regs, ensure_ascii=False, indent=4)}'
)
return
regs
def
dump_memory_info(target:
'SBTarget'
):
logger.debug(
'start dump_memory_info'
)
sections
=
[]
for
module
in
target.module_iter():
module: SBModule
for
section
in
module.section_iter():
section: SBSection
module_name
=
module.
file
.GetFilename()
start, end, size, name
=
get_section_info(target, section)
section_info
=
{
'module'
: module_name,
'start'
: start,
'end'
: end,
'size'
: size,
'name'
: name,
}
print
(f
'Appending: {name}'
)
sections.append(section_info)
return
sections
def
get_section_info(tar, sec):
name
=
sec.name
if
sec.name
is
not
None
else
""
if
sec.GetParent().name
is
not
None
:
name
=
sec.GetParent().name
+
"."
+
sec.name
module_name
=
sec.addr.module.
file
.GetFilename()
module_name
=
module_name
if
module_name
is
not
None
else
""
long_name
=
module_name
+
"."
+
name
return
sec.GetLoadAddress(tar), (sec.GetLoadAddress(tar)
+
sec.size), sec.size, long_name
def
dump_memory(process:
'SBProcess'
, dump_path: Path, black_list:
Dict
[
str
,
List
[
str
]], max_seg_size:
int
):
logger.debug(
'start dump memory'
)
memory_list
=
[]
mem_info
=
lldb.SBMemoryRegionInfo()
start_addr
=
-
1
next_region_addr
=
0
while
next_region_addr > start_addr:
err
=
process.GetMemoryRegionInfo(next_region_addr, mem_info)
if
not
err.success:
logger.warning(f
'GetMemoryRegionInfo failed, {err}, break'
)
break
next_region_addr
=
mem_info.GetRegionEnd()
if
next_region_addr >
=
sys.maxsize:
logger.info(f
'next_region_addr:0x{next_region_addr:x} >= sys.maxsize, break'
)
break
start
=
mem_info.GetRegionBase()
end
=
mem_info.GetRegionEnd()
region_name
=
'UNKNOWN'
if
mem_info.IsMapped():
name
=
mem_info.GetName()
if
name
is
None
:
name
=
''
mem_info_obj
=
{
'start'
: start,
'end'
: end,
'name'
: name,
'permissions'
: {
'r'
: mem_info.IsReadable(),
'w'
: mem_info.IsWritable(),
'x'
: mem_info.IsExecutable(),
},
'content_file'
: '',
}
memory_list.append(mem_info_obj)
for
seg_info
in
memory_list:
try
:
start_addr
=
seg_info[
'start'
]
end_addr
=
seg_info[
'end'
]
region_name
=
seg_info[
'name'
]
permissions
=
seg_info[
'permissions'
]
if
seg_info[
'permissions'
][
'r'
]
is
False
:
logger.warning(f
'Skip dump {region_name} permissions => {permissions}'
)
continue
predicted_size
=
end_addr
-
start_addr
if
predicted_size > max_seg_size:
logger.warning(f
'Skip dump {region_name} size:0x{predicted_size:x}'
)
continue
skip_dump
=
False
for
rule
in
black_list[
'startswith'
]:
if
region_name.startswith(rule):
skip_dump
=
True
logger.warning(f
'Skip dump {region_name} hit startswith rule:{rule}'
)
if
skip_dump:
continue
for
rule
in
black_list[
'endswith'
]:
if
region_name.endswith(rule):
skip_dump
=
True
logger.warning(f
'Skip dump {region_name} hit endswith rule:{rule}'
)
if
skip_dump:
continue
for
rule
in
black_list[
'includes'
]:
if
rule
in
region_name:
skip_dump
=
True
logger.warning(f
'Skip dump {region_name} hit includes rule:{rule}'
)
if
skip_dump:
continue
ts
=
datetime.now()
err
=
lldb.SBError()
seg_content
=
process.ReadMemory(start_addr, predicted_size, err)
tm
=
(datetime.now()
-
ts).total_seconds()
if
seg_content
is
None
:
logger.debug(f
'Segment empty: @0x{start_addr:016x} {region_name} => {err}'
)
else
:
logger.info(f
'Dumping @0x{start_addr:016x} {tm:.2f}s size:0x{len(seg_content):x}: {region_name} {permissions}'
)
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课