2017年发表过这篇文章,当时图片全部上传kanxue.com,然后在bbs.pediy.com中引用图片既然不显示,然后超过2年帖子不能编辑了,于是重发补图和附件。
Part 1 - 利用IDA、objdump、gdb等静态分析或调试器优先加载符号表的特性,从而通过修改节.symtab中符号字符信息来欺骗这些工具)
这个样本是某个主程序调用的动态库,我之前分析了很多 次,用IDA打开查看导出函数所有的函数名称都是乱起八糟没有意义的,之前以为是所有的函数名称都被加密混淆了。同时我也用IDA和gdb调试了很多次,知道这个样本中的代码是加密的,运行的时候会动态解密,解密函数名称为tf_rkg_vavg, 这个函数内部会调用mprotect并递归调用自己来解密代码,解密成功后这个函数在末尾会跳转到已解密的ragel_byq去执行。其中ragel_byq函数是被加密的,反汇编显示的是乱起八糟的汇编指令,如下图所示。
ragel_byq函数解密前
之前调试的时候一直都是在主程序下调试,然后这个动态库里面下断点,由于主程序很大所以很不方便,于是想直接写个Demo去调用这个动态库,然后去解密看是否可行。
vim unpack.c
源码如下:
#include<stdio.h>
#include<dlfcn.h>
int main()
{
void *handle1 = dlopen("./98750.so", RTLD_NOW); //程序虽然在同一个目录,必须带./,否则找不到
if (NULL == handle1)
fprintf(stderr, "Failed to open!\n");
int (* f)()=(int(*)()) dlsym(handle1, "tf_rkg_vavg");
if (f != NULL)
f();
dlclose(handle1);
return 0;
}
编译
gcc -g -o unpack unpack.c -ldl
调试以上这个程序,发现dlsym返回空,特别奇怪,当时觉得是不是C++ mangling之类的符号改变,导出符号没写对导致。
网上查了一下查看ELF动态库导出函数方法
(查看so动态库导出函数)方法一: readelf -DsW 98750.so [| cat -n | grep tf_rkg_vavg ]
该命令不仅显示符号表信息,还会显示.gnu.hash节信息
输出如下:
root@speedlinux:/home/test# readelf -Ds 98750.so | cat -n
1
2 Symbol table for image:
3 Num Buc: Value Size Type Bind Vis Ndx Name
4 58 0: 00015564 4 OBJECT GLOBAL DEFAULT 23 CGameCreature_GetMoney
5 213 0: 00015608 4 OBJECT GLOBAL DEFAULT 23 CGuild_GetMemberOnlineCou
6 264 0: 00015618 4 OBJECT GLOBAL DEFAULT 23 ItemBag_RemoveItem
7 168 2: 0000f26d 51 FUNC GLOBAL DEFAULT 12 KickFix2
8 220 9: 00007ddc 267 FUNC GLOBAL DEFAULT 12 guser_subpoint
9 181 10: 000153e4 4 OBJECT GLOBAL DEFAULT 23 pdbr
10 160 11: 000153d0 4 OBJECT GLOBAL DEFAULT 23 lastDidList
11 195 11: 0000b6c3 63 FUNC GLOBAL DEFAULT 12 MyCreEnterWorld
12 135 12: 00007ba0 78 FUNC GLOBAL DEFAULT 12 freedbret
13 201 12: 0000a203 156 FUNC GLOBAL DEFAULT 12 InsertCode1
14 108 13: 00007d17 197 FUNC GLOBAL DEFAULT 12 guser_addpoint
15 202 13: 0000a2b3 175 FUNC GLOBAL DEFAULT 12 InsertCode2
16 204 14: 0000a3a6 67 FUNC GLOBAL DEFAULT 12 InsertCode3
17 102 15: 0001555c 4 OBJECT GLOBAL DEFAULT 23 timenow
。。。
323 Symbol table of `.gnu.hash' for image:
324 Num Buc: Value Size Type Bind Vis Ndx Name
325 48 0: 00015600 4 OBJECT GLOBAL DEFAULT 23 constring
326 49 2: 0000b70f 437 FUNC GLOBAL DEFAULT 12 InsertCreEnterWorld
327 50 4: 0000e403 1054 FUNC GLOBAL DEFAULT 12 item_rnd_ling
328 51 5: 00007f48 94 FUNC GLOBAL DEFAULT 12 guser_offline
329 52 7: 00007688 79 FUNC GLOBAL DEFAULT 12 GetValue
330 53 10: 00012025 1682 FUNC GLOBAL DEFAULT 12 entry_old
331 54 10: 0000b8fb 228 FUNC GLOBAL DEFAULT 12 do_gameuser_getguild
332 55 10: 000151e0 36 OBJECT GLOBAL DEFAULT 22 st_1
333 56 11: 00015220 36 OBJECT GLOBAL DEFAULT 22 st_2
334 57 12: 00015260 36 OBJECT GLOBAL DEFAULT 22 st_3
335 58 13: 00015564 4 OBJECT GLOBAL DEFAULT 23 CGameCreature_GetMoney
336 59 13: 000152a0 36 OBJECT GLOBAL DEFAULT 22 st_4
337 60 13: 0000d68d 423 FUNC GLOBAL DEFAULT 12 IdentifyItem_addition
338 61 14: 000152e0 36 OBJECT GLOBAL DEFAULT 22 st_5
。。。
588 311 255: 000082bb 539 FUNC GLOBAL DEFAULT 12 add_newatt
589 312 258: 000100a0 75 FUNC GLOBAL DEFAULT 12 GactName
590 313 259: 00015544 4 OBJECT GLOBAL DEFAULT 23 ItemBag_GetItem
591 314 259: 00015730 4 OBJECT GLOBAL DEFAULT 23 CDBAgentResult_Clear
592 315 260: 0000f2ef 36 FUNC GLOBAL DEFAULT 12 CGS_SendPacketFackEnd
593 316 260: 0001563c 4 OBJECT GLOBAL DEFAULT 23 CGameWorld__GetGameSystem
594 317 261: 00015044 4 OBJECT GLOBAL DEFAULT 22 lingid
595 318 262: 000155f4 4 OBJECT GLOBAL DEFAULT 23 CChatItems_CChatItems
注意到-D选项说明,表示显示符号的时候使用动态节信息(即.dymbol)
readelf 帮助
Usage: readelf <option(s)> elf-file(s)
Display information about the contents of ELF format files
Options are:
-a --all Equivalent to: -h -l -S -s -r -d -V -A -I
-h --file-header Display the ELF file header
-l --program-headers Display the program headers
--segments An alias for --program-headers
-S --section-headers Display the sections' header
--sections An alias for --section-headers
-g --section-groups Display the section groups
-t --section-details Display the section details
-e --headers Equivalent to: -h -l -S
-s --syms Display the symbol table
--symbols An alias for --syms
--dyn-syms Display the dynamic symbol table
-n --notes Display the core notes (if present)
-r --relocs Display the relocations (if present)
-u --unwind Display the unwind info (if present)
-d --dynamic Display the dynamic section (if present)
-V --version-info Display the version sections (if present)
-A --arch-specific Display architecture specific information (if any).
-c --archive-index Display the symbol/file index in an archive
-D --use-dynamic Use the dynamic section info when displaying symbols
-x --hex-dump=<number|name>
Dump the contents of section <number|name> as bytes
-p --string-dump=<number|name>
Dump the contents of section <number|name> as strings
-R --relocated-dump=<number|name>
Dump the contents of section <number|name> as relocated bytes
-w[lLiaprmfFsoRt] or
--debug-dump[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames,
=frames-interp,=str,=loc,=Ranges,=pubtypes,
=gdb_index,=trace_info,=trace_abbrev,=trace_aranges]
Display the contents of DWARF2 debug sections
--dwarf-depth=N Do not display DIEs at depth N or greater
--dwarf-start=N Display DIEs starting with N, at the same depth
or deeper
-I --histogram Display histogram of bucket list lengths
-W --wide Allow output width to exceed 80 characters
@<file> Read options from <file>
-H --help Display this information
-v --version Display the version number of readelf
Report bugs to <http://www.sourceware.org/bugzilla/>
通过readelf -S 98750.s查看所有节信息,输出如下所示
root@speedlinux:/home/test# readelf -S 98750.so
There are 28 section headers, starting at offset 0x14484:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .note.gnu.build-i NOTE 000000f4 0000f4 000024 00 A 0 0 4
[ 2] .hash HASH 00000118 000118 000920 04 A 4 0 4
[ 3] .gnu.hash GNU_HASH 00000a38 000a38 000968 04 A 4 0 4
[ 4] .dynsym DYNSYM 000013a0 0013a0 0013f0 10 A 5 1 4
[ 5] .dynstr STRTAB 00002790 002790 001022 00 A 0 0 1
[ 6] .gnu.version VERSYM 000037b2 0037b2 00027e 02 A 4 0 2
[ 7] .gnu.version_r VERNEED 00003a30 003a30 000040 00 A 5 1 4
[ 8] .rel.dyn REL 00003a70 003a70 003708 08 A 4 0 4
[ 9] .rel.plt REL 00007178 007178 000018 08 A 4 11 4
[10] .init PROGBITS 00007190 007190 000030 00 AX 0 0 4
[11] .plt PROGBITS 000071c0 0071c0 000040 04 AX 0 0 4
[12] .text PROGBITS 00007200 007200 00b9c8 00 AX 0 0 16
[13] .fini PROGBITS 00012bc8 012bc8 00001c 00 AX 0 0 4
[14] .rodata PROGBITS 00012be4 012be4 000f0c 00 A 0 0 4
[15] .eh_frame PROGBITS 00013af0 013af0 000004 00 A 0 0 4
[16] .ctors PROGBITS 00014f04 013f04 000008 00 WA 0 0 4
[17] .dtors PROGBITS 00014f0c 013f0c 000008 00 WA 0 0 4
[18] .jcr PROGBITS 00014f14 013f14 000004 00 WA 0 0 4
[19] .dynamic DYNAMIC 00014f18 013f18 0000d0 08 WA 5 0 4
[20] .got PROGBITS 00014fe8 013fe8 00000c 04 WA 0 0 4
[21] .got.plt PROGBITS 00014ff4 013ff4 000018 04 WA 0 0 4
[22] .data PROGBITS 00015020 014020 000364 00 WA 0 0 32
[23] .bss NOBITS 000153a0 014384 00039c 00 WA 0 0 32
[24] .comment PROGBITS 00000000 014384 000025 01 MS 0 0 1
[25] .shstrtab STRTAB 00000000 0143a9 0000d8 00 0 0 1
[26] .symtab SYMTAB 00000000 0148e4 0016d0 10 27 47 4
[27] .strtab STRTAB 00000000 015fb4 001367 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
试了直接单独-D选项是没有输出的,必须结合-s选项,但是,单独-s选项却可以输出,而且输出包含了tf_rkg_vavg函数名称,如下表所示,
从表中输出可以看出,readelf -s xxx.so 只是输出.dynsym和.symtab这两个节的的内容,而tf_rkg_vavg函数名称出现在.symtab节中。
root@speedlinux:/home/test# readelf -s 98750.so | cat -n | grep tf_rkg_vavg
684 358: 000126b7 1168 FUNC GLOBAL DEFAULT 12 tf_rkg_vavg
root@speedlinux:/home/test# readelf -s 98750.so | cat -n
1
2 Symbol table '.dynsym' contains 319 entries:
3 Num: Value Size Type Bind Vis Ndx Name
4 0: 00000000 0 NOTYPE LOCAL DEFAULT UND
5 1: 00000000 0 FUNC GLOBAL DEFAULT UND getpagesize@GLIBC_2.0 (2)
6 2: 00000000 0 FUNC GLOBAL DEFAULT UND __errno_location@GLIBC_2.0 (2)
。。。
319 315: 0000f2ef 36 FUNC GLOBAL DEFAULT 12 CGS_SendPacketFackEnd
320 316: 0001563c 4 OBJECT GLOBAL DEFAULT 23 CGameWorld__GetGameSystem
321 317: 00015044 4 OBJECT GLOBAL DEFAULT 22 lingid
322 318: 000155f4 4 OBJECT GLOBAL DEFAULT 23 CChatItems_CChatItems
323
324 Symbol table '.symtab' contains 365 entries:
325 Num: Value Size Type Bind Vis Ndx Name
326 0: 00000000 0 NOTYPE LOCAL DEFAULT UND
327 1: 000000f4 0 SECTION LOCAL DEFAULT 1
328 2: 00000118 0 SECTION LOCAL DEFAULT 2
329 3: 00000a38 0 SECTION LOCAL DEFAULT 3
330 4: 000013a0 0 SECTION LOCAL DEFAULT 4
331 5: 00002790 0 SECTION LOCAL DEFAULT 5
332 6: 000037b2 0 SECTION LOCAL DEFAULT 6
333 7: 00003a30 0 SECTION LOCAL DEFAULT 7
334 8: 00003a70 0 SECTION LOCAL DEFAULT 8
。。。
685 359: 0001504c 12 OBJECT GLOBAL DEFAULT 22 uengrf
686 360: 0000f313 354 FUNC GLOBAL DEFAULT 12 VafregPTF_FraqCnpxrgSnpx
687 361: 00007501 150 FUNC GLOBAL DEFAULT 12 JevgrPbqrZrzbel
688 362: 0000e391 114 FUNC GLOBAL DEFAULT 12 HaFvtarqVgrz_ubbx
689 363: 00015738 4 OBJECT GLOBAL DEFAULT 23 PFxvyyPbagnvare_NqqFxvyy
690 364: 00007190 0 FUNC GLOBAL DEFAULT 10 _vavg
root@speedlinux:/home/test#
(查看so动态库导出函数)方法二:nm -D 98750.so
这个方法同样没有包含tf_rkg_vavg这个函数
root@speedlinux:/home/test# nm -D 98750.so | cat -n
1 000153b4 B ACEXFdList
2 00015534 B BroadcastMedia
3 0000d5f5 T BroadcastMediaEx
4 000154d0 B BroadcastMediaForScript
5 00015524 B Bulletin
6 000154cc B BulletinEx
7 000156e0 B CAttribMgr_GetAttrib
8 000155f4 B CChatItems_CChatItems
9 00015538 B CChatItems_Clear
10 00015648 B CChatItems_Push
11 00015710 B CDBAgentClt_PushSQLCmd
12 00015730 B CDBAgentResult_Clear
13 0001554c B CDBAgentResult_NextRow
。。。
311 000153ec B szXMLTemplate
312 00015048 D szhid
313 U time
314 0001555c B timenow
315 000153ac B tpo3
316 000153a8 B tpos
317 00007597 T trim
318 0000988e T xorbuf
root@speedlinux:/home/test# nm -D 98750.so | cat -n | grep tf_rkg_vavg
nm的帮助选项如下:
root@speedlinux:/home/test# nm -q 98750.so
nm: invalid option -- 'q'
Usage: nm [option(s)] [file(s)]
List symbols in [file(s)] (a.out by default).
The options are:
-a, --debug-syms Display debugger-only symbols
-A, --print-file-name Print name of the input file before every symbol
-B Same as --format=bsd
-C, --demangle[=STYLE] Decode low-level symbol names into user-level names
The STYLE, if specified, can be `auto' (the default),
`gnu', `lucid', `arm', `hp', `edg', `gnu-v3', `java'
or `gnat'
--no-demangle Do not demangle low-level symbol names
-D, --dynamic Display dynamic symbols instead of normal symbols
--defined-only Display only defined symbols
-e (ignored)
-f, --format=FORMAT Use the output format FORMAT. FORMAT can be `bsd',
`sysv' or `posix'. The default is `bsd'
-g, --extern-only Display only external symbols
-l, --line-numbers Use debugging information to find a filename and
line number for each symbol
-n, --numeric-sort Sort symbols numerically by address
-o Same as -A
-p, --no-sort Do not sort the symbols
-P, --portability Same as --format=posix
-r, --reverse-sort Reverse the sense of the sort
--plugin NAME Load the specified plugin
-S, --print-size Print size of defined symbols
-s, --print-armap Include index for symbols from archive members
--size-sort Sort symbols by size
--special-syms Include special symbols in the output
--synthetic Display synthetic symbols as well
-t, --radix=RADIX Use RADIX for printing symbol values
--target=BFDNAME Specify the target object format as BFDNAME
-u, --undefined-only Display only undefined symbols
-X 32_64 (ignored)
@FILE Read options from FILE
-h, --help Display this information
-V, --version Display this program's version number
nm: supported targets: elf32-i386 a.out-i386-linux pei-i386 elf32-little elf32-big elf64-x86-64 elf32-x86-64 pei-x86-64 elf64-l1om elf64-k1om elf64-little elf64-big plugin srec symbolsrec verilog tekhex binary ihex trad-core
从nm的帮助看有--demangle(即-C)选项,显示C++改编前的函数名称,当时想以上查看导出函数的两个方法对于C++函数是不是不能能够显示出来呢,而98750.so中的tf_rkg_vavg和ragel_byq这两个函数正好是C++函数,也就是说98750.so正好是C++函数。同时看到nm中还有个-a显示调试符号的选项,于是是试了一下如下输出
root@speedlinux:/home/test# nm -a 98750.so | cat -n
1 000153a0 b .bss
2 00000000 n .comment
3 00014f04 d .ctors
4 00015020 d .data
5 00014f0c d .dtors
6 00014f18 d .dynamic
7 00002790 r .dynstr
8 000013a0 r .dynsym
9 00013af0 r .eh_frame
10 00012bc8 t .fini
11 00000a38 r .gnu.hash
12 000037b2 r .gnu.version
13 00003a30 r .gnu.version_r
14 00014fe8 d .got
15 00014ff4 d .got.plt
16 00000118 r .hash
17 00007190 t .init
18 00014f14 d .jcr
19 000000f4 r .note.gnu.build-id
20 000071c0 t .plt
21 00003a70 r .rel.dyn
22 00007178 r .rel.plt
23 00012be4 r .rodata
24 00007200 t .text
25 00015528 B BVQ2CGE
26 00007bee T BaQOTrgCnlTQ
27 00007fa6 T BaYbtvarqFpber
28 000156ec B ErfSvaqAcp
29 00015514 B FHfreCrgQngn_QryCrg
。。。
353 00015620 B yu_12
354 0001556c B yu_13
355 0000902c T yvat_nqq
356 00015044 D yvatvq
357 U zcebgrpg@@TYVOP_2.0
358 0000b8c4 T zl_tnzrhfre_trgthvyq
359 0001505c D znkfzy
360 00015718 B znp
361 U znyybp@@TYVOP_2.0
362 U zrzfrg@@TYVOP_2.0
363 U zrzpcl@@TYVOP_2.0
364 U zrzpzc@@TYVOP_2.0
root@speedlinux:/home/test# nm -a 98750.so | cat -n | grep tf_rkg_vavg
322 000126b7 T tf_rkg_vavg
root@speedlinux:/home/test# nm -C 98750.so | cat -n
1 00015528 B BVQ2CGE
2 00007bee T BaQOTrgCnlTQ
3 00007fa6 T BaYbtvarqFpber
4 000156ec B ErfSvaqAcp
5 00015514 B FHfreCrgQngn_QryCrg
6 00015548 B FHfreCrgQngn_SvaqCrg
7 000155e4 B FHfreCrgQngn_TrgAhz
8 00015728 B FHfreCrgQngn_TrgPheePneelvatCrg
9 0001572c B FNggevoNqqgvba_TrgNqqPbhag
。。。。
326 00015620 B yu_12
327 0001556c B yu_13
328 0000902c T yvat_nqq
329 00015044 D yvatvq
330 U zcebgrpg@@TYVOP_2.0
331 0000b8c4 T zl_tnzrhfre_trgthvyq
332 0001505c D znkfzy
333 00015718 B znp
334 U znyybp@@TYVOP_2.0
335 U zrzfrg@@TYVOP_2.0
336 U zrzpcl@@TYVOP_2.0
337 U zrzpzc@@TYVOP_2.0
root@speedlinux:/home/test# nm -C 98750.so | cat -n | grep tf_rkg_vavg
296 000126b7 T tf_rkg_vavg
root@speedlinux:/home/test#
从以上输出看-a和-C确实可以显示出来,这两个函数名称也是从gdb和IDA里面看到并拷贝出来的,那么为什么objdump -D就显示不出来当时很是纳闷,到底跟C++编译器函数名称修饰的改编有没有关系,详细查了mangling和demangle的资料其实根本没有任何关系,C++改编也会有规律可循的,而且经过C++改编后是可以通过c++filt工具还原成原函数的,详细可以查阅c++filt的帮助说明,而且nm --demangle就是用来将符号中改编过的函数还原显示成原始函数的,
那么用dlsym去获取这个函数的地址返回结果为什么是空的,后来看了一下地址tf_rkg_vavg 在objdump -C中的地址为126b7,然后我就用这个地址过滤,结果如下:
root@cjy-virtual-machine:~/test# nm -C 98750.so | cat -n | grep 126b7
296 000126b7 T tf_rkg_vavg
root@cjy-virtual-machine:~/test# nm -D 98750.so | cat -n | grep 126b7
218 000126b7 T gs_ext_init
已经很明显了, gdb和IDA里面显示的函数都是错的(其实不是错的,跟IDA选择解析符号顺序有关系),当时分析这个动态库的时候,还以为所有函数都加密了呢?用gs_ext_init名称调用dlsym正常调用并解密代码,到底objdump -D选项有什么神奇之处,可以导致gdb,IDA同一个选项地址显示的结果不一样呢?而且gdb对gs_ext_init下断点根本找不到符号
nm -C 98750.so | grep 126b7这个输出为什么没有显示gs_ext_init这个函数呢,但是用readelf -a 过滤就有效,如下所示:
root@speedlinux:/home/test# nm -C 98750.so | cat -n | grep 126b7
296 000126b7 T tf_rkg_vavg
root@speedlinux:/home/test# readelf -a 98750.so | cat -n | grep 126b7
1859 0001272c 00011c01 R_386_32 000126b7 gs_ext_init
1860 00012760 00011c01 R_386_32 000126b7 gs_ext_init
2160 284: 000126b7 1168 FUNC GLOBAL DEFAULT 12 gs_ext_init
2556 358: 000126b7 1168 FUNC GLOBAL DEFAULT 12 tf_rkg_vavg
root@speedlinux:/home/test# readelf -a 98750.so | cat -n | grep tf_rkg_vavg
2556 358: 000126b7 1168 FUNC GLOBAL DEFAULT 12 tf_rkg_vavg
root@speedlinux:/home/test# readelf -a 98750.so | cat -n | grep gs_ext_init
1859 0001272c 00011c01 R_386_32 000126b7 gs_ext_init
1860 00012760 00011c01 R_386_32 000126b7 gs_ext_init
2160 284: 000126b7 1168 FUNC GLOBAL DEFAULT 12 gs_ext_init
readelf -a 98750.so | cat -n >readelf_a_98750.txt
通过查看readelf -a输出上下文,确认
Line 1859: 1859 0001272c 00011c01 R_386_32 000126b7 gs_ext_init
Line 1860: 1860 00012760 00011c01 R_386_32 000126b7 gs_ext_init
这两个包含gs_ext_init的关键字出现在动态库符号的重定位节'.rel.dyn'中,节开头内容如下
102 Relocation section '.rel.dyn' at offset 0x3a70 contains 1761 entries:
103 Offset Info Type Sym.Value Sym. Name
104 00007781 00000008 R_386_RELATIVE
105 000077b3 00000008 R_386_RELATIVE Line 1859: 1859 0001272c 00011c01 R_386_32 000126b7 gs_ext_init
Line 1860: 1860 00012760 00011c01 R_386_32 000126b7 gs_ext_init
。。。。
而
2160 284: 000126b7 1168 FUNC GLOBAL DEFAULT 12 gs_ext_init
出现在动态库符号节'.dynsym',该节的开头内容如下
1874 Symbol table '.dynsym' contains 319 entries:
1875 Num: Value Size Type Bind Vis Ndx Name
1876 0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1877 1: 00000000 0 FUNC GLOBAL DEFAULT UND getpagesize@GLIBC_2.0 (2)
1878 2: 00000000 0 FUNC GLOBAL DEFAULT UND __errno_location@GLIBC_2.0 (2)
1879 3: 00000000 0 FUNC GLOBAL DEFAULT UND sprintf@GLIBC_2.0 (2)
1880 4: 00000000 0 FUNC GLOBAL DEFAULT UND srand@GLIBC_2.0 (2)
1881 5: 00000000 0 FUNC GLOBAL DEFAULT UND connect@GLIBC_2.0 (2)
。。。。
而唯一包含tf_rkg_vavg这个函数名称的行,
2556 358: 000126b7 1168 FUNC GLOBAL DEFAULT 12 tf_rkg_vavg
出现在'.symtab'符号表节中,该节的开头内容如下
2196 Symbol table '.symtab' contains 365 entries:
2197 Num: Value Size Type Bind Vis Ndx Name
2198 0: 00000000 0 NOTYPE LOCAL DEFAULT UND
2199 1: 000000f4 0 SECTION LOCAL DEFAULT 1
2200 2: 00000118 0 SECTION LOCAL DEFAULT 2
2201 3: 00000a38 0 SECTION LOCAL DEFAULT 3
2202 4: 000013a0 0 SECTION LOCAL DEFAULT 4
。。。。
到这里终于恍然大悟,tf_rkg_vavg这个只是调试符号,所以只出现在.symtab调试符号节中,而调试符号只是程序在调试的时候在用到,是专门给调试器使用的,而程序运行时根本不需要调试符号,而IDA静态分析工具或者gdb调试器当程序中包含符号表的时候会优先用符号表去表示各个函数,就是说程序真正运行时用到的函数名称gs_ext_init被符号表给覆盖了,这也解释了为什么gdb中用gs_ext_init去下断点会提示找不到符号,于是作者利用这一点将所有符号表中的内容乱改一通,程序仍然能够正常运行,但当别人用IDA,gdb调试的时候只能看到被混淆的乱起八糟的函数了。
那么问题来了,能不能让IDA,gdb忽略程序中的调试符号表呢?
找到了一种IDA中显示原始名称的方法,就是在汇编窗口中定位到tf_rkg_vavg这个函数,光标指向它,然后
选择菜单->View->Print internal flags,
在控制台窗口中输出如下:
pub [tf_rkg_vavg] code func xrefd
function does not modify the stack
cmt: Alternative name is 'gs_ext_init'
最后想到用strip可以去除符号信息
root@speedlinux:/home/test# strip 98750.so
root@speedlinux:/home/test# readelf -S 98750.so
There are 26 section headers, starting at offset 0x14474:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .note.gnu.build-i NOTE 000000f4 0000f4 000024 00 A 0 0 4
[ 2] .hash HASH 00000118 000118 000920 04 A 4 0 4
[ 3] .gnu.hash GNU_HASH 00000a38 000a38 000968 04 A 4 0 4
[ 4] .dynsym DYNSYM 000013a0 0013a0 0013f0 10 A 5 1 4
[ 5] .dynstr STRTAB 00002790 002790 001022 00 A 0 0 1
[ 6] .gnu.version VERSYM 000037b2 0037b2 00027e 02 A 4 0 2
[ 7] .gnu.version_r VERNEED 00003a30 003a30 000040 00 A 5 1 4
[ 8] .rel.dyn REL 00003a70 003a70 003708 08 A 4 0 4
[ 9] .rel.plt REL 00007178 007178 000018 08 A 4 11 4
[10] .init PROGBITS 00007190 007190 000030 00 AX 0 0 4
[11] .plt PROGBITS 000071c0 0071c0 000040 04 AX 0 0 4
[12] .text PROGBITS 00007200 007200 00b9c8 00 AX 0 0 16
[13] .fini PROGBITS 00012bc8 012bc8 00001c 00 AX 0 0 4
[14] .rodata PROGBITS 00012be4 012be4 000f0c 00 A 0 0 4
[15] .eh_frame PROGBITS 00013af0 013af0 000004 00 A 0 0 4
[16] .ctors PROGBITS 00014f04 013f04 000008 00 WA 0 0 4
[17] .dtors PROGBITS 00014f0c 013f0c 000008 00 WA 0 0 4
[18] .jcr PROGBITS 00014f14 013f14 000004 00 WA 0 0 4
[19] .dynamic DYNAMIC 00014f18 013f18 0000d0 08 WA 5 0 4
[20] .got PROGBITS 00014fe8 013fe8 00000c 04 WA 0 0 4
[21] .got.plt PROGBITS 00014ff4 013ff4 000018 04 WA 0 0 4
[22] .data PROGBITS 00015020 014020 000364 00 WA 0 0 32
[23] .bss NOBITS 000153a0 014384 00039c 00 WA 0 0 32
[24] .comment PROGBITS 00000000 014384 000025 01 MS 0 0 1
[25] .shstrtab STRTAB 00000000 0143a9 0000c8 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
对比之前的节信息,如下两个节的信息不见了
[26] .symtab SYMTAB 00000000 0148e4 0016d0 10 27 47 4
[27] .strtab STRTAB 00000000 015fb4 001367 00 0 0 1
再次放到IDA里面分析,所有的导出函数都正常了。
而解密后跳转函数ragel_byq的原始名称就是entry_old
最后修改主程序如下
vim unpack.c
源码如下:
#include<stdio.h>
#include<dlfcn.h>
int main()
{
void *handle1 = dlopen("./98750.so", RTLD_NOW); //程序虽然在同一个目录,必须带./,否则找不到
if (NULL == handle1)
fprintf(stderr, "Failed to open!\n");
int (* f)()=(int(*)()) dlsym(handle1, "gs_ext_init");
if (f != NULL)
f();
dlclose(handle1);
return 0;
}
编译
gcc -g -o unpack unpack.c -ldl
然后用gdb调试对gs_ext_init下断点啥的都正常了,如下图所示:
root@speedlinux:/home/test# vim unpack.c
root@speedlinux:/home/test# gcc -g -o unpack unpack.c -ldl
root@speedlinux:/home/test# gdb unpack
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /home/test/unpack...done.
(gdb) b main
Breakpoint 1 at 0x804850d: file unpack.c, line 5.
(gdb) r
Starting program: /home/test/unpack
Breakpoint 1, main () at unpack.c:5
5 void *handle1 = dlopen("./98750.so", RTLD_NOW); //程序虽然在同一个目录,必须带./,否则找不到
(gdb) n
6 if (NULL == handle1)
(gdb) n
8 int (* f)()=(int(*)()) dlsym(handle1, "gs_ext_init");
(gdb) n
9 if (f != NULL)
(gdb) p f
$1 = (int (*)()) 0xb7e1f6b7 <gs_ext_init>
(gdb) b gs_ext_init
Breakpoint 2 at 0xb7e1f6bb
(gdb) d 2
(gdb) b ragel_byq
Function "ragel_byq" not defined.
Make breakpoint pending on future shared library load? (y or [n]) n
(gdb) b entry_old
Breakpoint 3 at 0xb7e1f025
(gdb) c
Continuing.
Breakpoint 3, 0xb7e1f025 in entry_old () from ./98750.so
(gdb) set disassembly-flavor intel
(gdb) disas
Dump of assembler code for function entry_old:
=> 0xb7e1f025 <+0>: addr16 mov ebp,esp
0xb7e1f028 <+3>: push ebx
0xb7e1f029 <+4>: sub esp,0x34
0xb7e1f02c <+7>: mov DWORD PTR [ebp-0x18],0x0
0xb7e1f033 <+14>: mov DWORD PTR [ebp-0x10],0x0
0xb7e1f03a <+21>: mov DWORD PTR [ebp-0xc],0x0
0xb7e1f041 <+28>: mov DWORD PTR [esp],0xb7e20ac7
0xb7e1f048 <+35>: call 0xb7e8b830 <_IO_puts>
0xb7e1f04d <+40>: mov DWORD PTR [esp],0x1000000
0xb7e1f054 <+47>: call 0xb7e9dec0 <__GI___libc_malloc>
0xb7e1f059 <+52>: mov ds:0xb7e223b4,eax
0xb7e1f05e <+57>: mov eax,ds:0xb7e223b4
0xb7e1f063 <+62>: mov DWORD PTR [esp+0x8],0x1000000
0xb7e1f06b <+70>: mov DWORD PTR [esp+0x4],0x0
0xb7e1f073 <+78>: mov DWORD PTR [esp],eax
0xb7e1f076 <+81>: call 0xb7f597f0 <__memset_sse2_rep>
0xb7e1f07b <+86>: mov DWORD PTR [esp],0x61c
0xb7e1f082 <+93>: call 0xb7e9dec0 <__GI___libc_malloc>
0xb7e1f087 <+98>: mov ds:0xb7e223b8,eax
0xb7e1f08c <+103>: mov eax,ds:0xb7e223b8
0xb7e1f091 <+108>: mov DWORD PTR [esp+0x8],0x61c
0xb7e1f099 <+116>: mov DWORD PTR [esp+0x4],0x0
0xb7e1f0a1 <+124>: mov DWORD PTR [esp],eax
0xb7e1f0a4 <+127>: call 0xb7f597f0 <__memset_sse2_rep>
0xb7e1f0a9 <+132>: mov DWORD PTR ds:0xb7e2264c,0x80564f4
。。。。
补充:(可以用查看so动态库导出函数)方法三:objdump -T 98750.so (详细自己看说明了)
然后就是分析破解的问题了,破解方法有两种方法:
破解方法一:DUMP出来分析,然后修复重定位项、修改入口地址等导致运行错误的问题
破解方法二:给程序打补丁,由于程序是动态解密的,因此必须代码解密后动态给程序打内存补丁。
part 2 利用LD_PRELOAD先加载并且可以Hook同名函数来patch程序达到破解的目的
上面附件中有两个附加你中要用到的配置文件,gsext.ini和tzgs1.ini,将这两个配置文件、98750.so和unpack程序同一个目录下,然后运行unpack程序,运行输出报错如下:
root@cjy-virtual-machine:~/test# ./unpack
²̄馳_ext_init() ok.
MCODEn: [0x68,0x65,0x43,0x43,0x99,0xAC,0xAE,0xC7,0x71,0xCD,0xD3,0x66,0xB1,0x23]
MCODE2=43996871CDC766D3B1
MCODE0: [0x68,0x65,0x43,0x5D,0x4B,0x27,0xAE,0xC7,0x71,0xCD,0xC1,0x96,0xDF,0x21]
[注意]APP应用上架合规检测服务,协助应用顺利上架!
最后于 2022-5-2 12:54
被cjycjw编辑
,原因: