-
-
[原创]实战分析之符号混淆和LD_PRELOAD技术的动态补丁破解(98750.so)- 图片和附件修复版
-
2022-5-2 12:53 15478
-
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] errcode=1 |
正常认证过的程序会读取gsext.ini配置文件,并将其内容输出,如下表所示,这个也是可以作为是否破解的标志之一。
²̄馳_ext_init() ok. configBlock->CKInterval=20 configBlock->SwitchITEX=1 configBlock->guildwaritemid=28889 configBlock->SongRate=1.000000 configBlock->Song2Rate=10.000000 configBlock->PosX1=218 configBlock->PosY1=-554 configBlock->PosX2=328 configBlock->PosY2=-488 configBlock->SongExRate=30.000000 configBlock->PosExX1=2143 configBlock->PosExY1=2400 configBlock->PosExX2=2206 configBlock->PosExY2=2463 configBlock->HourEx1=20 configBlock->HourEx2=21 configBlock->HourEx3=0 configBlock->SwitchRTEX=1 configBlock->AddPointBase=1 configBlock->DropColor=4 configBlock->SimithSC=8 configBlock->SimithFL=11 configBlock->SwitchLogin=0 configBlock->DBAccount= configBlock->lxcmd=xiaojiba |
通过gdb提示,并且对write下断点,然后bt跟踪堆栈,在结合disas反汇编指令,可以定位到第一个需要破解的地方在函数dopredoinfo中,具体如下所示,注意disas反汇编信息中的相对于函数的偏移量无论对于定位跳转地址还是后期patch都十分有用,几个重点的偏移,下表中已经用红色标出。
root@cjy-virtual-machine:~/test# gdb ./unpack (gdb) b write Function "write" not defined. Make breakpoint pending on future shared library load? (y or [n]) y Breakpoint 1 (write) pending. (gdb) r Starting program: /root/test/unpack Breakpoint 1, 0xb7efb1b0 in write () from /lib/i386-linux-gnu/libc.so.6 (gdb) c Continuing. Breakpoint 1, 0xb7efb1b0 in write () from /lib/i386-linux-gnu/libc.so.6 (gdb) c Continuing. Breakpoint 1, 0xb7efb1b0 in write () from /lib/i386-linux-gnu/libc.so.6 (gdb) c Continuing. ²̄馳_ext_init() ok. Breakpoint 1, 0xb7efb1b0 in write () from /lib/i386-linux-gnu/libc.so.6 (gdb) c Continuing. Breakpoint 1, 0xb7efb1b0 in write () from /lib/i386-linux-gnu/libc.so.6 (gdb) c Continuing. MCODEn: [0x68,0x65,0x43,0x43,0x99,0xAC,0xAE,0xC7,0x71,0xCD,0xD3,0x66,0xB1,0x23] Breakpoint 1, 0xb7efb1b0 in write () from /lib/i386-linux-gnu/libc.so.6 (gdb) bt #0 0xb7efb1b0 in write () from /lib/i386-linux-gnu/libc.so.6 #1 0xb7e8fb05 in _IO_file_write () from /lib/i386-linux-gnu/libc.so.6 #2 0xb7e8f9e4 in ?? () from /lib/i386-linux-gnu/libc.so.6 #3 0xb7e9105e in _IO_do_write () from /lib/i386-linux-gnu/libc.so.6 #4 0xb7e91415 in _IO_file_overflow () from /lib/i386-linux-gnu/libc.so.6 #5 0xb7e905a3 in _IO_file_xsputn () from /lib/i386-linux-gnu/libc.so.6 #6 0xb7e62504 in vfprintf () from /lib/i386-linux-gnu/libc.so.6 #7 0xb7e6bc2f in printf () from /lib/i386-linux-gnu/libc.so.6 #8 0xb7e1af25 in dopredoinfo () from ./98750.so #9 0xb7e1b16c in entry_old () from ./98750.so #10 0xb7e1bb3e in gs_ext_init () from ./98750.so #11 0x08048757 in main () at unpack.c:51 (gdb) set disassembly-flavor intel (gdb) disas dopredoinfo Dump of assembler code for function dopredoinfo: 0xb7e1accc <+0>: push ebp 0xb7e1accd <+1>: mov ebp,esp 0xb7e1accf <+3>: push edi 0xb7e1acd0 <+4>: push esi 0xb7e1acd1 <+5>: push ebx 0xb7e1acd2 <+6>: sub esp,0xbc 0xb7e1acd8 <+12>: mov DWORD PTR [ebp-0x1c],0x0 。。。。。 0xb7e1ad5e <+146>: mov DWORD PTR [esp+0x4],eax 0xb7e1ad62 <+150>: mov DWORD PTR [esp],0xb7e1e718 0xb7e1ad69 <+157>: call 0xb7f4e770 0xb7e1ad6e <+162>: test eax,eax // 未破解,$eax=-1 0xb7e1ad70 <+164>: je 0xb7e1b015 <dopredoinfo+841> //要破解的跳转 0xb7e1ad76 <+170>: movzx eax,BYTE PTR ds:0xb7e1e725 0xb7e1ad7d <+177>: movzx edi,al 0xb7e1ad80 <+180>: movzx eax,BYTE PTR ds:0xb7e1e724 0xb7e1ad87 <+187>: movzx eax,al 。。。。。。 0xb7e1b002 <+822>: mov DWORD PTR [esp],0xb7e1cabd 0xb7e1b009 <+829>: call 0xb7e866a0 <puts> 0xb7e1b00e <+834>: mov eax,0x1 0xb7e1b013 <+839>: jmp 0xb7e1b01a <dopredoinfo+846> 0xb7e1b015 <+841>: mov eax,0x0 //正常的用户从函数164偏移处直接跳到这边 0xb7e1b01a <+846>: add esp,0xbc 0xb7e1b020 <+852>: pop ebx 0xb7e1b021 <+853>: pop esi 0xb7e1b022 <+854>: pop edi 0xb7e1b023 <+855>: pop ebp 0xb7e1b024 <+856>: ret End of assembler dump. |
从以上信息知道,最简单的patch方法有,在偏移162处eax的值改成0,或者将164将jz改成jnz。先将162处eax改成0看是否能破解成功。
我们编写一个动态库导出同名函数名称entry_old,然后将库地址赋值给LD_PRELOAD环境变量,这样我们就hook了程序中的entry_old函数,当程序调用entry_old函数的时候,自动变成调用我们的entry_old函数,因为程序调用entry_old时所有的加密代码都已经解密,所以直接在我们的entry_old里面patch目标破解代码,然后再调用原始的entry_old入口函数即可。
这边我们直接将test eax,eax 两个自己的指令换成 xor eax,eax两个字节的指令,这两个指令对应的16进制数据在OD里面输入后,结果是xor eax,eax对应的指令汇编为:33C0或者31C0, 如下图所示:
编写代码测试如下:
vim libpatch.c #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include <stdio.h> #include<dlfcn.h> #include<link.h> #include<string.h> int entry_old(void) { printf("My Patch Success!\n"); void* handle = (void*)dlopen("./98750.so", RTLD_NOW | RTLD_GLOBAL | RTLD_NOLOAD); //不确定entry_oly是否会被多次调用,先RTLD_NOLOAD标识判断是否已经加载,注意RTLD_NOLOAD不能单独使用,必须与其他标识或操作 if (handle == NULL) { handle = dlopen("./98750.so", RTLD_NOW | RTLD_GLOBAL); //如果没有加载过,这边就加载 if (handle == NULL) { printf("Load library error!\n"); return 0; } } uint8_t code_orig1[2]; void* dopredoinfo_addr = NULL; dopredoinfo_addr=dlsym(handle, "dopredoinfo"); if (dopredoinfo_addr == NULL) { printf("get dopredoinfo address failed!\n"); } uint8_t code1[] = {0x31,0xc0}; //=xor eax,eax memcpy(code_orig1, dopredoinfo_addr+162, 2); memcpy(dopredoinfo_addr+162,code1, 2); //打补丁 int (* f)()=(int(*)(void)) dlsym(handle, "entry_old"); //调用原始的认证函数 int ret = f(); dlclose(handle); memcpy(dopredoinfo_addr+162,code_orig1, 2); //还原为原始函数防止后期程序中有代码校验 return ret; } gcc -g3 -fPIC -shared -o libpatch.so libpatch.c LD_PRELOAD=./libpatch.so ./unpack 输出如下(报了另外一个错误): My Patch Success! ²̄馳_ext_init() ok. Gethostname error, Connection timed out check error! errcode=2 |
以下根据输出信息gdb调试跟踪第二处需要破解的地方。
root@cjy-virtual-machine:~/test# LD_PRELOAD=./libpatch.so gdb ./unpack (gdb) b write Function "write" not defined. Make breakpoint pending on future shared library load? (y or [n]) y Breakpoint 1 (write) pending. (gdb) r Starting program: /root/test/unpack Breakpoint 1, 0xb7ef71b0 in write () from /lib/i386-linux-gnu/libc.so.6 (gdb) c Continuing. My Patch Success! Breakpoint 1, 0xb7ef71b0 in write () from /lib/i386-linux-gnu/libc.so.6 (gdb) c Continuing. ²̄馳_ext_init() ok. Breakpoint 1, 0xb7ef71b0 in write () from /lib/i386-linux-gnu/libc.so.6 (gdb) c Continuing. Breakpoint 1, 0xb7ef71b0 in write () from /lib/i386-linux-gnu/libc.so. (gdb) print *(char**)($esp+8) $3 = 0xbfffcd18 "Connect Error:Connection refused\n c.9133xy.com." (gdb) x/s *(char**)($esp+8) 0xbfffcd18: "Connect Error:Connection refused\n c.9133xy.com." (gdb) x/s *(int*)($esp+8) 0xbfffcd18: "Connect Error:Connection refused\n c.9133xy.com." (gdb) print "%s\n",*(char**)($esp+8) $5 = 0xbfffcd18 "Connect Error:Connection refused\n c.9133xy.com." (gdb) bt #0 0xb7ef71b0 in write () from /lib/i386-linux-gnu/libc.so.6 #1 0xb7e8bb05 in _IO_file_write () from /lib/i386-linux-gnu/libc.so.6 #2 0xb7e8b9e4 in ?? () from /lib/i386-linux-gnu/libc.so.6 #3 0xb7e8c6fa in _IO_file_xsputn () from /lib/i386-linux-gnu/libc.so. #4 0xb7e62f07 in ?? () from /lib/i386-linux-gnu/libc.so.6 #5 0xb7e5db85 in vfprintf () from /lib/i386-linux-gnu/libc.so.6 #6 0xb7e67bef in fprintf () from /lib/i386-linux-gnu/libc.so.6 #7 0xb7e0fad5 in http_request () from ./98750.so #8 0xb7e0fd91 in sn_time_chk () from ./98750.so #9 0xb7e18179 in entry_old () from ./98750.so #10 0xb7fd85de in entry_old () at libpatch.c:32 #11 0xb7e18b3e in gs_ext_init () from ./98750.so #12 0x08048757 in main () at unpack.c:51 从上面的堆栈看出错误输出在 entry_old ()->sn_time_chk()-> http_request() (gdb) set disassembly-flavor intel (gdb) disas http_request Dump of assembler code for function http_request: 0xb7e0f8fc <+0>: push ebp 0xb7e0f8fd <+1>: mov ebp,esp 0xb7e0f8ff <+3>: push ebx 0xb7e0f900 <+4>: sub esp,0x184 0xb7e0f906 <+10>: mov DWORD PTR [ebp-0x20],0x0 0xb7e0f90d <+17>: mov DWORD PTR [ebp-0x15f],0x2e63696c 0xb7e0f917 <+27>: mov DWORD PTR [ebp-0x15b],0x33333139 0xb7e0f921 <+37>: mov DWORD PTR [ebp-0x157],0x632e7978 0xb7e0f92b <+47>: mov WORD PTR [ebp-0x153],0x6d6f 。。。 。。。 0xb7e0fd20 <+1060>: add eax,0x4 0xb7e0fd23 <+1063>: mov DWORD PTR [esp+0x4],eax 0xb7e0fd27 <+1067>: mov eax,DWORD PTR [ebp+0x8] 0xb7e0fd2a <+1070>: mov DWORD PTR [esp],eax 0xb7e0fd2d <+1073>: call 0xb7f4e280 0xb7e0fd32 <+1078>: mov eax,0x0 0xb7e0fd37 <+1083>: add esp,0x184 0xb7e0fd3d <+1089>: pop ebx 0xb7e0fd3e <+1090>: pop ebp 0xb7e0fd3f <+1091>: ret (gdb) disas sn_time_chk Dump of assembler code for function sn_time_chk: 0xb7e0fd40 <+0>: push ebp 0xb7e0fd41 <+1>: mov ebp,esp 0xb7e0fd43 <+3>: sub esp,0x38 0xb7e0fd46 <+6>: call 0xb7f00800 <getpagesize> 0xb7e0fd4b <+11>: mov DWORD PTR [ebp-0x18],eax 0xb7e0fd4e <+14>: mov DWORD PTR [ebp-0x14],0xb7e0f106 0xb7e0fd55 <+21>: mov DWORD PTR [ebp-0x10],0x0 0xb7e0fd5c <+28>: mov DWORD PTR [ebp-0xc],0x0 0xb7e0fd63 <+35>: mov DWORD PTR [esp+0x8],0xe 0xb7e0fd6b <+43>: mov DWORD PTR [esp+0x4],0xb7e1b718 0xb7e0fd73 <+51>: lea eax,[ebp-0x26] 0xb7e0fd76 <+54>: mov DWORD PTR [esp],eax 0xb7e0fd79 <+57>: call 0xb7f4e280 0xb7e0fd7e <+62>: lea eax,[ebp-0x26] 0xb7e0fd81 <+65>: mov DWORD PTR [esp+0x4],0xe 0xb7e0fd89 <+73>: mov DWORD PTR [esp],eax 0xb7e0fd8c <+76>: call 0xb7e0f8fc <http_request> 0xb7e0fd91 <+81>: test eax,eax 0xb7e0fd93 <+83>: je 0xb7e0fda8 <sn_time_chk+104> 0xb7e0fd95 <+85>: mov DWORD PTR [esp],0xb7e18e8a 0xb7e0fd9c <+92>: call 0xb7e826a0 <puts> ---Type <return> to continue, or q <return> to quit--- 0xb7e0fda1 <+97>: mov eax,0x1 0xb7e0fda6 <+102>: jmp 0xb7e0fe12 <sn_time_chk+210> 0xb7e0fda8 <+104>: mov eax,DWORD PTR [ebp-0x14] 0xb7e0fdab <+107>: mov edx,0x0 0xb7e0fdb0 <+112>: div DWORD PTR [ebp-0x18] 0xb7e0fdb3 <+115>: imul eax,DWORD PTR [ebp-0x18] 0xb7e0fdb7 <+119>: mov DWORD PTR [esp+0x8],0x7 0xb7e0fdbf <+127>: mov edx,DWORD PTR [ebp-0x18] 0xb7e0fdc2 <+130>: mov DWORD PTR [esp+0x4],edx 0xb7e0fdc6 <+134>: mov DWORD PTR [esp],eax 0xb7e0fdc9 <+137>: call 0xb7f03f10 <mprotect> 0xb7e0fdce <+142>: mov DWORD PTR [ebp-0x10],0x0 0xb7e0fdd5 <+149>: jmp 0xb7e0fe07 <sn_time_chk+199> 0xb7e0fdd7 <+151>: mov eax,DWORD PTR [ebp-0x10] 0xb7e0fdda <+154>: add eax,DWORD PTR [ebp-0x14] 0xb7e0fddd <+157>: mov edx,DWORD PTR [ebp-0x10] 0xb7e0fde0 <+160>: add edx,DWORD PTR [ebp-0x14] 0xb7e0fde3 <+163>: movzx ecx,BYTE PTR [edx] 0xb7e0fde6 <+166>: mov edx,DWORD PTR [ebp-0xc] 0xb7e0fde9 <+169>: movzx edx,BYTE PTR [ebp+edx*1-0x26] 0xb7e0fdee <+174>: xor edx,ecx 0xb7e0fdf0 <+176>: mov BYTE PTR [eax],dl ---Type <return> to continue, or q <return> to quit--- 0xb7e0fdf2 <+178>: add DWORD PTR [ebp-0xc],0x1 0xb7e0fdf6 <+182>: cmp DWORD PTR [ebp-0xc],0xe 0xb7e0fdfa <+186>: jne 0xb7e0fe03 <sn_time_chk+195> 0xb7e0fdfc <+188>: mov DWORD PTR [ebp-0xc],0x0 0xb7e0fe03 <+195>: add DWORD PTR [ebp-0x10],0x1 0xb7e0fe07 <+199>: cmp DWORD PTR [ebp-0x10],0x1f 0xb7e0fe0b <+203>: jle 0xb7e0fdd7 <sn_time_chk+151> 0xb7e0fe0d <+205>: mov eax,0x0 0xb7e0fe12 <+210>: leave 0xb7e0fe13 <+211>: ret End of assembler dump. 简单分析一下http_request和sn_time_chk函数,可以知道,sn_time_chk调用http_request,并且http_request必须返回0,才正确,此时sn_time_chk也返回0 |
当时就想到了patch函数http_request直接返回0,patch的方法就是直接将最后的函数汇编代码拷贝到函数开头,
即,将如下函数结尾汇编代码
0xb7e0fd32 <+1078>: mov eax,0x0
0xb7e0fd37 <+1083>: add esp,0x184
0xb7e0fd3d <+1089>: pop ebx
0xb7e0fd3e <+1090>: pop ebp
0xb7e0fd3f <+1091>: ret
拷贝到函数开头如下汇编代码之后,
0xb7e0f8fc <+0>: push ebp
0xb7e0f8fd <+1>: mov ebp,esp
0xb7e0f8ff <+3>: push ebx
0xb7e0f900 <+4>: sub esp,0x184
这样子函数返回0,堆栈也是平衡的。
于是在原来patch的地方再增加一个patch,最终修改代码如下:
vim libpatch.c #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include <stdio.h> #include<dlfcn.h> #include<link.h> #include<string.h> int entry_old(void) { printf("My Patch Success!\n"); void* handle = (void*)dlopen("./98750.so", RTLD_NOW | RTLD_GLOBAL | RTLD_NOLOAD); //不确定entry_oly是否会被多次调用,先RTLD_NOLOAD标识判断是否已经加载,注意RTLD_NOLOAD不能单独使用,必须与其他标识或操作 if (handle == NULL) { handle = dlopen("./98750.so", RTLD_NOW | RTLD_GLOBAL); //如果没有加载过,这边就加载 if (handle == NULL) { printf("Load library error!\n"); return 0; } } int (* f)() = NULL; int ret = 0; int patched = 0; uint8_t code_orig1[2]; uint8_t code_orig2[14]; void* dopredoinfo_addr = NULL; void* http_request_addr = NULL; dopredoinfo_addr=dlsym(handle, "dopredoinfo"); http_request_addr = dlsym(handle, "http_request"); if (dopredoinfo_addr == NULL) { printf("get dopredoinfo address failed!\n"); goto ENTRY_OLD; } if (http_request_addr == NULL) { printf("get http_request address failed!\n"); goto ENTRY_OLD; } uint8_t code1[] = {0x31,0xc0}; //=xor eax,eax memcpy(code_orig1, dopredoinfo_addr+162, 2); //备份原始数据 memcpy(code_orig2, http_request_addr+10,14); memcpy(dopredoinfo_addr+162,code1, 2); //打补丁 memcpy(http_request_addr+10,http_request_addr+1078,14); patched = 1; ENTRY_OLD: f = (int(*)(void)) dlsym(handle, "entry_old"); //调用原始的认证函数 ret = f(); dlclose(handle); if (patched) { memcpy(dopredoinfo_addr+162,code_orig1, 2); //还原为原始函数防止后期程序中有代码校验 memcpy(http_request_addr+10,code_orig2, 14); } return ret; } gcc -g3 -fPIC -shared -o libpatch.so libpatch.c root@cjy-virtual-machine:~/test# LD_PRELAOD=./libpatch.so ./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,0xC7,0x2E,0x8E,0xAE,0xC7,0x71,0xCA,0xC1,0x96,0xDF,0x21] errcode=1 root@cjy-virtual-machine:~/test# LD_PRELOAD=./libpatch.so ./unpack My Patch Success! ²̄馳_ext_init() ok. configBlock->CKInterval=20 configBlock->SwitchITEX=1 configBlock->guildwaritemid=28889 configBlock->SongRate=1.000000 configBlock->Song2Rate=10.000000 configBlock->PosX1=218 configBlock->PosY1=-554 configBlock->PosX2=328 configBlock->PosY2=-488 configBlock->SongExRate=30.000000 configBlock->PosExX1=2143 configBlock->PosExY1=2400 configBlock->PosExX2=2206 configBlock->PosExY2=2463 configBlock->HourEx1=20 configBlock->HourEx2=21 configBlock->HourEx3=0 configBlock->SwitchRTEX=1 configBlock->AddPointBase=1 configBlock->DropColor=4 configBlock->SimithSC=8 configBlock->SimithFL=11 configBlock->SwitchLogin=0 configBlock->DBAccount= configBlock->lxcmd=xiaojiba hcolor=3 hrates:100,100,100, maxsml=10 lrates:100,100,100,80,70,70,60,50,40,35,10,10,10,10,10, sublev:0,0,0,0,0,0,0,0,0,0,5,5,5,5,5, latt1:0.020000,0.040000,0.060000,0.080000,0.120000,0.160000,0.200000,0.300000,0.400000,0.600000,0.600000,0.700000,0.800000,0.900000,1.000000, latt2:0.020000,0.040000,0.060000,0.080000,0.120000,0.160000,0.200000,0.300000,0.400000,0.600000,0.600000,0.700000,0.800000,0.900000,1.000000, latt3:20,40,60,80,120,160,200,300,400,500,500,500,500,500,500, latt4:20,40,60,80,120,160,200,300,400,500,500,500,500,500,500, st_1:10,30,50,80,110,150,90,130,180, st_2:10,30,50,80,110,150,90,130,180, st_3:0.010000,0.010000,0.020000,0.030000,0.040000,0.070000,0.030000,0.040000,0.070000, st_4:0.000000,0.000000,0.000000,0.010000,0.010000,0.030000,0.010000,0.010000,0.030000, st_5:10,30,50,80,110,150,90,130,180, st_6:10,20,30,100,200,400,200,300,600, st_7:10,30,50,80,110,150,90,130,180, lh_01:100,120,150,180,210,240,270, lh_02:10,12,15,18,21,24,27, lh_03:100,120,150,180,210,240,270, lh_04:1,2,3,4,5,6,7, lh_05:100,120,150,180,210,240,270, lh_06:10,12,15,18,21,24,27, lh_11:10,12,15,18,21,24,27, lh_12:1,2,3,4,5,6,7, 段错误 (核心已转储) root@cjy-virtual-machine:~/test# |
从以上输出看,基本可以算破解完成了,其实由于http_request是导出函数,第二处可以不用patch的方式,可以直接定义一个同名的导出函数,这个函数返回0即可。另外这个so动态库其实是蜀门游戏锻造系统的一个动态库,将这个libpatch.so用LD_PRELOAD导入game_server里面是可以运行起来,但是右键钻石的时候每次都会掉线, 其实有很多细节要分析,程序获取本地的mac地址、IP地址、/lost-found文件的时间信息,将这些信息发送给服务器,服务器发回来一些字符信息,对这些信息抑或判断是否为目标结果,另外程序将mac地址的一部分赋值给变量参与程序解密,和其他运算,这也是导致掉线的原因。其实在第一个patch前的函数调用和对比可以推测目标机器的mac,ip等信息,另外需要找出所有引用mac的地方,然后伪造正确的变量关联值。由于要分析的而函数细节太多,而且需要寻找mac的所有引用,所以借助IDA最好不过。
Part3 完全终极破解
先给出最终破解的LD_PREOLOAD动态库hook源程序
vim libx.c #include <stdio.h> #include<dlfcn.h> #include<link.h> #include<string.h> int http_request(void* mac, size_t size) { int i; for (i = 0; i <= 13; ++i) { *((uint8_t*)mac + i) ^= (uint8_t)0x32u; } return 0; } int GetMac(char *s) { uint8_t mac[14] = {0x00,0x0c,0x29,0x36,0x27,0x4a,0xc0,0xa8,0x01,0xbc,0xb3,0xe5,0xab,0x54}; memcpy(s, mac, 14); return 0; } gcc -shared -o libx.so -fPIC libx.c root@speedlinux:/home/test# LD_PRELOAD=./libx.so ./unpack ²̄馳_ext_init() ok. configBlock->CKInterval=20 configBlock->SwitchITEX=1 configBlock->guildwaritemid=28889 configBlock->SongRate=1.000000 configBlock->Song2Rate=10.000000 configBlock->PosX1=218 configBlock->PosY1=-554 configBlock->PosX2=328 configBlock->PosY2=-488 configBlock->SongExRate=30.000000 configBlock->PosExX1=2143 configBlock->PosExY1=2400 configBlock->PosExX2=2206 configBlock->PosExY2=2463 configBlock->HourEx1=20 configBlock->HourEx2=21 configBlock->HourEx3=0 configBlock->SwitchRTEX=1 configBlock->AddPointBase=1 configBlock->DropColor=4 configBlock->SimithSC=8 configBlock->SimithFL=11 configBlock->SwitchLogin=0 configBlock->DBAccount= configBlock->lxcmd=xiaojiba root@speedlinux:/home/test# |
借助IDA进行分析步骤如下(注:分析是2017年用的IDA6.8、最新版的IDA反汇编效果可能不同):
(1)IDA本地打开去除符号的98750.so
(2)将IDA目录下dbgsrv子目录下的linux_server拷贝到linux, chmod +x linux_server 并运行起来
(3)IDA->Debugger->Process Options..设置调试参数
(4)在Exports导出函数窗口中找到entry_old函数,并然后找到引用该函数的地方,并F2下断点
(5)F9运行起来,提示运行程序风险,选择Yes, 然后弹出的Add map..窗口,全部选择Cancel( 当然也可以将对应用到的动态库从linux拷贝到windows目录下,然后指定映射关系,这版其他的库我们不关心也不用在IDA打开里面分析,全部取消即可)
遇到98750.so会提示是否是一样的动态库,选择same
运行来到断点处,并且程序代码全部解密完成
(6)F7步入函数显示的都是乱码,如下所示
(7)解决乱码,转化汇编代码或伪代码
光标停在函数的第一行,然后菜单->Edit->Begin selection
向下不断拖动,知道看到另外一个函数的名称,说明这个函数结束了,然后按一下C,并选择Analyze,将选择的区域转化成代码(当然也可以一点一点按C将指定区域转化成代码但是这样子太慢了)
转化后仍然有部分没有正确转化成代码,被分隔线分隔,如下是开头的分隔线
如下是函数结尾部分的分割线
将分隔线之间的数据选中,按C选择Force强制转化成代码
此时又将大部分的数据转化成为了代码,多了更多的分隔线,但仍然还有部分的分隔线间的数据没有被转化成为代码,重复以上转化,知道该函数内的所有的数据转化成代码。(如果数据只有一小块,并没有多余一个屏幕,可以按Shift选择,然后按C转化,或者直接将光标定位到数据的开头部分,按C,通常IDA会自动将后面的数据转化成代码)
全部转化成代码后,重新将光标定位到entry_old函数开头部分(或者public entry_old)字符名称上,然后右键->Create function...创建函数
此时通常会出现局部变量或者参数
然后光标停在函数内部,按F5查看函数的伪代码,如下函数显示不全啊
返回到汇编函数,观察函数提早结束的地方,entry_old函数尾部被IDA识别错误,提早结束了,如下所示
解决办法就是将光标停在正真函数结尾的地方,retn指令上,然后菜单->Edit->Functions->Set function end 设置真正的函数结束位置
设置后立即多了函数结束标识符
然后重新按F5,查看伪代码,函数仍然提早结束了,如下所示
查看伪代码发现原来是分隔线导致(TODO:这个分割线是什么,是如何产生,是如何消除)
这个应该是反编译插件的BUG了,目前没有比较好的解决办法,按照如下方法有时候可以去除这个分隔线。
将分割线选中后者选中前一个汇编指令,然后Undefine,然后再次按C转化成代码,如果无法去除就没办法了,部分分隔线也不会影响反汇编,如果遇到影响反汇编的分割线又去不掉的话,那就直接看汇编窗口,不要看伪代码吧。
通过undefine并code去掉部分分隔线之后,伪代码如下。
可以看出dopredoinfo和sn_time_chk都必须返回0, 另外mac地址信息也参数运算。
下面重点分析dopredoinfo和sn_time_chk这个两个信息和所有对mac地址的引用。
注:对于其他的无法显示的函数汇编代码,错误识别成数据的部分全部可以采用上面的方法进行处理。后面不再描述处理过程,都是转化后的代码。
(8)在分析前先获取解密后的程序快照,这样子下次就不用重新运行动态解密代码,就可以直接分析了。
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。
赞赏
|
|
---|---|
|
感谢分享经验
|
|
大神,解密后的能不能分享一份?万分感谢
|
|
你说他是乱改的,他可不是乱改的啊doge 他这个函数名混淆 就是一个rot13
给楼主赞一个 |