首页
论坛
专栏
课程

也谈 HOW TO CODE A LOADER

2005-5-25 21:03 10642

也谈 HOW TO CODE A LOADER

2005-5-25 21:03
10642
附件:a_loader_oep.rar

标题:《也谈code a loader》
作者:oep
时间:2005.5.25

一、前言
    还没进入正题前,请先翻看Iczelion的《Win32调试API》,还有nbw翻译的(How to code a loader)。
    KeyMaker做的内存注册机可以得到本机号码,并且必须是明码的,KeyMaker无法运算远程用户电脑里
的注册号。如果能改进Kaymaker生成的代码,让内存注册机算任意程序中的注册号,不是更完美的loader吗?
这样我们就不必要花太多的时间去研究研究软件的算法了,而且现在很多软件中为了防止跟踪和破解他们的
算法,都在程序循环中加入了大量的废循环和多种类型的障碍。
    曾建议过树袋熊要求他在Keymaker中加入这种代码,可以把截取出来的数据为我们自己所调用。但树袋熊
说加入这种代码后错误太多,所以在3年后的今天,我们只有用自己手动构造这种代码了。:(
     另:以前讲loader的文章也很多的,如果觉得我是在说废话,千万不要向我扔西瓜皮吆:)

二、原理
    普通的软件算法都是这样的:
        根据机器号(x)-->经过算法运算f(x)-->得到真实的注册号明码或者暗码
    特别是暗码,在Keymaker中是无法截取的。
   
      比如以下crackme的代码:
        0040B8DC    > \68>push eProject.0054BA58             ;  ASCII "BCDFGHJKMPQRTVWXY2346789"
        0040B8E1    .  53 push ebx                           ;这里压入机器号码
        0040B8E2    .  56 push esi                           ;
        0040B8E3    .  8D>lea ecx,dword ptr ss:[esp+20]
        0040B8E7    .  51 push ecx
        0040B8E8    .  8D>lea ecx,dword ptr ss:[esp+20]
        0040B8EC    .  E8>call eProject.00409AE0             ;第一次进行大量的算法循环变形
        0040B8F1    .  8B>mov eax,dword ptr ds:[edi+C]       ;运算结果返回EAX中,此处是暗码
        ........
        ........
        ........  
        0040B9FE    .  68>push eProject.0054BA04             ;  ASCII "P6VQJ392XHYRB7GK8MTD4WCF"
        0040BA03    .  50 push eax                           ;上面第一次计算的结果,是暗码
        0040BA04    .  56 push esi                          
        0040BA05    .  8D>lea eax,dword ptr ss:[esp+34]
        0040BA09    .  50 push eax
        0040BA0A    .  8D>lea ecx,dword ptr ss:[esp+20]
        0040BA0E    .  E8>call eProject.00409AE0             ;这里第二次进入大量的循环和变形
        0040BA13    .  50 push eax  ------------------------ ;这里产生正确的注册号码:)

       CS:40BA13时EAX指向堆栈的指针,指针又指向了准确的注册号。所以我们现在要获得EAX指向的
堆栈指针,再把指针数据所指向的数据截获,就可以得到正确的注册号了。
       如果只是简单的得到本机的注册号,不言而喻,那完全可以用KeyMaker截获CS:40BA13,就可以得到
准确的结果了。但我们这里要谈的是:!!!计算远程计算机上的号码!!! 况且,如果CS:40BA13处得到的是暗
码呢? hoho,let's go

三、实战

      1、程序在运行的时候,都会显示给你机器号的。如果我们在CS:40B8E1处,压入其他电脑上的机器号,
那在CS:40BA13处得到的注册号又会是谁的呢?哈,想到了吗?
       你一定猜测到了:得到的注册号就是CS:40B8E1所压入堆栈的机器号所对应的注册号码。

      2、想法是不错吧?但如何让程序来自动完成呢?不是每个人都能熟练的运用OD和SOFTICE的,假如你
要让一个全不懂电脑的人来操作(呵呵,夸张了),他又怎么知道如何做到呢?那就需要我们自己来写程序,
这就是要打造我们自己的loader。
        在需要构造我们的代码前,有几个API函数必须让你了解的,如果你还不熟练,我建议你去查查
《Microsoft?Win32?Programmer's Reference 》和API手册,他们是:
     CreateProcess ,WriteProcessMemory,ReadProcessMemory,GetThreadContext,SetThreadContext,
     WaitForDebugEvent

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 核心代码如下(win32汇编语言):
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.................................
.................................
invoke GetStartupInfo,addr startinfo
invoke CreateProcess, addr Crackme, NULL, NULL, NULL, FALSE,\
                      DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS, NULL, NULL,\
                      addr startinfo, addr pi
