首页
社区
课程
招聘
[原创]Linux符号混淆和LD_PRELOAD技术的动态补丁破解linux动态库(98750.so)
发表于: 2017-3-23 21:34 14089

[原创]Linux符号混淆和LD_PRELOAD技术的动态补丁破解linux动态库(98750.so)

2017-3-23 21:34
14089

以下为本人原创,未经允许请勿随意转载。

Part 1 - 利用IDA、objdump、gdb等静态分析或调试器优先加载符号表的特性,从而通过修改节.symtab中符号字符信息来欺骗这些工具)
这个样本是某个主程序调用的动态库,我之前分析了很多  次,用IDA打开查看导出函数所有的函数名称都是乱起八糟没有意义的,之前以为是所有的函数名称都被加密混淆了。同时我也用IDA和gdb调试了很多次,知道这个样本中的代码是加密的,运行的时候会动态解密,解密函数名称为tf_rkg_vavg, 这个函数内部会调用mprotect并递归调用自己来解密代码,解密成功后这个函数在末尾会跳转到已解密的ragel_byq去执行。其中ragel_byq函数是被加密的,反汇编显示的是乱起八糟的汇编指令,如下图所示。
ragel_byq函数解密前
之前调试的时候一直都是在主程序下调试,然后这个动态库里面下断点,由于主程序很大所以很不方便,于是想直接写个Demo去调用这个动态库,然后去解密看是否可行。
vim unpack.c
源码如下:
#include
#include
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 
 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
...
通过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
。。。
  [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)
 。。。。
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
   。。。
    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
。。。
     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_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调试的时候只能看到被混淆的乱起八糟的函数了。
(TDOO:)那么问题来了,能不能让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
。。。
  [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
#include
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>
然后就是分析破解的问题了,破解方法有两种方法:
破解方法一:DUMP出来分析,然后修复重定位项、修改入口地址等导致运行错误的问题
破解方法二:给程序打补丁,由于程序是动态解密的,因此必须代码解密后动态给程序打内存补丁。下面part 2可以用
补充:
(查看so动态库导出函数)方法三:objdump -T 98750.so (详细自己看说明了)
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 
   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 
   0xb7e1b00e <+834>:    mov    eax,0x1
   0xb7e1b013 <+839>:    jmp    0xb7e1b01a 
   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 
#include
#include
#include
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 
   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 
   0xb7e0fd91 <+81>:    test   eax,eax
   0xb7e0fd93 <+83>:    je     0xb7e0fda8 
   0xb7e0fd95 <+85>:    mov    DWORD PTR [esp],0xb7e18e8a
   0xb7e0fd9c <+92>:    call   0xb7e826a0 
---Type 
   0xb7e0fda1 <+97>:    mov    eax,0x1
   0xb7e0fda6 <+102>:    jmp    0xb7e0fe12 
   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 
   0xb7e0fdce <+142>:    mov    DWORD PTR [ebp-0x10],0x0
   0xb7e0fdd5 <+149>:    jmp    0xb7e0fe07 
   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 
   0xb7e0fdf2 <+178>:    add    DWORD PTR [ebp-0xc],0x1
   0xb7e0fdf6 <+182>:    cmp    DWORD PTR [ebp-0xc],0xe
   0xb7e0fdfa <+186>:    jne    0xb7e0fe03 
   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 
   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 
#include
#include
#include
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,
段错误 (核心已转储)
从以上输出看,基本可以算破解完成了,其实由于http_request是导出函数,第二处可以不用patch的方式,可以直接定义一个同名的导出函数,这个函数返回0即可。 其实有很多细节要分析,程序获取本地的mac地址、IP地址、/lost-found文件的时间信息,将这些信息发送给服务器,服务器发回来一些字符信息,对这些信息抑或判断是否为目标结果,另外程序将mac地址的一部分赋值给变量参与程序解密,和其他运算。其实在第一个patch前的函数调用和对比可以推测目标机器的mac,ip等信息,另外需要找出所有引用mac的地方,然后伪造正确的变量关联值。由于要分析的而函数细节太多,而且需要寻找mac的所有引用,所以借助IDA最好不过。
Part3 完全终极破解
先给出最终破解的LD_PREOLOAD动态库hook源程序
vim libx.c
#include 
#include
#include
#include
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进行分析步骤如下:
(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)在分析前先获取解密后的程序快照,这样子下次就不用重新运行动态解密代码,就可以直接分析了。



[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
免费 0
支持
分享
最新回复 (3)
雪    币: 50161
活跃值: (20670)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
2
图片没有过来
2017-3-23 22:38
0
雪    币: 7142
活跃值: (4402)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
图片地址是对的, 显示出来的怎么是防盗链的
2017-3-23 22:51
0
雪    币: 10210
活跃值: (3446)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
图片不显示,附件不存在
2017-3-24 13:02
0
游客
登录 | 注册 方可回帖
返回
//