ExpressCache OEM Patch v2.0

按照zts9989同学的提示再次进行分析,果然发现1.3.110以上版本存在文件校验的暗桩,故更换破解方式。

此次补丁使用了特征码定位,所以理论上可以兼容多个版本,但是能兼容多少我也不知道。个人环境win10x64 118版本、win7x64 86版本测试通过。别的版本也欢迎各位观众帮忙测试,如有问题可在下面讨论。

用法:停止ExpressCache服务后应用此补丁。

下载地址:ECPatch.7z,解压密码仍然为本站域名。

Known issues:
1、win7 x64只能装110版本。这是因为win7及以前的系统只认sha1签名,而之后的系统都是sha256签名。据说打KB3033929补丁就可以支持sha256了。
2、关机掉缓存重启不掉。可能是硬件坑,见楼下z同学的研究历程。

破解Win2003标准版IAS Radius服务器客户端IP数限制

Win2003的Internet Authentication Service(IAS)服务是基于Windows Server平台的自带RADIUS服务端,结合Active Directory用于VPN拨入、无线802.1x接入等非常方便。

IAS要求必须指定来源客户端IP地址,查询帮助知可以填写192.168.0.0/24这样的子网地址以实现IP段,但填写后提示只有企业版才支持此功能。

因为没钱买企业版(好吧,其实是某云提供的企业版2003只有64位版,博主的1G内存小主机没必要因为这一个功能去换64位而损失各种兼容性),考虑进行破解。因为是系统Network Service服务,不便于使用OD调试,故优先用IDA静态分析,不过微软的系统DLL都公开了PDB方便用户调试和研究,分析起来其实并不复杂,IDA中很快找到对GetVersionExW的调用,随后连蒙带猜搞清周围的逻辑。破解思路与方法如下:

1、注册表HKLM\SYSTEM\CurrentControlSet\Services\RemoteAccess\Policy键,LicenseType值改为2是企业版的标志。这样以后IAS控制台就可以输入子网IP而不提示上面的对话框。