.while TRUE
       invoke WaitForDebugEvent, addr DBEvent, INFINITE
       .if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
          invoke  WriteProcessMemory,pi.hProcess,040B8E1h,addr newtext,1,NULL
          invoke  WriteProcessMemory,pi.hProcess,040BA13h,addr newtext,1,NULL
          invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId,DBG_CONTINUE
          .continue
       .elseif DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
          invoke wsprintf, addr buffer, addr ExitProc, TotalInstruction
          ;invoke MessageBox, 0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION
          .break
       .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
           .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
             mov context.ContextFlags, CONTEXT_FULL
             invoke GetThreadContext, pi.hThread, addr context
             .if     context.regEip == 040BA14h
              dec     context.regEip
              invoke  WriteProcessMemory,pi.hProcess,040BA13h,addr oldbyte,1,NULL
              mov eax,context.regEax            
              invoke ReadProcessMemory,pi.hProcess,eax,addr value,4,NULL
              mov eax,value
              invoke ReadProcessMemory,pi.hProcess,eax,addr BUFFER,01Dh,NULL
              invoke SetThreadContext,pi.hThread, addr context  
              invoke  CloseHandle,pi.hProcess
              invoke  CloseHandle,pi.hThread
;             invoke TerminateProcess,pi.hProcess,-1        ;可采用强行退出的办法
              .break                                        ;这里中断循环
            .elseif  context.regEip == 040B8E2h
              dec     context.regEip
              invoke  WriteProcessMemory,pi.hProcess,040B8E1h,addr oldbyte1,1,NULL
              mov eax,context.regEsi
              invoke  WriteProcessMemory,pi.hProcess,eax,addr BUFFER,0Dh,NULL
              invoke SetThreadContext,pi.hThread, addr context  
              invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
              .continue
            .endif           
          .endif
       .endif
       invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId,DBG_CONTINUE
.endw
          invoke  CloseHandle,pi.hProcess
          invoke  CloseHandle,pi.hThread         ;再次调用CloseHandle,你能明白原因的:)
          ............................
          ............................                                       

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 上面代码分析如下:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;         

            <1>用DEBUG_PROCESS+DEBUG_ONLY_THIS_PROCESS标志创建我们的Crackme进程

            <2>用WaitForDebugEvent来等待调试事件的发生
                  invoke WaitForDebugEvent, addr DBEvent, INFINITE

            <3>crackme在入口处会向我们的窗口发送CREATE_PROCESS_DEBUG_EVENT消息,我们在这里
               修改程序的代码
              .if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
                   invoke  WriteProcessMemory,pi.hProcess,040B8E1h,addr int_3,1,NULL
                   invoke  WriteProcessMemory,pi.hProcess,040BA13h,addr int_3,1,NULL
                   invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId,DBG_CONTINUE
                   .continue
               就是向CS:40B7BD 和CS:40BA13处写入INT 3断点CC,然后让循环继续进行,等待我们的
               INT3断点发生

            <4> 第一次INT 3断点发生后。我们窗口收到EXCEPTION_DEBUG_EVENT消息,所以我们在这里判断
                是不是到达我们设定的IP地址了?所以调用
             mov context.ContextFlags, CONTEXT_FULL       ;表示全模式读写
             invoke GetThreadContext, pi.hThread, addr context
                程序首先运行到CS:40B8E1处,中断,此时EIP已经指向了CS:40B8E2,所以dec context.regEip
             就是让Crackme在恢复循环的时候,继续从CS:40B8E1执行。因为此时ESI中指向的地址就是机器号
             所以我们要得到机器号在Crackme中的地址,所以 mov eax,context.regEsi,得到地址后能操作吗?
             答案“是的”,调用invoke  WriteProcessMemory,pi.hProcess,eax,addr BUFFER,0Dh,NULL
              BUFFER中,就是窗口输入的机器号,我们虽然不能通过寄存器直接读写Crackme中的数据,
              但CreateProcess给了我们尚方宝剑,我们可以直接写Crackme的内存,这个内存正是ESI所要压栈
              的内容,此时,0D个字节写入后,我们自己电脑里的机器号,已经替换成其他电脑里的号码了。
              然后用SetThreadContext把EIP的数据写回去,并让Crackme恢复执行。.continue
               .elseif  context.regEip == 040B8E2h
                 dec     context.regEip
                 invoke  WriteProcessMemory,pi.hProcess,040B8E1h,addr oldbyte1,1,NULL ;恢复
                 mov eax,context.regEsi
                 invoke  WriteProcessMemory,pi.hProcess,eax,addr BUFFER,0Dh,NULL
                 invoke SetThreadContext,pi.hThread, addr context                  ;设置生效果  
                invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
              .continue                                                   ;继续循环

            <5>程序第2次发生INT 3断点的时候,被输入的机器号,正确的执行了算法,得到我们需要的
               结果了,呵呵,估计你已经猜测到了,重复上面<4>的步骤,得到RegEax中的数值,并从内存
               中把这个DWORD读出来,放到EAX中,再次读内存中EAX指向的数据,就是我们所需要的数据了

             .if     context.regEip == 040BA14h
              dec     context.regEip
              invoke  WriteProcessMemory,pi.hProcess,040BA13h,addr oldbyte,1,NULL
              mov eax,context.regEax            
              invoke ReadProcessMemory,pi.hProcess,eax,addr value,4,NULL
              mov eax,value
              invoke ReadProcessMemory,pi.hProcess,eax,addr BUFFER,01Dh,NULL
              invoke SetThreadContext,pi.hThread, addr context  
              invoke  CloseHandle,pi.hProcess
              invoke  CloseHandle,pi.hThread                ;第一次关闭程序句炳
