XiangXiangRen
总版主
总版主
  • 注册日期2003-02-22
  • 最后登录2015-09-01
  • 粉丝13
  • 关注0
  • 积分1042分
  • 威望472点
  • 贡献值1点
  • 好评度145点
  • 原创分13分
  • 专家分1分
阅读:2864回复:10

Windows 驱动编码中的简单反阅读手法(1)

楼主#
更多 发布于:2008-03-20 14:17
    本文作者上海楚狂人,有问题请联系QQ16191935,Email mfc_tan_wen@163.com

    好像驱网文件版的windows内核驱动开发者都深受反汇编的困绕。这篇文章讨论在驱动编码中采用特殊的编码手法,来简单的防止反汇编阅读。避免“一览无余”的状况。这种方法不同于加壳加密。加壳和加密的方法比较单一,从而容易被人以同样单一的方法整体解密。同时驱动中进行加密稍显复杂,有时有一些稳定性方面的障碍。
    刘涛涛的“扭曲变换”是根本的防止逆向解决方案。但是需要付出巨大的艰辛。
    下面的一些方法是比较简单的操作手法。这样的做法显然不能完全防止逆向。但是能起到一定的“遮掩”作用。好处是随时可以做。而且可以个人根据代码的重要程度,决定哪些部位做,那些部位不做。

1.混淆字符串

    

    在代码中,出现了直接字符串是非常令人恼火的一件事。往往这些常数字符串在反汇编的时候会直接被人看见,对反工程者是最好的引导。

    以下的代码都会暴露我们的字符串:

    char *str = "mystr";
    wchar_t buffer[2] = { L"hello,world."};
    UNICODE_STRING my_str;
    RtlInitUnicodeString(&my_str,L"hello,world");

    隐蔽这些常数字符串并不能完全防止反工程。但是毫无疑问的是,会给反工程增加很多麻烦。

    隐蔽这些字符串的手法是:在写代码的时候并不写字符串的明文。而是书写密文。总是在使用之前解密。这样的手法的后果是,在静态反汇编的时候,反工程者是看不见字符串的。但是,反汇编者显然会在调试的时候看见字符串。

    在调试的时候才看见合法的字符串比静态反汇编的时候看见字符串要麻烦许多。因为一般只有对一段代码有兴趣才去调试它。而之所以对那段代码有兴趣,有些时候是因为看见了感兴趣的字符串。全部跟踪调试所有的代码,是艰巨的任务,只有具有重大价值的目标才值得那样去做。

    不过有点要注意的是,你不能把所有的字符串都用同样的一招进行加密。至少不能用相同的密钥。否则,也许一个简单的解密程序就把你所有的字符串恢复为明文了。

    考虑:
    char *str = "mystr";

    改为:
    char str[] = { 'm' ^ 0x12, 'y' ^ 0x12, 's' ^ 0x12 ,'t' ^ 0x12,'r' ^ 0x12,'\0' ^ 0x12}
    
    如果认为异或是最简单的加密方法,那么0x12就是这里的密钥。现在编译出来的字符串已经面目全非了。当然,要解密这个字符串需要一个函数。
    
    char *dazeEstr(char *str, char key)
    {    
        char *p = str;
        while( ((*p) ^=  key) != '\0')
        {    p++;    }
        return str;
    }

    你可以试试静态反汇编下面的代码.
    char *str = { 'm' ^ 0x12, 'y' ^ 0x12, 's' ^ 0x12 ,'t' ^ 0x12,'r' ^ 0x12,'\0' ^ 0x12};
    dazeEstr(str,0x12);
    printf(str);

    当然你通过分析一定会知道printf的结果。调试也可以知道结论。但是比直接用眼睛可以看见可是麻烦多了。当然这样写代码也有些让人抓狂。但是,你总是可以先按自己的本来的习惯写完代码,然后把关键的字符串这样处理。

    下面是一个宽字符的处理方法:

    wchar_t *dazeEstrW(wchar_t *str, wchar_t key)
    {    
        wchar_t *p = str;
        while( ((*p) ^=  key) != '\0')
        {    p++;    }
        return str;
    }
        
    
    下面的代码:    

    UNICODE_STRING my_str;
    RtlInitUnicodeString(&my_str,L"hello,world");
        
    其实总是可以改为:

    wchar_t buffer[] = { L"hello,world."};
    UNICODE_STRING my_str;
    RtlInitUnicodeString(&my_str,buffer);

    那么加密的写法:

    wchar_t buffer[] = {
        L'h' ^ 0x3a,L'e' ^ 0x3a,L'l' ^ 0x3a,L'l' ^ 0x3a,
        L'o' ^ 0x3a,L' ^ 0x3a,'L'w' ^ 0x3a,L'o' ^ 0x3a,
        L'r' ^ 0x3a,L'l' ^ 0x3a,L'd' ^ 0x3a,L'.' ^ 0x3a,
        L'\0' ^ 0x3a};
    UNICODE_STRING my_str;
    RtlInitUnicodeString(&my_str,dazeEstrW(buffer,0x3a));
        
    比较明显的缺陷是书写常数字符串的时候变得麻烦。我一直在追寻更简单的写法。但是遗憾的是,我还没有找到。怎么说呢?如果你觉得隐蔽是值得的,那就这样做。你甚至可以用更复杂的加密算法。只要你能算出密文,然后填写在代码常数中。不过那样修改代码变得太困难了。如果你能写一个预编译工具自动修改代码,确实是一个好办法。不过对于一种仅仅防止肉眼直观看到字符串的方式,更复杂的加密方法往往没必要。因为无论多复杂的算法,解密算法都很容易在你自己的代码里找到。

