首页
社区
课程
招聘
[原创]铁棒计划<1>
发表于: 2007-4-15 09:56 12735

[原创]铁棒计划<1>

2007-4-15 09:56
12735

铁棒计划<1>
kflnig
        有比cracker更加了解破解所在吗?所以就让我这个cracker来谈谈软件保护的方法。本文基于borland c++6.0调试通过。你最好懂点内联汇编。如有错误告诉我。我家里没有上网,手头的开发资料只有borland的帮助文档(其实很不错)。
首先是anti         OD。
1、IsDebuggerPresent
        BOOL IsDebuggerPresent(VOID)
        这个函数相信大家用得最多。但是也没有什么用处。随便都可以被搞定。
微软说:
If the current process is running in the context of a debugger, the return value is nonzero.
If the current process is not running in the context of a debugger, the return value is zero.
翻译过来就是:
如果当前的应用程序在调试器下运行,则返回值不为0,如果不在调试器下运行,则返回值为0。
通常我们可以这么写:
        bool isdebuggeron;
isdebuggeron=IsDebuggerPresent();
if (isdebuggeron)
        {……
        }
省略号表示你想要的代码。当然傻子才会这么写。我们用OD一调试就可以在输入函数中看到IsDebuggerPresent,而IsDebuggerPresent的作用只有用来发现调试器。所以,这个东西绝对不可以出现在输入函数列表之中。
        让它不显示在输入表中方法很多,我来介绍一种不常用的。
        我们用IDA分析kernel32.dll。找到IsDebuggerPresent如下:
; BOOL IsDebuggerPresent(void)
.text:7C812E03                 public IsDebuggerPresent
.text:7C812E03 IsDebuggerPresent proc near             ; CODE XREF: .text:loc_7C874FB1 p
.text:7C812E03                 mov     eax, large fs:18h
.text:7C812E09                 mov     eax, [eax+30h]
.text:7C812E0C                 movzx   eax, byte ptr [eax+2]
.text:7C812E10                 retn
.text:7C812E10
.text:7C812E10 IsDebuggerPresent endp
        然后我们来自己写一个my IsDebuggerPresent函数。
bool my IsDebuggerPresent ()
{
__asm
{
        mov     eax, large fs:18h
        mov     eax, [eax+30h]
        movzx   eax, byte ptr [eax+2]
}
}
这个就是我们自己的 IsDebuggerPresent,不会再在输入表中出现 IsDebuggerPresent啦!
简单实用!
2、FindWindow
HWND FindWindow(

    LPCTSTR lpClassName,        // pointer to class name
    LPCTSTR lpWindowName         // pointer to window name
   );       

Parameters

lpClassName

Points to a null-terminated string that specifies the class name or is an atom that identifies the class-name string. If this parameter is an atom, it must be a global atom created by a previous call to the GlobalAddAtom function. The atom, a 16-bit value, must be placed in the low-order word of lpClassName; the high-order word must be zero.

lpWindowName