;             invoke TerminateProcess,pi.hProcess,-1        ;可采用强行退出的办法
              .break                                        ;结果已经得到了,中断循环      
                    
        3、剩下的事情你已经知道该做什么了吧?正确的注册号已经读到你自己的BUFFER中了
           想做什么就是你自己的事情了:),还是输入吧,你的窗口不是有个输入框吗?           
四、小结

      我写的很多,不知道是不是已经说明白,我已经力求详尽。
      写完程序后我自己也有很多深思的问题:
      1、被加壳的程序中,KEYMAKER是如何来定位断点的?指令在没释放到内存前,是无法插入CC点的。
         是利用单步跟踪指令,判断EIP为所需要的数值,然后断下来的吗?如果那样,Crackme运行起
         来岂不是特慢?
      2、如果Crackme中刚好有SEH断点来处理INT_3指令呢?如何才能让断点准确的返回到我们的控制端?
         Keymaker中是利用的哪种类型的断点?如果壳中有INT3和INT1的断点,就更无法顺利完成我们的
         程序了。可惜找不到树袋熊了,要不可以问个究竟。

五、后记   
       写到这里就要结束了,把我的思路就当是抛砖引玉吧,就当是对《How to code a loader》的一点
补充吧。献给象我一样摸索中前进的人,写给所有的Cracker。
       如果你有更好的想法,我们可以交流,我的EMAIL:miaomiao226@hotmail.com,转载请保持信息的
完整性,谢谢!

                                             oep
                                             2005.5.25晚

[公告]安全服务和外包项目请将项目需求发到看雪企服平台:https://qifu.kanxue.com

最新回复 (19)
shuair 2 2005-5-26 09:02
2
0
支持以下..
cd37ycs 2005-5-26 09:09
3
0
强烈支持一下.
nbw 24 2005-5-26 09:26
4
0
顶。学习一下!
liyangsj 25 2005-5-26 09:39
5
0
好文章学习了
baby2008 28 2005-5-26 10:05
6
0
那位能人做个工具放上来啊
ForEver 20 2005-5-26 11:26
7
0
收藏一份
prince 16 2005-5-26 12:35
8
0
学习学习~
winndy 17 2005-5-26 15:17
9
0
学习,学习

支持
lnn1123 13 2005-5-26 16:48
10
0
学习,要学的东西真是多啊
netu0 2005-5-27 08:53
11
0
实现了我前一段时间在论坛里提到的一个想法。谢谢。
无奈无赖 2005-5-27 09:35
12
0
怎么办啊?我在这里学习了这么久,还是水平不见提升。看你们大虾的文章,我越来越感觉到自己的无能了。。

我要怎么学习,学习到什么时候才能达到这种水平啊?
wyuu 2005-5-27 12:15
13
0
学习,学习

支持
北极星2003 25 2005-5-27 12:16
14
0
看完文章才觉得这个思路很不错,但怎么就想不到呢?
hangj 3 2005-5-27 13:24
15
0
太深!看不懂
minbin 2005-5-28 14:28
16
0
记得几年前TMG有个KEYGEN工具,原理差不多。可以去搜一下。
hyzhang 2005-5-31 21:29
17
0
嗯!如果这样的话,程序就成了你的注册机了。
呵呵,如果程序中不出现明码又如何。
oep1 4 2005-6-1 21:17
18
0
最初由 hyzhang 发布
嗯!如果这样的话,程序就成了你的注册机了。
呵呵,如果程序中不出现明码又如何。


不管是明码或者是暗码,已经读到你的自己程序的内存中,所以可以通过简单的逆运算,得到所需要的内容(用户名或注册号)

另:我这里只是介绍一种方法,可以突破CrackMe中寄存器的存取限制。依次类推,方法可以用在其他用途上,呵呵,开动你们自己的灵感吧
ikki 9 2005-6-1 21:38
19
0
我觉得,简单点的操作,例如在某处中断,读取某处内存地址等可以写个通用的程序,但涉及到操作复杂的程序,还是要自己来写代码
wenglingok 26 2005-6-1 22:37
20
0
好办法,偶想就是内存补丁和内存注册机合而为一,用内存补丁修改内存中机器码,用内存注册机读取内存中的注册码
游客
登录 | 注册 方可回帖
返回