2.字符串混淆的实例

    下面我们用一个实例来试试。

    写一个简单的驱动如下:

    #include <ntifs.h>

    NTSTATUS DriverEntry(
        PDRIVER_OBJECT driver,
        PUNICODE_STRING reg)
    {
        UNICODE_STRING my_str;
        RtlInitUnicodeString(&my_str,L"hello,world");

        DbgPrint("%wZ",&my_str);
        return STATUS_SUCCESS;
    }

    以上函数的Release版本用IDA反汇编的效果是这样的:

    MyDriverEntry   proc near               ; CODE XREF: start:loc_1402B

    DestinationString= UNICODE_STRING ptr -8

                 push    ebp
                 mov     ebp, esp
                 push    ecx
                 push    ecx
                 push    offset SourceString    ; "hello,world"
                 lea     eax, [ebp+DestinationString]
                 push    eax                 ; DestinationString
                 call    ds:RtlInitUnicodeString
                 lea     eax, [ebp+DestinationString]
                 push    eax
                 push    offset Format       ; "%wZ"
                 call    DbgPrint

                 pop     ecx
                 pop     ecx
                 xor     eax, eax
                 leave
                 retn    8

    MyDriverEntry   endp
        

    上面的情况,很容易读懂这段代码打印了一个"Hello,world".现在把c代码改成下面这样的:

    wchar_t *dazeEstrW(wchar_t *str, wchar_t key)
    {    
        wchar_t *p = str;
        while( ((*p) ^=  key) != '\0')
        {    p++;    }
        return str;
    }

    NTSTATUS DriverEntry(
        PDRIVER_OBJECT driver,
        PUNICODE_STRING reg)
    {
        wchar_t buffer[] = {
            L'h' ^ 0x3a,L'e' ^ 0x3a,L'l' ^ 0x3a,
            L'l' ^ 0x3a,L'o' ^ 0x3a,L'r' ^ 0x3a,
            L'w' ^ 0x3a,L'o' ^ 0x3a,L'r' ^ 0x3a,
            L'l' ^ 0x3a,    L'd' ^ 0x3a,L'.' ^ 0x3a,
            L'\0' ^ 0x3a};
        UNICODE_STRING my_str;
        dazeEstrW(buffer,0x3a);
        RtlInitUnicodeString(&my_str,buffer);
        DbgPrint("%wZ",&my_str);
        return STATUS_SUCCESS;
    }

    IDA反汇编的结果变成下面这样:

    MyDriverEntry   proc near               ; CODE XREF: start:loc_1402Bj

    DestinationString= UNICODE_STRING ptr -28h
    SourceString    = word ptr -20h
    var_1C          = word ptr -1Ch
    var_1A          = word ptr -1Ah
    var_18          = word ptr -18h
    var_16          = word ptr -16h
    var_14          = word ptr -14h
    var_12          = word ptr -12h
    var_10          = word ptr -10h
    var_E           = word ptr -0Eh
    var_C           = word ptr -0Ch
    var_A           = word ptr -0Ah
    var_8           = word ptr -8
    var_4           = dword ptr -4

         push    ebp
         mov     ebp, esp
         sub     esp, 28h
         mov     eax, dword_13000
         mov     [ebp+var_4], eax
         push    3Ah
         lea     eax, [ebp+SourceString]
         push    eax
         mov     [ebp+SourceString], 52h
         mov     word ptr [ebp-1Eh], 5Fh
         mov     [ebp+var_1C], 56h
         mov     [ebp+var_1A], 56h
         mov     [ebp+var_18], 55h
         mov     [ebp+var_16], 48h
         mov     [ebp+var_14], 4Dh
         mov     [ebp+var_12], 55h
         mov     [ebp+var_10], 48h
         mov     [ebp+var_E], 56h
         mov     [ebp+var_C], 5Eh
         mov     [ebp+var_A], 14h
         mov     [ebp+var_8], 3Ah
         call    sub_11000

         lea     eax, [ebp+SourceString]
         push    eax             ; SourceString
         lea     eax, [ebp+DestinationString]
         push    eax             ; DestinationString
         call    ds:RtlInitUnicodeString
         lea     eax, [ebp+DestinationString]
         push    eax
         push    offset Format   ; "%wZ"
         call    DbgPrint

         pop     ecx
         pop     ecx
         mov     ecx, [ebp+var_4]
         xor     eax, eax
         call    sub_110CD

         leave
         retn    8

    MyDriverEntry   endp

    这样一来,就多少有些挑战性了。调试的时候,当然可以看见正确的字符串。但是静态反汇编的时候,要看明白那段52h,5fh,56h,55h...是什么就不是一件简单的事情了。遗憾的是,这里用了RtlInitUnicodeString和DbgPrint。我们的读者还是很容易了解这段代码做了什么。下面尝试隐藏那些函数存在的信息。

    对于那些对技术有兴趣而进行发掘的破解者来说(出于兴趣而不是处于巨大的经济利益),很多时候,调用的api函数是除了字符串之外,对他们最重要的引导之一。一些有经验的windows内核程序反汇编者,对程序流程的兴趣不大,但是看看几个关键的调用,就已经明白了十之八九。所以,让对方在静态反汇编的时候看不到直观的windows内核api函数调用,是非常有意义的。

