第三章.Anti-dump的原理
"我们踏进又踏不进同一条河,我们存在又不存在。"
                                                     ----赫拉克利特
    事物往往就是这样,它在发展中需要正面的也需要反面的,他们是对立统一在一起的。在前面我们的dump已经基本和一个LordPE的功能差不多了,现在我们要分析一下,一个程序是如何anti-dump的,同时我们也会给出一些简单的解决办法。

注意:下面所说的方法都是在NT内核下的执行的,并不涉及9x下的。

一.纠正imagesize
这里我们首先讨论一下关于LordPE的其中一个correct ImageSize的功能。
 

首先我们要知道,这个anti是如何产生的。根据上面的介绍我们知道,我们获得的image size是通过MODULEENTRY32的结构的快照,但是这个结构是从那里获得这些数据的呢?
答案是通过PEB(Process Environment Block)进程环境模块获得的。下面是PEB的结构。
struct   _PEB (sizeof=488) 
+000 byte     InheritedAddressSpace 
+001 byte     ReadImageFileExecOptions 
+002 byte     BeingDebugged 
+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

我们从FS:[30]就可以获得这个PEB的首地址。然后在0C处的_PEB_LDR_DATA *Ldr是一个关键通过它,我们能访问到

typedef struct _PEB_LDR_DATA {
ULONG Length;
BOOLEAN Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;

该结构的后三个成员是指向LDR_MODULE链表结构中相应三条双向链表头的指针,分别是按照加载顺序、在内存中的地址顺序和初始化顺序排列的模块信息结构的指针。于是通过它,我们能访问到_LDR_MODULE结构,而这里面包括了本进程的SizeOfImage。
_LDR_MODULE结构如下:
typedef struct _LDR_MODULE {
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID BaseAddress;
PVOID EntryPoint;
ULONG SizeOfImage;                   //进程的image size
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
LIST_ENTRY HashTableEntry;
ULONG TimeDateStamp;
} LDR_MODULE, *PLDR_MODULE;

所以,我们得到关键的代码就是:

//这里的几个代码是修改PEB的关键
  __asm
  {
    mov eax,fs:[30h]                //获得PEB地址
    mov eax,[eax+0ch]               // +00c struct   _PEB_LDR_DATA *Ldr 
    mov eax,[eax+0ch]               // _LDR_MODULE的首地址
    mov dword ptr [eax+20h],1000h   //eax+20是保存image size的地方

}

上面的代码的作用就是把image size的大小改为了1000h,这样我们用MODULEENTRY32得到的大小是不准确的。所以大家在跟踪壳的时候特别要小心上面的几行代码。如果看见它就直接跳过吧。

好了,知道他是怎么anti的以后看看LordPE是怎么使用correct ImageSize的,我稍微跟踪了一下它的的代码。发现了下面的API函数:
1.  GetProcessBaseSize
2.  GetProcessPath
3.  CreateFileA
4.  ReadFile
看到这里想必大家也知道,怎么回事了吧。他在获得了绝对路径以后打开了磁盘上的PE文件,读取里面PE头的ImageSize,来纠正这个错误了的image size。恩,或许你会说,那么我写一个anti来修改PE文件头的ImageSize,让它获得的还是错误的。呵呵,我不得不提醒你的是,任何对文件的操作都是危险的。我们来设想一下这种情形:当你把原来的PE文件打开了以后,把一个错误的ImageSize写入了这个PE文件里面,使LordPE的correct ImageSize仍然无功而反,而打算在某一个地方,再次打开文件把正确的信息写回PE文件。(否则你下次就别想打开它了)且不说这样的办法很烦琐,我们设想一下这个时候,你的机器死机了!你不得不重起机器。而你的收尾工作还没结束。于是你的PE文件除了anti的作者,别人在不知道为什么的情况下,再也别想打开它了。

所以一个聪明的程序员,都不会做这种危险的事情。好了,来看看我是如何实现这个功能的吧。我在原来的基础上填加了一个按钮“纠正大小”的按钮。那再重新定义了一个函数来实现这个功能:

BOOL CorrectSizeFunc(HWND hDlg,HWND hWindList)
{
  //函数能获取文件的PE头部的SizeOfImage,作为正确的SizeOfImage
    LPCTSTR File_Name=NULL;                   
    WPARAM tmp=(WPARAM)SendMessage(hWindList,LB_GETCURSEL,0,0);
  if (tmp==LB_ERR)
  {
    MessageBox(hDlg,TEXT("Please choose a process..."),TEXT("oh...no,no,no..."),MB_OK);
    return FALSE;
  }
  DWORD IDProcess=SendMessage(hWindList,LB_GETITEMDATA,tmp,0); //获得此列单里面的进程ID
  ID=IDProcess;//全局变量ID的作用是控制在不同的进程的切换
  File_Name=GetFilePath(hDlg,IDProcess);
  if(!File_Name)
    return FALSE;
    //打开文件
  HANDLE  hFile;
  hFile=CreateFile(File_Name,GENERIC_READ,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

if (hFile == INVALID_HANDLE_VALUE )
  {
    return FALSE ;
  }

//创建文件映射内核对象
  HANDLE hMapping;
  hMapping =  CreateFileMapping (hFile, NULL, PAGE_READONLY,0,0,NULL);
  if (hMapping == NULL )
  {
    CloseHandle (hFile ) ;
    return FALSE;
  }
  //创建文件视图
  LPVOID ImageBase ;
  ImageBase =MapViewOfFile(hMapping,FILE_MAP_READ,0,0,0) ;
  if (ImageBase == NULL)
  {
    CloseHandle (hMapping) ;
    return FALSE;
  }
    //下面的代码就是从文件的PE头找到SizeOfImage的
  PIMAGE_DOS_HEADER DosHead = NULL ;
  PIMAGE_NT_HEADERS32 pNtHeader = NULL ;
    PIMAGE_FILE_HEADER pFileHeader = NULL ;
    PIMAGE_OPTIONAL_HEADER pOptionalHeader = NULL ;
    PIMAGE_SECTION_HEADER pSectionHeader = NULL ;
  DosHead=(PIMAGE_DOS_HEADER)ImageBase;
  pNtHeader = ( PIMAGE_NT_HEADERS32 ) ((DWORD)ImageBase + DosHead->e_lfanew ) ;
  pOptionalHeader = &pNtHeader->OptionalHeader;  
    sizeofimage=(int)pOptionalHeader->SizeOfImage;
    //找到了以后,输出结果
  char szBuffer[100];
  char szMsg[]="原来的image size是:%08X\n修整的image size是:%08X";
  wsprintf(szBuffer,szMsg,GetSizeOfImage(hDlg,IDProcess),sizeofimage);
  MessageBox(hDlg,szBuffer,TEXT("纠正结果"),MB_OK );
    CloseHandle (hMapping);
    CloseHandle (hFile) ;
  Sleep(200);
  return TRUE; 
}

这个功能实现到是很简单,但是同时要强调的一点是,在我在协调纠正的进程和dump进程是不一样的进程的时候,出了一些小问题。我采用了一个很愚蠢的办法,定义了一个全局变量ID来控制这种事情的发生。

二.实战

这个modify_module_imagesize程序包括了上面的修改PEB里面image size的代码。运行它:

可以看到原来的image size已经被修改了。

好了,现在用我们的dump来试试吧。
 

这样我们再dump它,就能得到原来C000大小的进程了。

二.其他anti方式
  根据上面的MODULEENTRY32结构,我们能得到很多启发。毕竟懂得了原理,那么寻找anti的办法是不难的。首先是基地址,我们能不能修改PEB中的基地址呢?
   事实上,这种方法会产生不必要的麻烦。也就是说不可行。
   另一种办法是修改文件的路径,这种办法很好。但是只是能在9x中使用,因为目前为止,不知道在NT内核下的这种办法。所以在这里我也不介绍了。有兴趣的读者可以去看看《软件加密技术内幕》P204-P207,在那里hying有完整的介绍。
  下面要介绍的是一种非常普遍,也很常用的办法。就是利用内存属性进行anti。
  我们知道,如果我们打开了一个进程,那么要读写进程里面的内容,必须要获得应有的权限读权利。实际上,当一个exe文件被load到内存里面的时候,他的所有取段都具有读的权利,而正是这样我们才能把他从内存中读出来,写到磁盘文件上面。所以,我们的anti来了,如果我们将部分不用区段的权限修改一下,把他的读权限取消。会发生什么结果呢?
  让我们来做一个简单的实验吧!
/*------------------------------------------------------------------------
  Save as Modify_the_read_right.cpp
                         (c) Lenus Margin 2005 10.15
-------------------------------------------------------------------------*/

#include <windows.h>
#include <iostream.h>
#include <stdio.h>

void main()
{
  DWORD dwOldProtect;  
  HMODULE ImageBase=GetModuleHandle(NULL);
  VirtualProtect(ImageBase,1000,PAGE_NOACCESS,&dwOldProtect);//将PE头改为不可访问的的属性    
  cout<<"The PE HEAD read right change to no_access!!"<<endl;
  cout<<"U can try to dump at here!"<<endl;
  getchar();
}

等运行了以后,我们用LordPE来dump它吧,发现了什么没有?一个经典的对话框!
 

这个东西,相信大家一定见过。让我们看看,在这个进程里面的400000处吧!
 

选中进程---右键---dump region就可以看到上面的窗口了。果然已经变成了NOACCESS的属性了。
那么应该怎么解决呢?这个我们就要请出更强的工具来了。现在我们就用OD的ollydump来dump它!
用OD载入以后,F9运行!好了,这时我们可以用LordPE还是看到上面的NOACCESS的属性,但是我们到OD的内存镜象里面看看是什么结果呢?
 

我们可以看到,他仍然是可读,可写,可执行的权限。
为什么会这样呢?
因为OD这个调试器需要编辑这个进程的空间中的数据,而如果一个区段因为设置了NOACCESS而得不到访问,不能编辑实在是不可想象的。所以他在我们需要使用这段空间的时候就将它的属性临时的修改到了完整的权限,而当我们使用完毕以后呢?再修改回来,可以说,ring3的调试器老是在干这种事情,比如说int3的放置也是这样的。从这些侧面我们也能体会到OD的强大。

好了,现在用ollydump就可以dump下来了。

四.小结
小结一下。关于anti这一章,以上的这些只是一些常见的手段。事实上,矛盾的较量永远不会停止,正如开篇的赫拉克利特所说"我们踏进又踏不进同一条河,我们存在又不存在。"在哲学上事物是矛盾的统一,没有了此消彼长的竞争,也没有了任何存在的可能。而在加密解密的世界anti-dump也是一个大学问,从而我们在避开anti的同时也会发现很多很巧妙的方法。

Anti-dump的原理相关推荐

  1. 【ceph】Admin Socket机制|ceph dump 命令原理

    目录 python 前端部分 Admin Socket后端部分 ceph 命令下发流程:命令行-->python 前端部分处理-->Admin Socket后端部分处理 前端部分 pyth ...

  2. dump java 原理_dumpsys实现原理

    概述 dumpsys是一个android手机里面的可执行文件. 从名字就可以看出,主要是用于dump 当前android system的一些信息.比如 activity(当前系统中所有activity ...

  3. Linux下用dump实现备份和还原

    对于系统而言,我们可以有很多种办法去备份(还原)系统或文件,之所以要去做备份,就是为了在系统或文件遭到损害时,能及时恢复,把损失减小到最小.当然,对于企业服务器而言,备份的重要性是举足轻重.咱们今天就 ...

  4. 标 题: 又灌水-反(调试/跟踪/脱壳) 技术集锦

    标 题: 又灌水-反(调试/跟踪/脱壳) 技术集锦 发信人: kongfoo 时 间: 2004-05-27,14:50 详细信息: 反(调试/跟踪/脱壳) 技术集锦 kongfoo/2004.4.1 ...

  5. 完美脱壳组装PE的一般步骤(Obsidium1.3.6.4 DEMO 主程序)

    最近学习了天草视频.记录一下笔记. Obsidium1.3.6.4DEMO版本主程序下载地址:http://download.csdn.net/detail/whatday/5244425 前期准备工 ...

  6. 前端:前端安全编码规范

    前言 随着互联网高速的发展,信息安全已经成为企业重点关注焦点之一,而前端又是引发安全问题的高危据点,所以,作为一个前端开发人员,需要了解前端的安全问题,以及如何去预防.修复安全漏洞. 下面就以前端可能 ...

  7. 微信移动端数据库组件WCDB系列(二) — 数据库修复三板斧

    前言 长久以来SQLite DB都有损坏问题,从Android.iOS等移动系统,到Windows.Linux 等桌面系统都会出现.由于微信所有消息都保存在DB,服务端不保留备份,一旦损坏将导致用户消 ...

  8. PE格式:导入表与IAT内存修正

    本章教程中,使用的工具是上次制作的PE结构解析器,如果还不会使用请先看前一篇文章中对该工具的介绍,本章节内容主要复习导入表结构的基础知识点,并通过前面编写的一些小案例,实现对内存的转储与导入表的脱壳修 ...

  9. C/C++ 导入表与IAT内存修正

    本章教程中,使用的工具是上次制作的PE结构解析器,如果还不会使用请先看前一篇文章中对该工具的介绍,本章节内容主要复习导入表结构的基础知识点,并通过前面编写的一些小案例,实现对内存的转储与导入表的脱壳修 ...

  10. JVM内存Dump原理与在线分析实战 | 得物技术

    1.前言 当前我们微服务容器化部署JVM 实例很多,常常需要进行JVM heap dump analysis,为了提升JVM 问题排查效率,得物技术保障团队研究了JVM内存Dump 原理与设计开发了J ...

最新文章

  1. jieba分词工具的使用方法
  2. python语法错误概述_python语法错误
  3. 英特尔AI芯片首次商用交货!推理性能3.7倍于英伟达T4,年贡献245亿涨250%
  4. linux内核配置与编译
  5. nyoj--79--导弹拦截(动态规划)
  6. 阿里云数据库四位小伙伴聚齐!共同开启生态合作新篇章!
  7. 阿里巴巴高级技术专家至简:聊工程师思维
  8. 乒乓球比赛 两个乒乓球队进行比赛,各出三人。甲队为a,b,c三人,乙队为x,y,z三人。已抽签决定比赛名单。有人向队员打听比赛的名单。a说他不和x比,c说他不和x,z比 请编程序找出三队赛手的名单。
  9. python模拟内置函数all_python内置函数all和any
  10. PHP动态生成select标签
  11. java datasource使用_java Datasource,数据库连接池
  12. java计算机二级内容总结
  13. php采集虎牙,全网首发PHP虎牙直播解析接口源码
  14. java实现冒泡算法
  15. 怎样选择拨号vps?
  16. android 水晶报表,水晶报表分组,统计,求和,sum()函数使用
  17. ValueError: The list of inputs passed to the model is redundant. All inputs should only appear once.
  18. TP6框架全新开发社区系统源码开源
  19. 小白重装系统教程_大神教你小白一键重装系统
  20. Super Ugly Number

热门文章

  1. 阿里云宣布进入 Serverless 容器时代,推出弹性容器实例服务 ECI
  2. Flutter学习指南:文件、存储和网络
  3. 重磅:苹果人工智能最完整解密,iBrain早已无处不在
  4. 常用HiveQL总结
  5. ADO.NET笔记——带参数的查询防止SQL注入攻击
  6. Netty with protobuf(二)
  7. 程序包com.sun.image.codec.jpeg.JPEGCo不存在解决办法
  8. 大数据将改变信息生命周期管理
  9. 光伏产业链遭致命伤:补贴下调或结束暴利
  10. linux 系统监控和进程管理