首页
社区
课程
招聘
[原创]实战分析之符号混淆和LD_PRELOAD技术的动态补丁破解(98750.so)- 图片和附件修复版
2022-5-2 12:53 15478

[原创]实战分析之符号混淆和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漏洞挖掘与利用;代码审计。

最后于 2022-5-2 12:54 被cjycjw编辑 ,原因:
上传的附件:
收藏
点赞4
打赏
分享
最新回复 (4)
雪    币: 576
活跃值: (2035)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kakasasa 2022-5-2 15:21
2
0
感谢分享经验
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_xtblfyri 2022-12-16 19:38
4
0
大神,解密后的能不能分享一份?万分感谢
雪    币: 88
活跃值: (69)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
guozizheng 2022-12-22 17:59
5
0
你说他是乱改的,他可不是乱改的啊doge 他这个函数名混淆 就是一个rot13
给楼主赞一个
游客
登录 | 注册 方可回帖
返回