3.内核函数

    对于RtlInitUnicodeString这样简单的函数,本质上没有必要隐藏。但是要隐藏也比较简单,虽然我的方法比较蠢:自己写一个。由于这个函数涉及到字符串的混淆处理,其实后面会发现写一个还是有点好处的:

    VOID dazeRtlInitUnicodeString(
        IN OUT PUNICODE_STRING  DestinationString,
        IN PWCHAR SourceString,
        IN ULONG length,
        IN WCHAR key,
        )
    {
        if(key != 0)
            dazeEstrW(SourceString,key);
        DestinationString->Buffer = SourceString;
        DestinationString->Length = length - sizeof(WCHAR);
        DestinationString->MaximumLength = length;
    }

    上面这个函数的用法是,你可以当普通的RltInitUnicodeString用,那么key填写为0,用法如下:

    WCHAR buf[] = { L"Hello,world" };
    UNICODE_STRING str;
    dazeRtlInitUnicodeString(&str,buf,sizeof(buf),0);

    这样反汇编的时候当然看不见RtlInitUnicodeString这个调用了。这是显然的。

    需要隐藏的一般都是关键调用。这些调用给人以对这个系统体系的结构理解的指导。比如说如下一个驱动:

    NTSTATUS DriverEntry(
        PDRIVER_OBJECT driver,
        PUNICODE_STRING reg)
    {
        UNICODE_STRING name_str;
        RtlInitUnicodeString( &name_str, L"\\FileSystem\\Filters\\MyFilterCDO" );
        PDEVICE_OBJECT my_cdo;
        status = IoCreateDevice(
                                    driver,
                                    0,                      //has no device extension
                                    &name_string,
                                    FILE_DEVICE_UNKNOWN,
                                    FILE_DEVICE_SECURE_OPEN,
                                    FALSE,
                                    &my_cdo );
        return status;
    }

    这个驱动生成了一个有名字的控制对象。这个信息反汇编一下一定会马上真相大白。下面我们DIY那个关键的调用。其实我们没有DIY,只是封装了一下。并且用MmGetSystemRoutine获得函数地址。

    typedef NTSTATUS
      (*DAZE_IoCreateDevice)(
        IN PDRIVER_OBJECT  DriverObject,
        IN ULONG  DeviceExtensionSize,
        IN PUNICODE_STRING  DeviceName  OPTIONAL,
        IN DEVICE_TYPE  DeviceType,
        IN ULONG  DeviceCharacteristics,
        IN BOOLEAN  Exclusive,
        OUT PDEVICE_OBJECT  *DeviceObject
        );

    NTSTATUS
      dazeIoCreateDevice(
        IN PDRIVER_OBJECT  DriverObject,
        IN ULONG  DeviceExtensionSize,
        IN PUNICODE_STRING  DeviceName  OPTIONAL,
        IN DEVICE_TYPE  DeviceType,
        IN ULONG  DeviceCharacteristics,
        IN BOOLEAN  Exclusive,
        OUT PDEVICE_OBJECT  *DeviceObject)
    {
        wchar_t name_buf[] = {
            'I' ^ 0x98,
            'o' ^ 0x98,
            'C' ^ 0x98,
            'r' ^ 0x98,
            'e' ^ 0x98,
            'a' ^ 0x98,
            't' ^ 0x98,
            'e' ^ 0x98,
            'D' ^ 0x98,
            'e' ^ 0x98,
            'v' ^ 0x98,
            'i' ^ 0x98,
            'c' ^ 0x98,
            'e' ^ 0x98,
            '\0'  ^ 0x98};
        UNICODE_STRING name_str;
        DAZE_IoCreateDevice function_pt;
        dazeRtlInitUnicodeString(&name_str,name_buf,sizeof(name_buf),0x98);
        function_pt = (DAZE_IoCreateDevice)MmGetSystemRoutineAddress(&name_str);    
        if(function_pt != NULL)
            return function_pt(
                DriverObject,DeviceExtensionSize,
                DeviceName,DeviceType,
                DeviceCharacteristics,
                Exclusive,DeviceObject);
        return STATUS_UNSUCCESSFUL;
    }