Points to a null-terminated string that specifies the window name (the window's title). If this parameter is NULL, all window names match.

Return Values
If the function succeeds, the return value is the handle to the window that has the specified class name and window name.
If the function fails, the return value is NULL. To get extended error information, call GetLastError.
上面的是很简单的英语。
lpClassName是指向类名字符串的指针。
lpWindowName是窗口标题。如果这个参数值为NULL,则所有的类名都会受到检测。
返回值,如果函数调用成功,返回窗口的句柄。如果不成功,返回NULL。
我们用OD自己的插件窗口工具,就可以知道,它的类名是OllyDBG。lpWindowName我们传入NULL就好了。
char *str="OllyDBG";
h_od=FindWindow(str,NULL);
if (h_od)
{
……
}
这样写就好了。如果OD开着,那么,我们就可以发现它。
3、GetTickCount
这个可是老牌的检测方法了。老,但是经典。
我们可以根据因为在调试的时候,在某些地方时间和原来的直接载入相比暂停太多了。所以可以根据时间的长短来猜测是否被调试。我这边设置的是1s。
int t1,t2;
t1=GetTickCount();
……
t2=GetTickCount();
if (t2-t1>1000)
{
……
}

上面是最简单和最常用的三个。

我们听说过虚拟机吧。听起来怎么都不像是一种简单的菜鸟可以实现的方法。其实,有些时候是我们自己太低估自己了。虚拟机,最通俗的讲就是把一条指令用另外的方式实现。
我们可以在设计算法的时候可以先把算法高级语言汇编化,然后再把汇编中的,这些指令用这些函数代替。当然我们有一些个我们自己的原则
1、        逻辑关系复杂的要
2、        能够让cracker看懂了之后赞叹的要
3、        废代码和效率低下的不要
4、        执行速度特别慢的不要
我想了一个礼拜。终于想到一点可以见人的代码。所以就贴出来给大家看看。
我们知道
0 xor 0=0
0 xor 1=1
1 xor 0=1
1 xor 1=0

0 and 0=0
0 and 1=0
1 and 0=0
1 and 1=1

0 or 0=0
0 or 1=1
1 or 0=1
1 or 1=1
得出一条定律:xor+and=or。
int and_(int a,int b)
{
__asm
{
        mov eax,a
        mov ebx,eax
        or eax,b
        xor ebx,b
        sub eax,ebx
}
}

int or_(int a,int b)
{
__asm
{
        mov eax,a
        mov ebx,eax
        and eax,b
        or ebx,b
        add eax,ebx
}
}

int xor_(int a,int b)
{
        __asm
        {
                mov eax,a
                mov ebx,eax
                or eax,b
                and ebx,b
                sub eax,ebx
        }
}

上面这写个xor,and,or是通过逻辑关系模拟实现的。让我们再来根据它们的定义来写一些个函数。
二进制下:
and是同为1时则为1否则为0
int myand(int a,int b)
{
        __asm
        {
                mov eax,a
                mov ebx,b
                mov ecx,32
                xor edx,edx
l4:
                shl edx,1
                shl eax,1
                jc l1
                shl ebx,1
                jmp l3
                l1:
                shl ebx,1
                adc edx,0
                l3:
                dec ecx
                jnz l4
                mov eax,edx
        }
}
这个东西代码质量一般,但是为了符合我上面的代码,所以我为了易读性,舍弃了一些效率。稍微解释一下。我们依次把a和b依次左移1位,如果两个都有溢出,那么edx=edx+1,否则就是edx=edx+0。还要依次让EDX左移。如果你非要把上面的代码看懂,那么对汇编指令的了解是不可以少的。比如说只有你了解了ADC,SHL指令会对标志寄存器有影响,再加上对它们本身的了解才能看懂。
写这个东西太费脑力了。or和xor我不写了。


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 7
支持
分享
最新回复 (22)
雪    币: 494
活跃值: (629)
能力值: ( LV9,RANK:1210 )
在线值:
发帖
回帖
粉丝
2
支持k同学,但那个似乎还不算vm,只是代码变形
2007-4-15 11:36
0
雪    币: 1946
活跃值: (248)
能力值: (RANK:330 )
在线值:
发帖
回帖
粉丝
3
有困难.找softworm
2007-4-15 17:20
0
雪    币: 846
活跃值: (221)
能力值: (RANK:570 )
在线值:
发帖
回帖
粉丝
4
xor+and=or

这个学习了

你的方法可以用于VM指令的执行部分
2007-4-15 19:34
0
雪    币: 1946
活跃值: (248)
能力值: (RANK:330 )
在线值:
发帖
回帖
粉丝
5
笨笨熊大人也在研究这玩意儿?
有空联系下一起研究研究。
2007-4-15 19:43
0
雪    币: 846
活跃值: (221)
能力值: (RANK:570 )
在线值:
发帖
回帖
粉丝
6
我是研究怎么写的,不是研究怎么破的。暂时只限于理论研究~~

执行模块方面,你觉得直接用相同的X86指令模拟好不好?例如将加法操作的SRC和DEST解释之后,将SRC和DEST的具体数值分别放到EDX和EBX,然后ADD EDX,EBX。最后将EDX回写到DEST。

这样只需要简单的一条ADD EDX,EBX。然后可以插入大量垃圾指令。

另外一个方案就是用复杂的等效指令实现了。例如LZ的。这样可以插入的垃圾指令要少一些。对于个人来说编码量就要大一点了(垃圾指令我准备写一个混淆器,自动插入)

前阵子看了CPU的基本流程。发现IA 64和RISC为了兼容X86体系,内置一个X86的译码器。

如果在执行模块里面再添加一个译码器,先翻译,然后再到具体的执行模块。

这样在改变指令格式的时候,便不用重写执行模块了。感觉上代码重用性会比较高。不过不知道会不会因此而减少破解难度
2007-4-15 20:10
0
雪    币: 1946
活跃值: (248)
能力值: (RANK:330 )
在线值:
发帖
回帖
粉丝
7
速度稍微注意下就好了。
太过注重速度会很搞得很麻烦。
假想一下CPU以后的速度.

[引用]
执行模块方面,你觉得直接用相同的X86指令模拟好不好?例如将加法操作的SRC和DEST解释之后,将SRC和DEST的具体数值分别放到EDX和EBX,然后ADD EDX,EBX。最后将EDX回写到DEST。
[/引用]

解释之后并不一定就要这两个寄存器.只是在存放时应该存放在对应的VM_Context中就好了.
2007-4-15 20:18
0
雪    币: 189
活跃值: (56)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
8
gz

gz
2007-4-15 20:25
0
雪    币: 846
活跃值: (221)
能力值: (RANK:570 )
在线值:
发帖
回帖
粉丝
9
用这两个寄存器是因为我一开始设想是直接用对应的X86指令来虚拟,然后保留几个寄存器作为VM本身的寄存器。

字符串指令占用EAX ESI EDI
REP PREFIX和LOOP指令占用ECX

剩下的就只有EDX,EBX和EBP了。

当然在X86转VM CODE时,为VM预留寄存器没有什么意义。

但是如果在之后提供C 转VM CODE的编译器时。使用CPU本身的寄存器,便可以减少内存读写操作,获得更好的效率了。

假设人可以接受的响应时间为定值,越有效率的VM执行模块代表你能在其中插入越多的垃圾指令,强度也就越高了
2007-4-15 21:42
0
雪    币: 1946
活跃值: (248)
能力值: (RANK:330 )
在线值:
发帖
回帖
粉丝
10
REP PREFIX和LOOP指令我还没有想好怎么写,你是怎么想的呢?
2007-4-15 22:21
0
雪    币: 846
活跃值: (221)
能力值: (RANK:570 )
在线值:
发帖
回帖
粉丝
11
我还没开始想每一条指令怎么模拟

实在不行,就不模拟
2007-4-16 09:56
0
雪    币: 263
活跃值: (10)
能力值: ( LV9,RANK:210 )
在线值:
发帖
回帖
粉丝
12
学习中……探索中……
2007-4-16 10:54
0
雪    币: 214
活跃值: (70)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
13
xor+and=or
2007-4-18 11:59
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
支持!!学习就是要全面些,很有帮助,
希望继续铁棒,最后变成
2007-4-24 03:02
0
雪    币: 301
活跃值: (300)
能力值: ( LV9,RANK:290 )
在线值:
发帖
回帖
粉丝
15
好文,学习
2007-4-24 07:23
0
雪    币: 6075
活跃值: (2236)
能力值: (RANK:1060 )
在线值:
发帖
回帖
粉丝
16
很像我之前无疾而终的brainfuck x86
2007-5-28 12:43
0
雪    币: 6075
活跃值: (2236)
能力值: (RANK:1060 )
在线值:
发帖
回帖
粉丝
17
这个我初中好像推过,当时的想法大概是:

A xor B 的意思是相同的为0,不同的为1

那要怎么获得相同的呢?

A and B 结果是都为1的为1

A or B 结果是有一个1的就为1

只有一个1叫做不同,那么含1的去掉两个都是1的就是只有1个1

于是

A or B - A and B = A xor B

or-and=xor
2007-5-28 13:54
0
雪    币: 331
活跃值: (56)
能力值: ( LV13,RANK:410 )
在线值:
发帖
回帖
粉丝
18
真值表~~~~
2007-5-28 19:28
0
雪    币: 146
活跃值: (33)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
19
插不上话,顶一下
2007-5-28 19:34
0
雪    币: 2319
活跃值: (565)
能力值: (RANK:300 )
在线值:
发帖
回帖
粉丝
20
同意,任何复杂的 logic operation 组合,最后都可以透过看 truth table,知道它与甚么 “相等” 和 “互通”
2007-5-28 19:41
0
雪    币: 1946
活跃值: (248)
能力值: (RANK:330 )
在线值:
发帖
回帖
粉丝
21
牛人们开始YY似的说听不懂的话了。
2007-5-28 19:48
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
很不错啊,学习了啊
呵呵
2007-5-29 00:26
0
雪    币: 99
活跃值: (186)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
呵呵 离散数学中的数理逻辑
2007-6-11 14:14
0
游客
登录 | 注册 方可回帖
返回
//