Bookmark and Share

Lee's 程序人生

HTML CSS Javascript XML AJAX ATLAS C# C++ 数据结构 软件工程 设计模式 asp.net Java 数字图象处理 Sql 数据库
  博客园  :: 首页  :: 新随笔  :: 联系 :: 管理

【转载】笑解 API 函数 -- API 绝密档案系列之一

Posted on 2009-02-23 09:04  analyzer  阅读(910)  评论(0编辑  收藏  举报

转自:http://bbs.pediy.com/showthread.php?s=&threadid=21959

 标 题: 【原创】笑解 API 函数 -- API 绝密档案系列之一
作 者: gzgzlxg
时 间: 2006-03-01,07:14
链 接: http://bbs.pediy.com/showthread.php?t=21959

看到 qduwg 老师凭"【翻译】3个脱壳相关的重要函数介绍" 也"骗"到一个精,不觉心痒,也弄几个函数来介绍一下,不过我不是翻译,而是著作。我 们就从 qduwg 老师介绍的几个函数入手。希望上面的话没有伤到 qduwg 老师的自尊心。我看到你对别人评价的反应,知道你很在意这些。

1. HMODULE GetModuleHandle(
  LPCTSTR lpModuleName   // address of module name to return handle 
                         // for
);

  GetModuleHandle 实际上分为两个函数
  
  GetModuleHandleA--处理 Ansi 字符串
  GetModuleHandleW--处理 Unicode 字符串
  
其实真正干活的函数是,GetModuleHandleW (指NT以后的系统),GetModuleHandleA 只不过将用户输入的 Ansi 字 符串转成 Unicode 字符串然后就直接调用 GetModuleHandleW,所以在 OD 中如果下断拦截这个函数,直接拦 截 GetModuleHandleW 就可以了,保险一个都跑不掉。我看一些新手写 Hook 程序时往往不厌其烦的将每个函数都“沟”一下,其实大可 不必,随着我的文章深入,你会发现一个基本原则--千条江河归大海,大部分的调用其实最后都是进入最底层的 ntoskrnl.exe、Hal.dll、 和其他几个内核程序。而我们平常打交道最多的 Kernel32.dll 和 ntdll.dll 其实只是对底层函数进行了包装(当然对于某些 ring3层的特定功能还是自己完成的),相当于一个接口,起一个中转和隔离的作用,将用户的函数调用进行预处理,比如检测用户参数的合法性,对用户参数 进行重新排序(底层函数的参数排列顺序有时和用户层不同,这样可以防止你轻易的弄明白底层在干什么),将用户的调用分离拆解为若干个更为细小的调用,因为 底层函数分类往往更细,这样比较灵活。
使用 GetModuleHandle 函数,在高级语言中可以直接写成:
hMod = GetModuleHandle(lpModuleName); 
编译器会根据参数的性质自行决定调用那一个函数,但在汇编中却不能这样使用,你必须明确的指明你要使用那一个函数。另外在OD中下断点,你也必须明确的指 明对那一个函数下断,例如在OD中,进入CPU窗口,按Ctrl-G,然后输入GetModuleHandle,OD会告诉你 “未识的标识符”,你必须 清楚的告诉 OD 你到底要在那一个函数下断,是 A 还是 W。按前面的说法其实你只要下 GetModuleHandleW 就可以了(当然有时候在 A下断,比较容易知道程序是从那里调用的,如果程序使用的是A调用)。

俗话说,口说无凭。你怎么知道那两个还是最后变成了一个?
看雪老大也在qduwg的文章后说:

引用:
不过这三个函数的定义只需要Google就能找到相关文档,因为太常用,不光是脱壳,编程是经常用的。
现在的强壳都不直接调用这些函数了,都自己实现。
其后跟贴更有"牛B王"--nbw 惊人的评论:
引用:
可以参考以下MSDN。写壳都是自己实现。

以前自己能写个GetProcAddress就觉得很不错了,后来看高手写虚拟机,半小时模拟出来LoadLibrary,那个让偶惊啊。。。。
我没有他们那样的水平,自己不会写,人都说半瓶醋晃荡,醋瓶满了就不响了,我这个瓶子还没有满,所以你们有幸能看到这些垃圾文章(当然也是为了骗一点"加精",风光风光),等有一天我的瓶子也满了(最好能够做到),也就只会“啊...”了。

既然如此,那就只好看看老盖是怎么做的,这个函数在 Kernel32.dll 中,你可能会问,你怎么知道在Kernel32.dll中的?qduwg 老师在翻译文中可惜漏掉了可能他认为不重要的信息:

QuickInfo
  Windows NT: Requires version 3.1 or later.
  Windows: Requires Windows 95 or later.
  Windows CE: Unsupported.
  Header: Declared in winbase.h.
  Import Library: Use kernel32.lib.    //这里指明了函数的归宿
  Unicode: Implemented as Unicode and ANSI versions on Windows NT.

See Also
Dynamic-Link Libraries Overview, Dynamic-Link Library Functions, FreeLibrary, GetModuleFileName, GetProcAddress, LoadLibrary,LoadResource 

现在我们来看看 GetModuleHandleA 的具体操作:
代码:
77E80B1A ; HMODULE __stdcall GetModuleHandleA(LPCSTR lpModuleName)
77E80B1A           public GetModuleHandleA
77E80B1A GetModuleHandleA proc near              ; CODE XREF: sub_77E684C2+2p
77E80B1A                                         ; CreateRemoteThread+FBp ...
77E80B1A
77E80B1A lpModuleName= dword ptr  8
77E80B1A
77E80B1A           push  ebp
77E80B1B           mov   ebp, esp
77E80B1D           cmp   [ebp+lpModuleName], 0
77E80B21           jnz   short loc_77E80B31      ; lpModuleName 是有效的则转移
77E80B23           mov   eax, large fs:18h       ; TEB.NT_TIB.Self
77E80B29           mov   eax, [eax+30h]          ; TEB.Peb
77E80B2C           mov   eax, [eax+8]            ; PEB.ImageBaseAddress
77E80B2F           jmp   short loc_77E80B45
77E80B31
77E80B31 loc_77E80B31:                           ; CODE XREF: GetModuleHandleA+7j
77E80B31           push  [ebp+lpModuleName]
77E80B34           call  @AnsiStrToUnicodeStr    ; 将Ansi字符串转成Unicode字符串
77E80B39           test  eax, eax
77E80B3B           jz    short loc_77E80B45
77E80B3D           push  dword ptr [eax+4]       ; lpModuleName
77E80B40           call  GetModuleHandleW        ; 这里开始同流合污了。
77E80B45
77E80B45 loc_77E80B45:                           ; CODE XREF: GetModuleHandleA+15j
77E80B45                                         ; GetModuleHandleA+21j
77E80B45           pop   ebp
77E80B46           retn  4
77E80B46 GetModuleHandleA endp
这些结果是在 IDA 中分析的,在使用IDA分析这些内核模块时请注意,一定要将那个符号文件.PDB和调试符号文件.DBG从老盖的网站 上下载回来(注意要下载最新的版本),和你要分析的文件放在一个目录下,这样,IDA就会自动加载这些符号文件,分析出的结果可读性大为提高。另外值得注 意的是 windows 的这些系统模块和符号文件的版本一定要相同,大多数的情况下,符号文件的版本会相对低一点,这样你可以到系统目录下去寻找过去版 本的系统模块,windows在update时会将这些旧的系统模块保存在其他目录,具体的版本对于可执行模块来讲,比较简单,就是PE文件头中的 TimeDateStamp,使用任何工具查看都行。
IMAGE_FILE_HEADER STRUCT
  Machine               WORD    ?
  NumberOfSections      WORD    ?
  TimeDateStamp         DWORD   ?  //就是这里,不明白的话请看 qduwg 的翻译文章
  PointerToSymbolTable  DWORD   ?
  NumberOfSymbols       DWORD   ?
  SizeOfOptionalHeader  WORD    ?
  Characteristics       WORD    ?
IMAGE_FILE_HEADER ENDS

对于PDB文件比较麻烦,我没有现成的工具来查看这个 TimeDateStamp ,用任何十六进制编辑器打开这个.PDB文件在偏 移 0x4404 的地方就是这个 TimeDateStamp 了,对于.DBG 文件,在偏移 0x8 的地方,这两个TimeDateStamp一 定要一样,否则宁愿不要使用。

下面对fs段加一些详细的说明,很多人可能不太熟悉这个段。

fs段在用户模式(Ring3)和系统模式(Ring0)分别指向两个最重要的系统结构:
Ring3:
fs --> TEB (Thread Environment Block)结构表 --> 7FFDE000
即“线程环境块”。

Ring0:
fs --> KPCR (Kernel Processor Control Region)结构表 --> FFFDF000
即“内核处理器控制域”。

在NT和2K和XP和2k+3中系统这个地址是固定的,随着版本的增大或Update,TEB会有新的内容增加,在将来的windows系统中,老盖为了 防止那些坏人(我可不是坏人,否则说不定也有能力做点坏事)太容易利用这个特性搞破坏,有可能将这个地址变成随机的,也就是每次启动后都会在不同的位置。

这些结构都是老盖没有公布的,所以如果你想使用这些结构,要特别小心,老盖为了防止小人搞破坏,每次Update都有可能修改这些结构而使的你程序崩溃。让你吃免费蓝玻璃。

因为是所谓 UnDocument 的东西,所以最好的办法就是通过老盖的 WinDBG 或他的兄弟 KD 来获得这些结构,由于上面说的原因,这个结 构以你自己看到的为准,至于如何使用 WinDBG 和 KD ,我想最好请那个叫 Themida flea老兄来写(这可不是我创出的名字,是这位 Themida老兄自己的图标,Themida可是我非常欣赏的强壳,虽然不太稳定) 在本论坛的精品 “【原创】用WinDbg动态脱 Reflector” 中,我说我学不会 WinDBG ,这位 Themida 老兄答到:
引用:
一群笨蛋,笑翻。。。

windbg的GUI只是个DEMO,各位可以编写自己的windbgGUI
由此可知,flea 老兄,高人也。flea 老兄不但可以教你们如何使用 WinDBG 和 KD,甚至可以教你们写一个带有自己特色 的 debug,例如输入你自己开发的命令 allstruct,哇! 所有undocument 的结构都展现在你眼前,想想,那是什么境界。

下面这些是在我的机器上显示的结构,遗憾的是看不到TEB的完整结构,可以到4F提供的w2kundoc.inc中看到这个TEB的完整结构,不过我可不 能保证是正确的。但对于我们所要讨论的问题,使用WinDBG提供的信息就足够了。使用WinDBG的内核模式或直接使用KD:(双机模式或虚拟机)输入 下面的命令:
代码:
kd> !teb                 //老盖的Debug命令一般都需要这个"哇"符号做前缀,读作"哇!!添一杯"
TEB at 7FFDE000
    ExceptionList:    12f830                "   0x0
    Stack Base:       130000                |   0x4
    Stack Limit:      126000                |   0x8
    SubSystemTib:     0                     |   0xC     这里其实是 NT_TIB 的一个结构
    FiberData:        1e00                  |   0x10
    ArbitraryUser:    0                     |   0x14
    Self:             7ffde000              /   0x18
    EnvironmentPtr:   0                         0x1C
    ClientId:         4cc.4c8                   0x20
    Real ClientId:    4cc.4c8                   0x24
    RpcHandle:        0                         0x28
    Tls Storage:      0                         0x2C
    PEB Address:      7ffdf000                  0x30  这里指向 PEB 表,即进程环境块
    LastErrorValue:   0
    LastStatusValue:  8000001a
    Count Owned Locks:0
    HardErrorsMode:   0
这里只是TEB表的部分,但已经够使用的了,如果想在开发程序中使用这些结构,如前所说,可以到4F的inc中去寻找,也可以自己到网上去google一把,但不一定有美满的结果。
代码:
kd> !kdex2x86.strct peb  //如果 kdex2x86 已经加载,可以直接输入 !strct peb
struct   _PEB (sizeof=488)
+000 byte     InheritedAddressSpace
+001 byte     ReadImageFileExecOptions
+002 byte     BeingDebugged                   //Debug运行标志
+003 byte     SpareBool
+004 void     *Mutant
+008 void     *ImageBaseAddress               //这里就是程序加载的基地址了
+00c struct   _PEB_LDR_DATA *Ldr
+010 struct   _RTL_USER_PROCESS_PARAMETERS *ProcessParameters
+014 void     *SubSystemData
+018 void     *ProcessHeap
+01c void     *FastPebLock
+020 void     *FastPebLockRoutine
+024 void     *FastPebUnlockRoutine
+028 uint32   EnvironmentUpdateCount
+02c void     *KernelCallbackTable
+030 uint32   SystemReserved[2]
+038 struct   _PEB_FREE_BLOCK *FreeList
+03c uint32   TlsExpansionCounter
+040 void     *TlsBitmap
+044 uint32   TlsBitmapBits[2]
+04c void     *ReadOnlySharedMemoryBase
+050 void     *ReadOnlySharedMemoryHeap
+054 void     **ReadOnlyStaticServerData
+058 void     *AnsiCodePageData
+05c void     *OemCodePageData
+060 void     *UnicodeCaseTableData
+064 uint32   NumberOfProcessors
+068 uint32   NtGlobalFlag
+070 union    _LARGE_INTEGER CriticalSectionTimeout
+070    uint32   LowPart
+074    int32    HighPart
+070    struct   __unnamed3 u
+070       uint32   LowPart
+074       int32    HighPart
+070    int64    QuadPart
+078 uint32   HeapSegmentReserve
+07c uint32   HeapSegmentCommit
+080 uint32   HeapDeCommitTotalFreeThreshold
+084 uint32   HeapDeCommitFreeBlockThreshold
+088 uint32   NumberOfHeaps
+08c uint32   MaximumNumberOfHeaps
+090 void     **ProcessHeaps
+094 void     *GdiSharedHandleTable
+098 void     *ProcessStarterHelper
+09c uint32   GdiDCAttributeList
+0a0 void     *LoaderLock
+0a4 uint32   OSMajorVersion
+0a8 uint32   OSMinorVersion
+0ac uint16   OSBuildNumber
+0ae uint16   OSCSDVersion
+0b0 uint32   OSPlatformId
+0b4 uint32   ImageSubsystem
+0b8 uint32   ImageSubsystemMajorVersion
+0bc uint32   ImageSubsystemMinorVersion
+0c0 uint32   ImageProcessAffinityMask
+0c4 uint32   GdiHandleBuffer[34]
+14c function *PostProcessInitRoutine
+150 void     *TlsExpansionBitmap
+154 uint32   TlsExpansionBitmapBits[32]
+1d4 uint32   SessionId
+1d8 void     *AppCompatInfo
+1dc struct   _UNICODE_STRING CSDVersion
+1dc    uint16   Length
+1de    uint16   MaximumLength
+1e0    uint16   *Buffer
注意这些是WinDBG的扩展命令,由外部模块 kdex2x86.dll 提供(可以到老盖的网站去下载),先觉条件是这个模块必须被装载。
至于那个Ring0的 KPCR 结构如果我的兴趣还在,而且那个瓶子还没有满,再详细讨论,KPCR 的结构巨大无比也复杂无比。前些日 子 qduwg 老师发了题为 “For菜鸟文章:PE文件格式, qduwg翻译”,我没有留心说漏嘴,大概意思是 PE 结构拿过来使用不就完了,用 得着去学吗。其实此话来源就是因为底层的这些结构,和底层的这些结构相比, PE结构那真是小巫见大巫了(PE 文件头其实就是一个非常简单的结构),如 果你一个 PE 文件头都要花那么大的力气去学,而且到处都可以找到详细的资料,那么底层的这些结构怎么办??? 其一是这些结构远比 PE 结构更加庞 大,更加复杂;其二是根本就没有任何资料和解释,全部要靠你自己去努力去理解,去破解。而且这些巨大的结构有无数多,怎么办,以我看,如果真是那样,不如 回家种红薯。没有想到这段无心的话引起了两国战争,伊万大叔立刻就对我发动了战争,伊万大叔在俄国,我在加拿大,你想,加拿大全国警察加军队也不过才几万 人,怎么能打的过伊万大叔,于是我只好狼狈逃跑。哈哈,由于我的当机立断,结果世界大战没有爆发,为了消除坏影响,伊万大叔买通看雪老大,将这场争论给完 全删了,毁灭罪证,实在可惜,哈哈。伊万大叔在随后的跟贴中可不要对我口诛笔伐。
这些是玩笑话,当不得真,如果不写一些这样的垃圾,我的创作热情恐怕立刻就消失了,所以请看官们忍忍。

这里插一句题外话,大家在使用OD脱壳时,往往为了隐藏OD,会加载IsDebug V1.4.dll来隐藏OD,其实完全不需要加载这个东西,在OD中 的数据窗口, 按 Ctrl-G ,在打开的小窗中输入 7FFDF000(见上面的PEB表)然后在偏移 量 02(PEB.BeingDebugged 标志)的地方或者干脆输入 7FFDF002, 将鼠标放在这个地址上,Ctrl-E,将 1 改为零 就 IsDebug 了,如果已经是 0 就是你已经使用什么 dll 将 Debug 隐藏了。 

对这两张表有了一定的认识后,我们再来看上面的那段代码的头部:
代码:
77E80B1A           push  ebp
77E80B1B           mov   ebp, esp
77E80B1D           cmp   [ebp+lpModuleName], 0
77E80B21           jnz   short loc_77E80B31      ; lpModuleName 是有效的则转移
77E80B23           mov   eax, large fs:18h       ; TEB.NT_TIB.Self
77E80B29           mov   eax, [eax+30h]          ; TEB.Peb
77E80B2C           mov   eax, [eax+8]            ; PEB.ImageBaseAddress
77E80B2F           jmp   short loc_77E80B45
其中:
代码:
77E80B23           mov   eax, large fs:18h       ; TEB.NT_TIB.Self
就是将那个 7FFDE000 的地址送到 eax 中去,现在我们知道那是 TEB 表。
代码:
77E80B29           mov   eax, [eax+30h]          ; TEB.Peb
根据上面的 TEB 表,我们知道 7FFDE000 偏移 30h 的地方是一个指针,指向 PEB 表。
代码:
77E80B2C           mov   eax, [eax+8]            ; PEB.ImageBaseAddress
根据上面的 PEB 表,我们得到最后结果,原来这几句代码的目的是获得程序启动后被装载的基地址。就这么简单。
代码:
77E80B1D           cmp   [ebp+lpModuleName], 0
这句是检测用户给出的 lpModuleName 是否为零,如果为零,用户发出的请求是获取自己当前进程的 hModule,这就是为什么 要获取程序启动后被装载的基地址的原因了。再往下面的东西不用我解释了把,你可以亲眼看到他将那个Ansi字符串转换成Unicode后就调 用 GetModuleHandleW 最后跑到一起去了。
至于 GetModuleHandleW 干了一些什么事,且听下回分解,哈哈这样我可以多骗几个“加精”,qduwg 老师用三个 API 换一个“加精”,我准备用一个 API 调用换 3 到 5 个“加精”,哈哈,那当然还要看雪老大肯给我才行。

后话:过些日子我就要回中国了,回到我可爱的祖国,如果很忙,可能就不能连续的写了,这点请大家谅解,如果成了海带,那就继续制造垃圾。多给我一点鼓励,使我能多写几篇,否则热情一旦消失或瓶子不幸装满了,就没有这些垃圾文章了,哈哈。

另外伊万大叔花重金从专门出产那种体型苗条、曲线流畅的、并会飞的蟑螂的国家--德国(现在中国许多城市的蟑螂多为这个德国种,本土的蟑螂太笨,不会飞, 所以蔓延比较慢,危害也比较小,有灭亡的危险)买了一条游艇,还说会开到珠江口,在船上请我喝那个“可爱的水”--vodka,根据伊万大叔提供的资 料,vodka 没有任何的味道,最大的好处就是你喝过以后,不影响你和你那一半做某些亲热的动作(不过可没有规定必须是那一半,也许是小密),因为嘴中 没有异味。不过我还是认为我们中国的酒好,你看那茅台、五粮液,酒瓶一开满屋香,多来劲,另外中国酒还喝的放心,谁放心?当然是老婆啦,你想,满嘴的酒 气,那种可能对小密产生的专用动作不就失效了,想不到中国酒还有这种妙用吧!!不过到时候说不定又要爆发新的战争,为酒而战,不过这次是中俄之战了,哈 哈,请看官们不用担心,我依然会毫不犹豫的跳海逃走,幸亏我的游泳技术不错,尤其擅长长距离游泳。

注:修改了一些错笔字,另外为了赢得更高的点击率,将标题也换了,最好那位仁兄大才,给弄一个不吓死人不罢休的标题,那我就感激不尽了。伊万大叔就给了一个主意,说标题上一定要有个“密”字,我觉得有道理,这不,马上就成了绝密了。

 

我要啦免费统计