4.内核函数隐藏反汇编结果

    让人觉得有成就感的是,反汇编的结果,驱动里的Import表中,IoCreateDevice这个名字消失了。由于我们用了前面的字符串处理方法,同时字符串中也看不到这个信息。以上这个函数反汇编的结果是这样的:

    sub_11054       proc near               ; CODE XREF: sub_11130+2Bp

    SystemRoutineName= UNICODE_STRING ptr -2Ch
    var_24          = dword ptr -24h
    var_20          = word ptr -20h
    var_1E          = word ptr -1Eh
    var_1C          = word ptr -1Ch
    var_1A          = word ptr -1Ah
    var_18          = word ptr -18h
    var_16          = word ptr -16h
    var_14          = word ptr -14h
    var_12          = word ptr -12h
    var_10          = word ptr -10h
    var_E           = word ptr -0Eh
    var_C           = word ptr -0Ch
    var_A           = word ptr -0Ah
    var_8           = word ptr -8
    var_4           = dword ptr -4
    arg_0           = dword ptr  8
    arg_4           = dword ptr  0Ch
    arg_8           = dword ptr  10h
    arg_C           = dword ptr  14h
    arg_10          = dword ptr  18h
    arg_14          = dword ptr  1Ch
    arg_18          = dword ptr  20h

        push    ebp
        mov     ebp, esp
        sub     esp, 2Ch
        mov     eax, dword_13000
        push    98h
        mov     [ebp+var_4], eax
        push    1Eh
        lea     eax, [ebp+var_24]
        push    eax
        lea     eax, [ebp+SystemRoutineName]
        push    eax
        mov     word ptr [ebp+var_24], 0D1h
        mov     word ptr [ebp+var_24+2], 0F7h
        mov     [ebp+var_20], 0DBh
        mov     [ebp+var_1E], 0EAh
        mov     [ebp+var_1C], 0FDh
        mov     [ebp+var_1A], 0F9h
        mov     [ebp+var_18], 0ECh
        mov     [ebp+var_16], 0FDh
        mov     [ebp+var_14], 0DCh
        mov     [ebp+var_12], 0FDh
        mov     [ebp+var_10], 0EEh
        mov     [ebp+var_E], 0F1h
        mov     [ebp+var_C], 0FBh
        mov     [ebp+var_A], 0FDh
        mov     [ebp+var_8], 98h
        call    sub_11024

        lea     eax, [ebp+SystemRoutineName]
        push    eax             ; SystemRoutineName
        call    ds:MmGetSystemRoutineAddress
        test    eax, eax
        jz      short loc_110F7

        push    [ebp+arg_18]
        push    [ebp+arg_14]
        push    [ebp+arg_10]
        push    [ebp+arg_C]
        push    [ebp+arg_8]
        push    [ebp+arg_4]
        push    [ebp+arg_0]
        call    eax
        jmp     short loc_110FC

    loc_110F7:                              ; CODE XREF: sub_11054+88j
        mov     eax, 0C0000001h


    loc_110FC:                              ; CODE XREF: sub_11054+A1j
        mov     ecx, [ebp+var_4]
        call    sub_11176

        leave
        retn    1Ch

    sub_11054       endp

    以上的反汇编结果,可以确定调用了某个函数。但是究竟是什么函数,需要进行解密或者动态调试。直接阅读的困难度就大大增加了。