2、以上步骤后服务运行时还是会打版本不符合的EventLog,说明运行时还有判断,通过研究发现需要爆破。如图所示,iassvcs.dll中这里对OSVERSIONINFOEX.wSuiteMask和0x2做位测试,表示是否是企业版(参阅https://msdn.microsoft.com/en-us/library/windows/desktop/ms724833(v=vs.85).aspx)。用winhex将这里的jz改为nop即可。

3、system32目录下的文件受Windows File Protection(WFP)保护,直接替换会被秒还原。需要先将dllcache以及windows安装目录下的相应文件都删除或改名。另外替换正在使用中的文件也是将原文件改名,粘贴新文件,重启进程。

以上破解完成后测试服务启动正常,在我的环境下WFP好像也没有网上说的那样弹窗提示系统文件被fxxk了。实测可以添加0.0.0.0/0这样的ip地址匹配任意ip。

ELAN触控板改手写板实验

原理:逆向了ETDDeviceInformation.exe中取内核原始PS2数据并计算出多点触摸坐标的部分,然后使用Microsoft.Ink墨迹库来做文字识别。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;

namespace InkTest
{
    class ETD
    {
        [DllImport("kernel32")]
        static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode, byte[] lpInBuffer, uint nInBufferSize, byte[] lpOutBuffer, uint nOutBufferSize, out uint lpBytesReturned, IntPtr lpOverlapped);
        [DllImport("kernel32")]
        static extern IntPtr CreateFileA(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);
        [DllImport("kernel32")]
        static extern IntPtr OpenEventA(uint dwDesiredAccess, bool bInheritHandle, string lpName);
        [DllImport("kernel32")]
        static extern bool ResetEvent(IntPtr hEvent);
        [DllImport("kernel32")]
        static extern uint WaitForSingleObject(IntPtr hHandle, int dwMilliseconds);

        static public void Init()
        {
            var t = new Thread(ETDThread);
            t.Start();
        }
        static public int X;
        static public int Y;

        static void ETDThread()
        {
            IntPtr hDevice = CreateFileA(@"\\.\ETD", 0xC0000000, 0, IntPtr.Zero, 3, 0, IntPtr.Zero);
            IntPtr hEvent = OpenEventA(0x1f0003, false, @"Global\ETDOther_GetKernelData");            
            byte[] ioctlBuf = new byte[124814];
            uint nRet = (uint)ioctlBuf.Length;
            Array.Copy(BitConverter.GetBytes((short)0x608), ioctlBuf, 2);
            DeviceIoControl(hDevice, 0x9c412000, ioctlBuf, nRet, ioctlBuf, nRet, out nRet, IntPtr.Zero);
            int bufIndex = BitConverter.ToInt32(ioctlBuf, 0x406);
            byte[] data = new byte[6];
            while (true)
            {
                WaitForSingleObject(hEvent, -1);
                ResetEvent(hEvent);
                Array.Clear(ioctlBuf, 0, ioctlBuf.Length);
                nRet = (uint)ioctlBuf.Length;
                Array.Copy(BitConverter.GetBytes((short)0x708), ioctlBuf, 2);
                Array.Copy(BitConverter.GetBytes(bufIndex), 0, ioctlBuf, 2, 4);
                DeviceIoControl(hDevice, 0x9c412000, ioctlBuf, nRet, ioctlBuf, nRet, out nRet, IntPtr.Zero);
                int count = BitConverter.ToInt32(ioctlBuf, 10);
                bufIndex = BitConverter.ToInt32(ioctlBuf, 0x480e);

                for (int i = 0; i < count; ++i)
                {
                    byte d1 = ioctlBuf[14 + i * 18 + 4];
                    int d4 = BitConverter.ToInt32(ioctlBuf, 14 + i * 18 + 13);
                    byte d5 = ioctlBuf[14 + i * 18 + 17];
                    data[d4] = d5;
                    if (d4 == 5)
                    {
                        if (d1 == 16 && data[1] == 0) //release
                        {
                            X = Y = 0;
                        }
                        else if (d1 == 17)
                        {
                            X = (data[1] & 0xf) * 0x100 + data[2];
                            Y = (data[4] & 0xf) * 0x100 + data[5];
                        }
                    }
                }
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.Ink;
using System.IO;
using System.Runtime.InteropServices;


namespace InkTest
{
    public partial class Form1 : Form
    {
        List<Point> mPoints = new List<Point>();
        Ink mInk = new Ink();
        int mLastX, mLastY;
        DateTime mLastTime = DateTime.MaxValue;
        [DllImport("user32")]
        static extern int GetKeyState(int nVirtKey);

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            ETD.Init();
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            //this.Hide();
            if (GetKeyState(0xA0) > 0)
                return;
            if (ETD.X == 0 && ETD.Y == 0)
            {
                if (mLastX != 0 && mLastY != 0)
                {

                    mInk.Strokes.Add(mInk.CreateStroke(mPoints.ToArray()));
                    mPoints.Clear();
                    mLastTime = DateTime.Now;
                }
                else if (DateTime.Now - mLastTime > TimeSpan.FromSeconds(0.5))
                {
                    string str = mInk.Strokes.ToString();
                    mInk.DeleteStrokes();
                    mLastTime = DateTime.MaxValue;
                    Clipboard.SetText(str);
                    SendKeys.Send("^V");
                }
            }
            else
            {
                mPoints.Add(new Point(ETD.X, -ETD.Y));
            }
            mLastX = ETD.X;
            mLastY = ETD.Y;
            this.Text = string.Format("{0},{1}", ETD.X, ETD.Y);
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            Environment.Exit(0);
        }
    }
}

ExpressCache 1.0.86 OEM破解补丁

20151230 EDIT:已发布新版本破解,欢迎测试。关于新版本请移步lxf.me/432讨论。

20151120 EDIT:因为ExpressCache对蓝屏重启的情况无法缓存,Po主目前已经放弃了该方案而改用了C盘SSD、部分数据目录(如Users)符号链接到D盘的方案。所以暂时无法回应各位观众关于研究118版本的请求,还请各位见谅。

ExpressCache是非常流行的SSD缓存软件,对于主板不支持RAID无法开启Intel快速响应技术的笔记本是非常好的替代选择。与直接将系统装到SSD里相比,使用缓存加速有以下优点:

1、性价比高。只需不到100元购买16-32G的国产SSD即可让电脑加速数倍,无需购买128G以上的SSD。
2、空间有效利用率高。操作系统绝大多数文件都是八百年用不到一次的,而使用缓存则100%地加速最常用的文件。
3、系统盘以外也能加速。如C盘以外游戏读入、编程开发等零碎I/O。
4、数据安全。SSD坏了也不会丢任何数据。

作者满怀希望地购买了32G SSD,安装ExpressCache后发现,这玩意居然限制OEM厂商!只有联想华硕三星等笔记本才能使用!这么好的软件我大神船不能用绝对不!能!忍!

更惊奇的是,翻遍全球居然没有任何人破解过。是我瞎了还是说用SSD当缓存当真有这么小众?

遂亲自操刀搞定。2015年6月24日发布破解补丁,同时支持32位与64位版本。

用法:安装原版软件,重启电脑前应用此补丁。
软件下载地址:
http://think.lenovo.com.cn/support/driver/detail.aspx?DEditid=4337 (32位)
http://think.lenovo.com.cn/support/driver/detail.aspx?DEditid=4338 (64位)

破解补丁:expresscache.1.0.86-patch.7z,解压密码为本站域名。

破解效果:

在普通文件中隐藏TrueCrypt加密卷

TrueCrypt是很好的硬盘加密工具之一,虽然说因一些不能细说的原因目前停止了开发。官方给出替代产品是BitLocker,但众所周知BitLocker是Win7旗舰版里才有的功能,广大人民群众是用不起的,且向下的兼容性并不好。FreeOTFE是个人作品,在x64系统上有驱动签名的问题,虽说可以找作废的证书签名绕过,但肯定过不了杀软且再发布版本将流淌着浓浓的山寨气味。

288号文章中笔者已经详细比较了以上几种加密方式,其中对TrueCrypt和FreeOTFE的隐藏卷做了一些比较。TrueCrypt隐藏卷的缺点主要是往外层卷写数据时有破坏隐藏卷的风险,所以公安部门也会用这种办法,在无法确认隐藏卷存在的时候会破坏掉数据让嫌疑人在被调查结束后也无法取回自己的文件(得不到的就毁掉,这是真爱。。)

FreeOTFE的优点是可以在现有文件中(如电影)隐藏加密卷。近日研究发现,TrueCrypt也可以通过一些小trick实现这样的效果。

原理

TrueCrypt卷的设计结构如下:

偏移	长度	内容
0	512	卷头
65536	512	隐藏卷头
131072		加密数据
-131072	512	备份卷头
-65536	512	备份隐藏卷头

因为需要支持隐藏卷,卷头中存放有卷的开始偏移以及长度。所以只要卷头相应的位置上填上正确的数值,即可指定从任意文件中加载卷。

而挂载时TC提供使用备份卷头尝试加载的功能,这使得我们可以只更改文件尾部-128K或-64K位置的512字节数据,而保持文件头不变,这样原有文件几乎不会被破坏。

实验

TrueCrypt当然没有提供这样的功能。通过阅读TrueCrypt的代码,决定目前暂时使用OD调试的方法来完成这个任务,因为想自己重新写一个TC格式化程序会死人。

通过代码审查,发现格式化卷生成卷头的调用位于Common\Format.c:140行,在TrueCrypt Format.exe中该调用位于0x449251地址处。在这个调用时修改dataOffset和dataAreaSize参数即可指定加密卷在文件中的位置与大小。

调用结束后,header指向的内存,本例中为0x3A096DC内存中的512字节即为得到的卷头。选择512字节,在内存窗口右键点击二进制复制。

用WinHex打开需要当作容器的文件,定位到-0x20000的位置,Ctrl+B粘贴。

使用

TrueCrypt加载时选项中选中“使用备份头”即可。

然后在Windows中格式化即可正常使用。

问题与安全

在实验中表明,卷起始位置必须对齐,不对齐的话可以加载,但无法格式化与写入。具体多么整我没有再去测试,推测是扇区对齐,因为MSDN中ReadFile与WriteFile要求块设备必须是扇区对齐。

另外使用备份头加载的卷无法直接通过自带的修改密码功能修改密码,不过可以绕过,即先备份文件头512字节,将备份头复制到文件头512字节,然后修改密码,再把这512字节恢复回去。

关于取证安全,比起按照TrueCrypt手册使用隐藏卷,安全系数肯定有所下降,因为很容易通过分析文件损坏的位置来确定加密卷的位置,对于“被逼迫说出密码”的情形无法应对。为此应尽量使用电影与视频这样的无规律数据作为容器。此外,文件的修改日期会暴露存有加密数据。对于下载的电影,如果某个文件修改日期比较不合群,则会受到怀疑。

小工具:释放TrueCrypt加密卷占用空间

有不少好电影(大雾)想加密存放,但是心疼硬盘肿么办,加密盘没有填满的空间都浪费了肿么办!

TrueCrypt支持创建文件大小动态增长的加密盘,其原理是基于NTFS稀疏文件,建立时为0字节大小,有数据写入时才真正往硬盘写文件。

但是这样还不够。如果删除了加密盘中的文件,已经分配的空间并不会自动释放。VHD倒是支持先进行碎片整理,然后压缩以释放浪费的空间,但这样显然就和加密无缘了。

能想到的方法很简单,读取加密盘的空间分配信息,然后将加密盘文件对应的区域释放掉。查阅MSDN发现可以对NTFS稀疏文件使用FSCTL_SET_ZERO_DATA将指定区域设为稀疏并释放占用。

简单写了一段小程序,风格很烂(懒),目前只支持标准512扇区磁盘、FAT32分区。NTFS处理太复杂,需要处理MFT、$Bitmap可能的分块,对于开启NTFS压缩的还需要考虑忽略零碎的簇,写起来起码要1000行。

#include <Windows.h>
#include <stdio.h>
#include <string.h>

void error(char* str)
{
	printf(str);
	printf("按回车键退出。");
	getchar(); getchar();
	exit(-1);
}

void main()
{
	printf("TrueCrypt稀疏文件释放工具v0.1 by gmsj0001\n");
	char buf[64];
	printf("输入加密卷挂载盘符,带冒号:");
	scanf("%s", buf);
	char file[64];
	sprintf(file, "\\\\.\\%s", buf);
	//打开分区,读取FAT表
	HANDLE hDevice = CreateFile(file, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
	if (hDevice == INVALID_HANDLE_VALUE)
		error("分区打开失败!");
	char boot[512];
	DWORD dwRead;
	ReadFile(hDevice, boot, 512, &dwRead, NULL);
	if (memcmp(&boot[0x52], "FAT32   ", 8))
		error("不是FAT32分区!");
	int fat_addr = *(short*)&boot[0xe] * 512;
	int fat_size = *(int*)&boot[0x24] * 512;
	int clu_num = (*(int*)&boot[0x20] - *(short*)&boot[0xe] - *(char*)&boot[0x10] * *(int*)&boot[0x24]) / *(char*)&boot[0xd];
	int data_addr = (*(short*)&boot[0xe] + *(char*)&boot[0x10] * *(int*)&boot[0x24]) * 512;
	int clu_size = *(char*)&boot[0xd] * 512;
	int* fat = (int*)malloc(fat_size);
	printf("读取FAT表。。。\n");
	SetFilePointer(hDevice, fat_addr, 0, FILE_BEGIN);	//操作磁盘设备必须是扇区对齐
	ReadFile(hDevice, fat, fat_size, &dwRead, NULL);
	CloseHandle(hDevice);
	//打开文件
	printf("请卸载加密卷,然后输入加密卷文件名:");
	scanf("%s", file);
	hDevice = CreateFile(file, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
	if (hDevice == INVALID_HANDLE_VALUE)
		error("打开文件失败!");
	printf("正在释放空间,请祈祷本软件没有bug。。。\n");
	FILE_ZERO_DATA_INFORMATION zero;
	int i;
	for (i = 0; i < clu_num; ++i)
	{
		if (fat[i + 2] == 0)
		{
			zero.FileOffset.QuadPart = 0x20000 + data_addr + (LONGLONG)i * clu_size;	//0x20000为TrueCrypt文件头的长度
			for (; i < clu_num; ++i)
				if (fat[i + 2] != 0)
					break;
			zero.BeyondFinalZero.QuadPart = 0x20000 + data_addr + (LONGLONG)i * clu_size;
			DeviceIoControl(hDevice, FSCTL_SET_ZERO_DATA, &zero, sizeof(zero), NULL, 0, &dwRead, 0);
		}
	}
	CloseHandle(hDevice);
	free(fat);
	printf("搞定,按回车键退出。");
	getchar(); getchar();
}

暂不提供二进制程序,因为我不完全保证此工具是完全bug free的,万一被转载到天涯海角出了问题我扶不起。

使用效果:

以后建立加密盘可以大胆地开空间(当然FAT32不能超过32G),再也不用担心填满后删除造成空间浪费了。

手动编辑$BadClus标记坏扇区

高能警告:本文系给文件系统动手术,意在折腾与学习,实际操作中请务必小心谨慎,最好备份重要文件以及要编辑的扇区,进行每一次编辑操作前请务必确保自己已经了解这一步的原理,否则请在分分钟把整个分区弄丢后执行chkdsk修复的过程中默念100遍“不作死就不会死”。

友情提醒:本文仅记录笔者个人案例,仅说明$BadClus文件相关,不介绍基础知识,请先了解硬盘分区、NTFS文件系统、MFT表等相关知识。

修复文件与定位坏扇区位置

这几天打算调整分区对齐,因为没对齐的分区若要对齐必须全部数据平移而不能借助无损分区工具(所有的簇全部是没对齐的必须全部平移,分区工具只能按簇的单位滑动才能尽量少地移动数据),所以只能考虑在对齐的位置新建分区,将全部数据拷进来。

往移动硬盘拷数据时有一集动画片复制失败,使用WinHex拷贝选区到新文件写入到80%的时候报告错误,新文件不完整。查看新文件的大小在源文件中找到出错位置附近,跳过报告错误的位置将剩余部分手动复制到新文件中,出错的位置粘贴0字节补齐,这样即可得到基本修复的文件(当然没办法完整播放了,但是可以跳过那几秒)。之前用HDDREG修复过硬盘上的一些坏扇区,这次打算尝试通过NTFS的坏簇列表屏蔽。

用WinHex打开分区,找到出错的文件右键-位置-列出的簇,在簇窗口中右键-缩短连续的簇列表,手动计算之前编辑文件时出错的大概位置对应到哪个簇,并定位到这块位置,此时WinHex访问出错,并且应当能报告无法访问扇区的准确扇区编号,将扇区号记录下来,加上分区的起始扇区,即得到硬盘坏扇区的LBA。因为我是要重新分区,所以待重新分区后再用这个LBA计算出新分区的簇号,如果不是重新分区,直接记录簇号即可。

MarkBadClusTool是一个很好的作品,可根据LBA将坏簇填写到$BadClus里,但笔者使用时出现了一些问题,比如填写扇区扩展的数值填0的话程序报告无效数值,填小数目比如1的话程序最后执行完后压根没往$BadClus里写东西,估计是算法有缺陷。笔者硬盘的坏扇区不是机械故障,很小规模的,并且不会自己扩散,所以没必要把周边的好扇区一并屏蔽了。另外就是这个工具每次都要走两遍MFT,作用在于如果坏簇上已经存在文件,将尝试将该文件移走,而笔者已经手动把那个文件弄走并且修复了,默认坏簇现在处于free空间,仅需要把它添加到$BadClus这个文件里即可。因为MarkBadClusTool有提供源代码,遂决定自己折腾一下。

data runs、稀疏文件与NTFS压缩、$BadClus:$Bad研究笔记

对于硬盘上的数据,MFT中以data runs的结构存储文件的簇指针。简单地讲,就是一堆起始簇号-大小pairs的有序集合,这样就能表示一个文件了。pair使用可变长度整数记录,每个pair首字节高4位表示“起始簇号相对于上一块的delta”这个整数的长度,低4位表示“块大小”这个整数的长度。语言说起来比较绕,建议查阅已有文章,有图解的一目了然。

$BadClus的$Bad属性记录坏簇这个知识很容易就在网上找到,但是如果直接按上面的data runs填入坏簇的位置信息,写入后会发现分区丢了,说明“打开的姿势不对”。查阅MarkBadClusTool的代码,发现程序使用DataRunsToSpaceDataRuns“将常规Dataruns数据转换为用来描述空间信息的dataruns”。这句注释写的很不明所以,走读代码后发现这段代码是将坏簇的data runs两端与之间填入正常区域的长度。

原理上讲同为data runs是不应该有多种写法的,查阅了很多资料才得知真正含义:$BadClus:$Bad文件并不是一个将坏簇连接起来的文件,而是一个大小为整个分区的NTFS稀疏文件,其中正常的位置是稀疏文件的0,坏簇的地方指向磁盘上的坏簇。而稀疏簇的表示是将“起始簇号”设为-1,“起始簇号delta”设为0,表现在data runs里就是一个字节的整数长度接上正常块的长度数据。这一点MarkBadClusTool虽然执行的结果对,但写法与注释其实并不正确(那么真正的0簇开始的数据因该怎么写,答案是1x xx 00,见$Boot中的记录)。

顺便聊一下压缩。NTFS压缩是完全基于稀疏文件机制的。data runs只有真正簇(delta_lcn不为0)与稀疏簇(delta_lcn为0)之分,并不在乎存放的数据是压缩的还是未压缩的。数据是否压缩通过64K块是否在末尾有稀疏簇来判断。如果一个64K块尝试压缩后体积并没有减少4K以上,那么该数据依然会以未压缩形式存放,如果减小4K以上,那么就在末尾插入几个稀疏簇补齐到64K。

NTFS的文件属性查看压缩文件对于压缩后没有减小体积的其“占用空间”显示为文件大小,但是从上面的算法来看,有一定的几率是压缩后比未压缩大一个簇的。可实验建立一个刚好为4K的随机数据文件(无法被压缩),NTFS会将其补齐到64K然后尝试压缩,结果自然要比4K多几字节,这样就占用了2个簇的实际数据。

基于稀疏文件的NTFS压缩在对付迅雷下载刚开始创建文件时的卡死有奇效(迅雷脑残不在程序里指定创建稀疏文件。。。)

案例

下面以笔者的案例介绍一下手动修改的过程。

本次要处理的坏扇区为754546051到754546055连续的5个扇区,位于当前D盘,D盘起始扇区为83886080(40G整),持续400G。簇总数为104857599(最后一个簇为保留扇区,最后一个扇区为PBR的备份)。

754546051-754546055对应到D盘逻辑扇区为670659971-670659975,除以8得到簇号为83832496,刚好被包含在一个簇里。

构造整个分区大小的稀疏文件:第0到83832495共83832496个簇的0、第83832496簇开始长度为1的坏簇、第83832497到104857598共21025102个簇的0。写出来为:

04  B0 2E FF 04    41  01  B0 2E FF 04    04  4E D1 40 01    00

定位到$BadClus:$Bad的data runs位置($BadClus+0x168),填写上面的内容,然后将0x124位置的属性长度改为修改后长度,8字节对齐(本例改为0x60),在0x180位置填入MFT记录结尾的FF FF FF FF,在0x18位置填入修改后的MFT记录长度,8字节对齐(本例改为0x188)。然后找到$Bitmap文件,计算第83832496/8=10479062余0字节对应的扇区位置,将该字节第0位置1表示空间已分配。

然后即可写入磁盘。WinHex旧版本不支持DeviceIoControl卸载分区,换用新版本即可。

参考文献

1、MarkBadClusTool及其源代码:http://bbs.pediy.com/showthread.php?t=162539
2、上述源码中layout_ntfs.h中Attribute compression一节的注释
3、$BadClus:$Bad是稀疏文件:http://0cch.net/ntfsdoc/files/badclus.html
4、data run:http://blog.csdn.net/begges/article/details/6699236
5、《数据恢复技术深度揭秘》相关章节

手动编辑启动扇区制作DOS启动盘

为什么要做DOS启动盘?

目前在MS平台下需要涉及到开机引导的工作,如各种的PE系统、U盘工具箱、Win7的OEMBIOS激活等,基本上全部是使用grub4dos作为第一引导,即启动分区中有一个grldr文件。

这个时代需要进DOS操作的应用目前并不多,大多数人都习惯了PE。然而除了个人偏好以外,还是有部分软件需要在DOS下运行,如以前提到的HDD Regenerator。在grub4dos中进DOS并不难,找来io.sys与command.com然后在grub里打chainloader /io.sys就成了。

“老师,能不能给力点?”好的,我们知道grub4dos正如其名字,原本是个在DOS版的grub,名叫grub.exe,开机引导的grldr大概是之后才添加的。那么能不能在U盘或移动硬盘上只装DOS的启动,再在DOS下以一个程序的身份运行grub.exe,DOS–>grub–>chainloader /bootmgr或者DOS–>grub–>kernel /vmlinuz是不是显得NB些?

于是笔者就开始折腾,然后发现这道题还不是那么容易解决——这个问题实际上是如何在高版本系统下搞出一个带Win98引导记录的FAT32硬盘分区。笔者试了几款常用的软件,如USBoot、HPUSBFW,发现都不能如愿,遂只好自己解决。USBoot的HDD模式会把U盘缩水到2G,说明他可能用的是FAT12分区;HPUSBFW不支持选择单个分区,大概会把我500G的移动硬盘清理干净来着。以下内容需要读者了解系统启动的基本知识,诸如MBR、DBR、分区表等笔者不再详细复述。

在DOS/Win9X下,可以用format C: /s命令格式化一个FAT32分区并复制相关引导文件。格式化时分区的DBR将被填写DOS/Win9X的引导代码,即从IO.SYS文件启动。但是在高版本的Windows中,虽然也可以在我的电脑中右键格式化分区,但DBR会被填写另外的代码,对于NT5系统是从NTDLL启动,NT6系统从BOOTMGR启动。NT系统中格式化时也有一个“创建MS-DOS启动盘”选项,但这个选项仅在格式化软盘时有效,格式化硬盘时这个选项是灰色的,而且软盘分区为FAT12格式,其DBR引导代码也与硬盘分区不同。

笔者首次采用的方法是将U盘或移动硬盘映射为vmdk虚拟硬盘,然后使用虚拟机启动Win98的启动盘后format。这样需要注意一个问题,即Win7系统出于安全考虑,直接读写磁盘时会写保护有分区的空间,如需操作写保护的空间,必须使用DeviceIoControl锁定分区,显然,我们拿不到虚拟机软件的代码,所以需要想点办法。笔者想出的办法是用WinHex将移动硬盘MBR扇区结尾的55AA去掉使分区表失效,拔掉重插后Windows便不能加载分区,再用WinHex将其改回来,然后挂到虚拟机上用Win98启动盘进DOS格式化,效果很好。注意必要的时候还要用fdisk将分区设为活动,以及fdisk /mbr填写默认的MBR代码。Win98(DOS7.1)启动盘的镜像可以在此处下载。

“老师,能不能再给力点?”好的,上面的办法有点复杂,要用上虚拟机,还要用改分区表的危险办法避免写保护。我们既然知道MBR–>DBR–>IO.SYS的启动顺序,何不试试直接用WinHex在相应的地方填写引导代码?

笔者找到一篇神文章,详细地dump并分析了启动盘的引导记录。请猛击此处阅读(什么?不懂鸟语?那还学个蛋计算机,洗洗睡吧)。文章分析指出,Win98的DBR代码存在于分区的第一扇区和第三扇区。文章并不能方便地直接把十六进制代码拷出来,不过我们可以在format.com里寻找。

WinHex打开format.com,查找MSWIN4.1,可以查到两条记录,可以看到第一条记录之后不远写着FAT12,第二条是FAT32。找到FAT32的这段扇区数据复制5A-1FD范围的内容(即format.com 39C4-3B67),粘贴到U盘分区首个扇区的相应位置,再往后找到第三扇区的引导代码(即format.com 3D6A-3EEF),粘贴到U盘分区第三扇区的相应位置。U盘MBR也要初始化并设置分区标志为活动分区(80),Partition Manager之类的软件都可以做。

这样即免去了格式化分区而做到了刷写引导代码。最后拷贝io.sys、command.com两个文件,如果需要还可以拷贝himem.sys等文件,即做好了DOS启动盘,将grub.exe拷进来并编辑menu.lst即可进一步启动bootmgr、linux等其他系统。

哦对了,开机时会闪过Win98的蓝天白云,瞬间穿越的赶脚,有助提升逼格。如果不喜欢或者觉得很傻,可以创建0字节的logo.sys,或自建256色位图设计自己的开机画面,高端点亦可直接修改io.sys。

 

QQIntl在Win8、Server2008等系统上卡死故障的HACKFIX

一个月前开始使用Server 2008 R2作为工作站系统,发现QQIntl经常出现卡死,根本无法使用,从1.5到2.1版本问题均存在。似乎在Win8.1上也有同样的问题。

卡死是线程死锁导致的,锁死发生在NTDLL中加载DLL文件的系统加载锁上,且锁死发生后占有锁的线程已经结束(而不是等待另一个锁这种互相等待的死锁),给排查带来了很大的难度。锁死发生后OD、VS不能挂入调试,否则OD等也会卡死。OD亦不能在启动QQ时就挂入,因为QQ本身有防调试机制。只能用WinDbg挂入。也就是说,根本无从下手,只能HackFix。

经过N天的实验,来回更换了五六个方案,最终采用了以下方案的HackFix,至今运行基本稳定。

#include <windows.h>
#include <winnt.h>
#include <process.h>

HANDLE hHookHeap = NULL;

PVOID InstallHook(PVOID CodeAddr, LONG CodeLen, PVOID HookProc)    //ret: StubProc
{
    if (hHookHeap == NULL) hHookHeap = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0);
    DWORD oldProtect;
    VirtualProtect(CodeAddr, CodeLen, PAGE_EXECUTE_READWRITE, &oldProtect);
    PVOID StubProc = HeapAlloc(hHookHeap, 0, CodeLen + 5);
    memcpy(StubProc, (PVOID)CodeAddr, CodeLen);
    *((PBYTE)StubProc + CodeLen) = '\xE9';
    *(PDWORD)((PBYTE)StubProc + CodeLen + 1) = (DWORD)CodeAddr + CodeLen - ((DWORD)StubProc + CodeLen + 5);
    *((PBYTE)CodeAddr) = '\xE9';
    *(PDWORD)((PBYTE)CodeAddr + 1) = (DWORD)HookProc - ((DWORD)CodeAddr + 5);
    VirtualProtect(CodeAddr, CodeLen, oldProtect, &oldProtect);
    return StubProc;
}

DWORD lpLoaderLock = 0x1020c0;
void* lpStub;

void __stdcall hook_RtlEnterCriticalSection(CRITICAL_SECTION* lpCriticalSection)
{
	if ((DWORD)lpCriticalSection == lpLoaderLock)
	{
		int count;
		for (count = 0; count < 10; ++count)
		{
			DWORD dwLockThread = (DWORD)lpCriticalSection->OwningThread;
			if (dwLockThread == 0 || dwLockThread == GetCurrentThreadId())
			{
				break;
			}
			Sleep(10);
		}
		if (count == 10)
		{
			InitializeCriticalSectionEx(lpCriticalSection, 0, 0x2000000);
		}
	}
	((void(__stdcall*)(CRITICAL_SECTION*))lpStub)(lpCriticalSection);
}

void do_hook()
{
	DWORD hNTDLL = (DWORD)GetModuleHandleA("ntdll");
	lpLoaderLock += hNTDLL;
	lpStub = InstallHook((void*)(hNTDLL + 0x222b0), 5, &hook_RtlEnterCriticalSection);
}


BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		if (GetModuleHandleA("QQ.exe") == NULL)
			return FALSE;
		do_hook();
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

代码中的两个地址常数一个是加载锁的RVA,一个是RtlEnterCriticalSection的地址。

注入采用的是修改了QQ目录下的一个DLL使之LoadLibrary这个DLL(我选的是zlib,大部分TX签名的DLL在QQ登陆时会有hash校验而不能修改)。

删除Win7镜像中ei.cfg以达到选择操作系统的目的(偏执的完美主义版)

这篇文章讲的并不是有关删除ei.cfg这个文件就能选择安装的操作系统这一知识——delete键在哪里这种事情按说是不用教的。

通常情况下,我使用U盘安装系统,也就是把sources目录拷贝到U盘中,至于ei.cfg文件,直接按delete键就ok了。

但是在某些情况下必须用光盘——给Mac装系统的时候。去年某飞临走时需要把他Mac上的win7换成家庭高级版(出国装个盗版的旗舰版win7太高调)。那么需要在光盘镜像里删掉这个文件,这就稍微有点麻烦——我们需要用ultraiso之类的镜像编辑软件把这个文件删除,然后保存一个新镜像刻盘。嗯,广大群众都是这么做的。

但是作为一个处女座程序员。。。。。。。。我表示不能忍受为了删一个文件就要重新往硬盘里写3个G的文件!!!!!!!!!!并且这样出来的文件已经“不再是msdn原始档”,说不定ultraiso往里面留下点什么呢。(事实上,某飞也不能忍受,他最后宁愿从msdn上重新下个原版的家庭高级版镜像。)

能不能直接对ISO文件做个小patch来达到这一目的?当年我们修改试用版的VS2008和VS2010就是直接把新的序列号替换进去的。嗯,可以试一试。。

于是用虚拟光驱加载ISO,用WinHex打开光盘,找到sources目录表,往下翻到ei.cfg,记下在整个磁盘上的偏移。卸载虚拟光驱,winhex打开iso文件,找到这里,把ei.cfg这个文件名改掉一个字符。满怀信心地重新加载,结果发现sources目录只剩下200M,最后一个文件名叫du.dll,看来加载到这里的时候哪里坏掉了。

果然不能投机取巧,下面是技术探讨的正文。

win7-1

以中文x86带sp1版win7镜像为例。如图选中的一段为一条文件记录。为什么这样断句呢,因为目录表的起始扇区也是以00 01 02 00开始的,经常做结构探索的应该有这种直觉的断句方法。

查阅UDF与ECMA 167白皮书,可以找到这个数据的前16字节为Descriptor Tag结构,如下(ECMA 167 3/7.2)。

RBP Length Name Contents
0 2 Tag Identifier Uint16 (1/7.1.3)
2 2 Descriptor Version Uint16 (1/7.1.3)
4 1 Tag Checksum Uint8 (1/7.1.1)
5 1 Reserved #00 byte
6 2 Tag Serial Number Uint16 (1/7.1.3)
8 2 Descriptor CRC Uint16 (1/7.1.3)
10 2 Descriptor CRC Length Uint16 (1/7.1.3)
12 4 Tag Location Uint32 (1/7.1.5)

在这里Tag Identifier等于0x0101即257,查阅ECMA 167 4/7.2.1可知对应的是File Identifier Descriptor结构,翻到ECMA 167 4/14.4章节可找到该结构,如下。

RBP Length Name Contents
0 16 Descriptor Tag tag (4/7.2)(Tag=257)
16 2 File Version Number Uint16 (1/7.1.3)
18 1 File Characteristics Uint8 (1/7.1.1)
19 1 Length of File Identifier (=L_FI) Uint8 (1/7.1.1)
20 16 ICB long_ad (4/14.14.2)
36 2 Length of Implementation Use (=L_IU) Uint16 (1/7.1.3)
38 L_IU Implementation Use bytes
[L_IU+38] L_FI File Identifier d-characters (1/7.2)
[L_FI+L_IU+38] * Padding bytes

该章节中对File Characteristics描述有当该字段设置第2个bit位时代表该文件被删除。太好了,将这个位置的数据改为4,而不需要动下面的文件名了。

刚刚我们改文件名失败的原因相信在上面的结构描述中已经能找到答案:Descriptor Tag结构中有对数据的CRC校验,所以我们刚才直接改文件名就相当于破坏了光盘的结构。经测验,这里的CRC16采用的是XModem算法。http://www.lammertbies.nl/comm/info/crc-calculation.html这个地址可以在线计算各种算法的CRC。我们将修改后的上图0x9d418到0x9d43b共0x24个字节重新计算CRC,结果为0x942e,将这个结果填入0x9d410处。

最后需要计算Tag中除了偏移4以外的15个字节的checksum填入偏移4处,这个用Windows计算器就能算好,改为F4。

这样就改好了。如下图,一共修改了4个字节。

win7-2

保存加载虚拟光驱,成功。

 

参考文献:

Universal Disk Format Specification Revision 2.60  http://www.osta.org/specs/

Standard ECMA-167  http://www.ecma-international.org/publications/standards/Ecma-167.htm