5.系统函数隐藏通用写法

    写一个封装的隐藏型函数还是有一定工作量的。但是通过使用宏定义,可以让工作简化。而且好处是,你这些工作不会浪费。如果你写了一个头文件和一个C文件,那么只要加入到你的任何工程中,你的那个工程就可以享受“隐藏函数”的服务。这些工作是值得的。

    以下这个宏定义把剩余工作变成简单流程:

    #define DAZE_FUNC_BODY(func_name,key) \
        UNICODE_STRING name_str; \
        DAZE_##func_name function_pt; \
        dazeRtlInitUnicodeString(&name_str,name_buf,sizeof(name_buf),key); \
        function_pt = (DAZE_##func_name)MmGetSystemRoutineAddress(&name_str);    \
        if(function_pt != NULL) \
            return function_pt(


        
    (1)把函数原型从帮助中拷贝出来,稍加修改(加两个括弧、一个星号和一个DAZE_前缀)如下:

    typedef NTSTATUS
          (*DAZE_IoCreateDevice)(
        IN PDRIVER_OBJECT  DriverObject,
        IN ULONG  DeviceExtensionSize,
        IN PUNICODE_STRING  DeviceName  OPTIONAL,
        IN DEVICE_TYPE  DeviceType,
        IN ULONG  DeviceCharacteristics,
        IN BOOLEAN  Exclusive,
        OUT PDEVICE_OBJECT  *DeviceObject
        );

    然后开始封装,封装固定的方法如下:

    NTSTATUS
      dazeIoCreateDevice(
        IN PDRIVER_OBJECT  DriverObject,
        IN ULONG  DeviceExtensionSize,
        IN PUNICODE_STRING  DeviceName  OPTIONAL,
        IN DEVICE_TYPE  DeviceType,
        IN ULONG  DeviceCharacteristics,
        IN BOOLEAN  Exclusive,
        OUT PDEVICE_OBJECT  *DeviceObject)
    {
        wchar_t name_buf[] = {
            'I' ^ 0x98,
            'o' ^ 0x98,
            'C' ^ 0x98,
            'r' ^ 0x98,
            'e' ^ 0x98,
            'a' ^ 0x98,
            't' ^ 0x98,
            'e' ^ 0x98,
            'D' ^ 0x98,
            'e' ^ 0x98,
            'v' ^ 0x98,
            'i' ^ 0x98,
            'c' ^ 0x98,
            'e' ^ 0x98,
            '\0'  ^ 0x98};
        DAZE_FUNC_BODY(IoCreateDevice,0x98)
                DriverObject,DeviceExtensionSize,
                DeviceName,DeviceType,
                DeviceCharacteristics,
                Exclusive,DeviceObject);
        return STATUS_UNSUCCESSFUL;
    }
    
    先写加密字符串来表示函数名。然后用宏,最后填上所有的参数,最后返回不成功即可。

    如果你觉得你调用的这个函数不想轻易让人看见,就这样做吧。    
    
    上面的手法只“遮盖”了字符串和函数调用。下面再增加一些简单方法来“遮盖”各种数据、处理流程等等。

最新喜欢:

snoxsnox
looksail
荣誉会员
荣誉会员
  • 注册日期2005-05-22
  • 最后登录2014-03-15
  • 粉丝2
  • 关注0
  • 积分1016分
  • 威望991点
  • 贡献值0点
  • 好评度239点
  • 原创分0分
  • 专家分0分
沙发#
发布于:2008-03-20 16:36
难得一见的好贴,顶!
提问归提问,还是只能靠自己
wwg266543
驱动小牛
驱动小牛
  • 注册日期2007-07-03
  • 最后登录2014-08-27
  • 粉丝3
  • 关注1
  • 积分3分
  • 威望882点
  • 贡献值0点
  • 好评度9点
  • 原创分0分
  • 专家分54分
板凳#
发布于:2008-03-20 18:02
顶,学习了
dable_cn
驱动牛犊
驱动牛犊
  • 注册日期2005-09-05
  • 最后登录2009-05-25
  • 粉丝0
  • 关注0
  • 积分30分
  • 威望5点
  • 贡献值0点
  • 好评度4点
  • 原创分0分
  • 专家分0分
地板#
发布于:2008-03-20 20:04
受教了,好贴!
alwaysrun
驱动小牛
驱动小牛
  • 注册日期2006-06-01
  • 最后登录2016-01-09
  • 粉丝0
  • 关注0
  • 积分1059分
  • 威望752点
  • 贡献值1点
  • 好评度98点
  • 原创分0分
  • 专家分0分
地下室#
发布于:2008-03-21 13:16
不错,学习了
一颗平常的心!
wowocock
VIP专家组
VIP专家组
  • 注册日期2002-04-08
  • 最后登录2016-01-09
  • 粉丝16
  • 关注2
  • 积分601分
  • 威望1651点
  • 贡献值1点
  • 好评度1227点
  • 原创分1分
  • 专家分0分
5楼#
发布于:2008-03-21 14:36
MmGetSystemRoutineAddress也应该不要直接用,不然人家直接在那地方下个断点,你所有的函数也就一览无遗了.自己枚举PE来定位所有函数,同时在里面多设点陷阱,加上反调试功能,嘿嘿......
花开了,然后又会凋零,星星是璀璨的,可那光芒也会消失。在这样 一瞬间,人降生了,笑者,哭着,战斗,伤害,喜悦,悲伤憎恶,爱。一切都只是刹那间的邂逅,而最后都要归入死亡的永眠
geland
驱动牛犊
驱动牛犊
  • 注册日期2003-12-28
  • 最后登录2016-01-09
  • 粉丝0
  • 关注0
  • 积分25分
  • 威望251点
  • 贡献值0点
  • 好评度54点
  • 原创分0分
  • 专家分0分
6楼#
发布于:2008-03-22 10:47
这个一定要顶!
flyingbearboy
驱动牛犊
驱动牛犊
  • 注册日期2007-01-12
  • 最后登录2016-06-07
  • 粉丝0
  • 关注0
  • 积分15分
  • 威望99点
  • 贡献值0点
  • 好评度30点
  • 原创分0分
  • 专家分0分
7楼#
发布于:2008-03-25 15:52
好贴 顶一个
dreamsity
驱动小牛
驱动小牛
  • 注册日期2006-09-01
  • 最后登录2013-07-04
  • 粉丝0
  • 关注0
  • 积分40分
  • 威望821点
  • 贡献值1点
  • 好评度68点
  • 原创分1分
  • 专家分0分
8楼#
发布于:2008-03-27 19:15
其他一些方法:
(1)使用try except修改代码执行流程,比如func_a调用函数func_b,函数func_b调用函数func_c,在函数func_c中构造一个异常,在函数func_a中捕获。
(2)使用花指令修改函数体的实现,导致反汇编器无法正常识别。
(3)所有的函数全部使用动态分配的内存中的函数表调用,不使用任何直接调用。函数表在运行的过程中,动态修改。
(4)使用自己编写的PE的导出表解析工具,在运行的时候动态构造函数调用表。
(5)所有字符串全部加密
(6)关闭所有调试信息
(7)在驱动内构造一个解释器,在可执行程序存放中间语言执行代码
一切都是时间问题!
dreamsity
驱动小牛
驱动小牛
  • 注册日期2006-09-01
  • 最后登录2013-07-04
  • 粉丝0
  • 关注0
  • 积分40分
  • 威望821点
  • 贡献值1点
  • 好评度68点
  • 原创分1分
  • 专家分0分
9楼#
发布于:2008-03-27 19:18
(8)加密核心函数,运行的时候动态解密,函数调用完毕后,就动态释放。
一切都是时间问题!
gnoynehc
驱动牛犊
驱动牛犊
  • 注册日期2006-03-21
  • 最后登录2010-03-05
  • 粉丝0
  • 关注0
  • 积分0分
  • 威望13点
  • 贡献值0点
  • 好评度12点
  • 原创分0分
  • 专家分0分
10楼#
发布于:2008-03-31 22:33
good
Spring Chen
游客

返回顶部