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同学的研究历程。

关于BrainWallet的安全性

因为国外不少VPS都可以使用Bitpay,虽然Paypal也是可以支付的,但比特币肯定要更方便更安全,也免去了输网银密码。所以近日在交易平台上将两年前挖的一些LTC换成了BTC,又顺便软妹币换了一些。

然而这次回归却发现,脑钱包关站了!看了下相关新闻,说是某黑客大会上某黑客做了个程序用字典扫出来700多个币,宣称脑钱包“很不安全”,然后作者就删代码关站了。

先回忆一下脑钱包的原理:用户指定一段字符串进行SHA256作为私钥。就这么简单,没了。无需任何客户端或在线钱包,仅使用脑钱包的离线HTML+JS就能完成比特币地址的生成和交易(离线签名,然后通过blockchain.info提供的API发出去),永远不用担心重装系统忘了备份C盘的钱包文件而丢币。简洁就是美,我喜欢。

完全随机生成的密钥将具有256bit的安全性。256bit是什么概念呢?我们知道普朗克时间是1e-43秒数量级,宇宙年龄150亿年也就是1e17数量级,加起来也就是10的60次方的数量级,换成二进制差不多200bit。也就是说一台计算机从宇宙大爆炸开始以宇宙最小时间间隔的速度穷举密码,至今也只能枚举出200bit的密码,你需要1亿亿台计算机才能枚举完256bit。。。(当然,如果你假设宇宙中所有粒子都是计算机的话当我没说,这个是1e1079数量级)

用户指定的密码不足256bit则安全性会降低,所以密码是越长越好。然而黑客说有人用30多字符的密码还是被他拿下了。其实这是个误读,因为脑钱包当时提供了默认字符串,是用几个英文单词组合起来的。实际上常用单词并不多,假设使用3000个常用单词,使用5个英语单词实际上只有10^17种可能(假设随机选取),其信息熵为57bit。事实上人们更倾向于使用更简单的单词,所以这个值会更少。据黑客说他的软件可以在单机上一秒猜130000个密码,那么考虑有人用30000台电脑的僵尸网络去计算,算全57bit种可能性只需要一年。如果黑客拿下了超算中心就更开心了。

所以关键还是在于有人不会用,或者低估了数量级,比如有小白直接用了空字符串的hash。正确使用脑钱包只需要记住一点——尽可能使你的密钥的信息熵更大,这样脑钱包才不至于秒变脑残包。具体实施考虑以下几点:1、要长,个人建议30位字符以上。2、尽量不要使用英语单词。3、不要使用别人用烂的密码模式。4、尽量不要使用社工能得到的信息。5、增加组合算法难度。

两年前,我用了“喜欢的人的照片”的sha256直接派生私钥,在此要说明,使用硬盘上某个特定文件作为密钥文件取hash是可行的,但是这个文件最好是只有你自己持有。另外,最好你的电脑上没有装各家的全家桶,因为全家桶们的云查杀就是个hash库。当然,用密钥文件的hash再加上你自己想出的字符当salt效果应该不错,不过最好去cmd5网站看一下,不要用那里面已经有的常用salt算法。

参考文献:导致brainwallet关站的那个一秒猜130000的黑客在大会上的演讲稿,讲得非常清楚,建议阅读。https://rya.nc/cracking_cryptocurrency_brainwallets.pdf

另外建议阅读此文:一些可行脑钱包的尝试。作者的文笔比po主要好,解释和建议都通俗易懂,而且还给真正理解这篇文章的粉丝送币。

西数硬盘调整APM避免停转

跟daddy置换电脑时购买了西数WD10JPVX笔记本硬盘作为移动硬盘,结果该硬盘有个坑爹的设定,就是每当空闲十几秒就直接停转了,再次启动时系统就会卡个两秒钟。可能西数没去考虑将此硬盘作为从盘或移动硬盘使用。

搜索到了“C1门”,试着用wdidle3修改,表示对C1(磁头归位)也许有效,但对停转(04)没有效果。

继续搜索得知这是APM(高级电源管理)的问题,此型号APM默认值为0x60,比较流行的解决方法是安装CrystalDiskInfo,将APM关闭或者设为0x80。但由于硬盘断电后该设置会复位,所以需要将CrystalDiskInfo设为开机启动。

为了一个破硬盘要装个daemon?po主表示不能忍,再说我这是移动硬盘,插别的电脑上怎么办,中国能移动daemon又移不动。

考虑研究一下如何调用APM,然后自己写一个小程序放到移动硬盘上,插上后运行一下。

期间参考了这篇文章,但是编译后发现只能支持本地硬盘,无法识别移动硬盘。谷歌未果,只好OD挂一下CrystalDiskInfo,发现其对本地硬盘和移动硬盘调整APM时DeviceIoControl的ioctl id不一样,搜索此id得知USB移动硬盘是SCSI接口。继续搜索得到关于SCSI ATA PASSTHROUGH调用的白皮书,经多次试验成功实现修改APM。

代码如下:

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

void main()
{
	char name[260] = "\\\\.\\";
	GetModuleFileNameA(NULL, &name[4], 256);
	name[6] = 0;
	HANDLE handle = CreateFileA(name, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
	STORAGE_DEVICE_NUMBER sdn;
	DWORD nRet = 0;
	BOOL ret = DeviceIoControl(handle, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &nRet, NULL);
	sprintf(&name[4], "PhysicalDrive%d", sdn.DeviceNumber);
	CloseHandle(handle);
	handle = CreateFileA(name, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
	ATA_PASS_THROUGH_EX aptex = {0};
	aptex.Length = sizeof(ATA_PASS_THROUGH_EX);
	aptex.TimeOutValue = 2;
	aptex.CurrentTaskFile[6] = 0xEF;
	aptex.CurrentTaskFile[0] = 0x05;
	aptex.CurrentTaskFile[1] = 0x80;
	ret = DeviceIoControl(handle, IOCTL_ATA_PASS_THROUGH, &aptex, sizeof(ATA_PASS_THROUGH_EX), NULL, 0, &nRet, NULL);
	SCSI_PASS_THROUGH spt = {0};
	spt.Length = sizeof(SCSI_PASS_THROUGH);
	spt.TimeOutValue = 2;
	spt.CdbLength = 12;
	spt.Cdb[0] = 0xA1;
	spt.Cdb[1] = 3 << 1;
	memcpy(&spt.Cdb[3], aptex.CurrentTaskFile, 8);
	ret = DeviceIoControl(handle, IOCTL_SCSI_PASS_THROUGH, &spt, sizeof(SCSI_PASS_THROUGH), NULL, 0, &nRet, NULL);
	CloseHandle(handle);
}

程序会根据自己所在的盘符打开PhysicalDrive,并尝试SATA与SCSI两种方式修改APM值为0x80。

对于不想编译的同学,也可直接点此下载,解压密码为本站域名。不要修改文件名,windows会将文件名带有Setup的程序以UAC管理员权限启动。

小工具:Everybackup

五年前的这个时候(2010年12月7号),某人本着天下无贼的思想把电脑带去了演出礼堂,用实际行动证明了天下无贼是不成立的(那个时候世界上还没有nozuonodie这个词)。损失包括并不仅限于一年的和某人的聊天记录,导致这段时间由于后来的强行删除记忆而永远成了断片;以及正在开发的约万行LibCHD及相关项目,导致本人月流水万元的游戏辅助工作室直接倒闭。

在此之后,关于如何备份代码(或者别的重要文件)的问题一直很困扰,直接打包会有一堆无用的文件(比如Debug/Release目录、IntelliSense数据库),浪费时间和空间。

后来发现WinRAR、7zip可以写通配符的文件列表,如*.cpp *.vb这样,也可以写排除列表,例如*\Debug\*,这样用了一段时间,发现了个问题——有些我fork下来编译的类库我并不需要备份,因为这些不是『我的』代码。

好吧,rar和7z都可以写排除列表。可是排除列表只有一个,而这种项目有时藏得很深。最好是能有一个能在每目录里建个配置文件什么的来说明这个目录下哪些需要备份哪些需要排除,类似apache的.htaccess。这个并不复杂,想了下就用批处理写了个枚举每个目录下的include和exclude文件然后汇总成全的include和exclude交给winrar。

这样又用了一段时间,又发现两个问题。第一,假如父目录默认排除了*.exe,而某个子目录需要包含*.exe(例如一个做pediy的项目),那么实测子目录的包含不能生效。第二,winrar对目录的扫描会花费很多时间。

第一个问题是集合运算的逻辑题,严格按照父目录——子目录的顺序求并集(包含)和差集(排除)即可解决。第二个问题,想到咱做程序的必备Everything搜索,用Everything索引好的列表想必应该很快。

于是研究了一下写出此工具。用法很简单,在任意需要备份的目录中建立列表文件backup.lst,写上需要包含和排除的文件名或通配符。软件会调用everything找出全盘所有的backup.lst,然后再根据里面的通配符再次调用everything找到符合要求的文件。并利用.NET 3.5 LINQ的集合运算最终生成备份文件列表喂给winrar。

由于everything的专业性,建表速度比winrar自己扫描要快很多。目前的代码筛选我的Projects目录各种文件类型只要4秒,而winrar自己的方案要10秒以上。主要瓶颈似乎是与everything主窗口IPC通信,如果改成异步并行可能会更快。

目前测试正常。代码和release目前都在github上,欢迎试用或改造。https://github.com/gmsj0001/Everybackup

 

ATL/WTL黑科技

1、真正的按需编译

一般来说我们提到“类库”概念的概念马上就想到DLL,也就是运行时类库。这样写框架的话框架有多少代码DLL就有多大。据实验最新的MFC的程序只需要一个空的对话框,静态编译出来EXE大小就有1M。

而C++模板技术几乎相当于宏替换,代码的实现都直接写在头文件里,并且只有在真正调用时才会编译。这样使得ATL对SDK的封装是在源代码级别的,相当于用到什么才去拉一段代码贴上去。没有MFC那样庞大的设计结构,耦合性很低,编译出的代码也就很精练了。

2、编译期多态

和上面类似,使用C++模板可以实现编译期间基类对子类成员随便直接访问,而无需通过运行时虚函数,这在通常的面向对象编程思想中是反人类的。例如以下代码:

template<class T>
class CWindow
{
public:
	void Create()
	{
		CreateWindow((T*)this->ClassName, ...)
	}
};

class MyWindow : public CWindow<MyWindow>
{
public:
	MyWindow() : ClassName("My Window Class")
	{
	}
	char* ClassName;
};

第一次看到这样的代码我觉得我就在想,卧槽面向对象不带这样搞的。。。

3、动态代码实现HWND映射到窗口类指针

很久以前我就在设计如何在C++中实现类成员函数的委托,这玩意最大的用处可能就是窗口子类化了,可以省去维护一个HWND到指针的映射表,后来就写了一个动态代码的方案将this指针填写到ECX中(lxf.me/217)。今天发现ATL在几十年前就用上这种方法了,并且实现得比我的更加优雅和类型安全,还顺便把x86、x64、ia64、arm、mips等众多构架的实现都写好了(有兴趣可阅读atlstdthunk.h)。

综上,微软大法好,*nix一生黑。

生日悖论与Hash需要位数

m个数字随机选取n次,各不相同的概率P=((m-1)/m)^(n*(n-1)/2)

m、n很大时取最高次项公式近似为P=((m-1)/m)^(n^2/2)

lim(m->+…)((m-1)/m)^m=1/e

有P=(1/e)^(n^2/2/m)

n=(log(1/e)(P)*2*m)^(1/2)

当P=0.5时,代入上式n=(1.386*m)^(1/2)=1.177*m^(1/2)

在生日悖论原题中,m=365,得n=22.49,即班上有23人时生日出现重复的概率就超过0.5了(虽然是近似计算,但这个误差已经很小了)。

选取hash的话,考虑简单用32数表示hash,看上去能表示的数字有40多亿,但从上式中可以看出,出现碰撞的概率其实是其能表示的数字开平方的级别,也就是数万条数据就可能出现碰撞,这是很可怕的。

为了使crc32不容易碰撞,我们取P=0.95,可求得n=20991。若要求更高取P=0.99,求得n=9291。

(鉴于博主的高中数学已经还给老师,高数又都喂狗了,windows的计算器没法对亿级别的数做指数运算所以没法用最开始的公式验证,所以上面的计算如有疏漏还请读者指正)

破解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。

PHP小轮子计划

因为“PHP是世界上最好的语言”,所以大凡尝试过用原生PHP写网页的同学都会被要裸写一堆重复代码恶心到,不想恶心稍微偷懒一点的话又会被黑客分分钟sql注入之类的。

用框架是个好的选择,然而意味着学习成本提高、语言灵活性降低、程序执行效率降低。比如我显然没必要做去学习Zend然后给工作室写一个简单的学生管理系统这样大炮打蚊子的事。轻量级的框架固然也有,比如CI我就很喜欢,但也有着把简单问题复杂化以及某些功能用着不顺手的问题。有能力让框架比原生更简单的我觉得只有微软帝国,比如COM和.NET,设计上几乎找不到缺点。

于是萌生自己造个框架的想法。这个框架应该具有以下的特点:

框架应该极度简洁(强迫症发作),最好1000行以内搞定,以至于它其实只是个类库,接近原生开发。
不为了面向对象而面向对象,好好做单身狗(PHP-CGI作为单进程模型,本质为过程式编程,有些东西硬要去封装也必然会搞成singleton)。
在该框架基础上写代码清爽。
MVC,只做小项目的话也至少做到VC。
有一个URI router。
输入安全检查。
DB接口与安全,有需求可以做sql语句生成,但active record估计用不上。
session与cache存储。

20151028 Edit:

目前项目的设计架子已经基本成型,代码开始提交到github上,欢迎围观。
https://github.com/gmsj0001/x-php

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);
        }
    }
}

硬链接问题:不应使用Xcopy移动C:\Program Files目录到数据盘

有一些文章提到了可以使用xcopy将C:\Program Files、C:\Program Files (x86)、C:\ProgramData、C:\Users移动到D盘,然后在C盘建立NTFS符号链接以减少空间占用。例如:

xcopy /e /h /k /x /y /b /c “C:\Program Files” “D:\Program Files”
rd /s “C:\Program Files”
mklink /j “C:\Program Files” “D:\Program Files”

笔者有一段时间也这样做过,但最近经过深入研究,发现这样做忽视了一个很大的问题——NTFS硬链接。

windows vista以上,所有系统文件的实际上均存放在winsxs目录中,然后在system32或syswow64目录下建立硬链接,这样实际上只占用一份空间。安装更新时也是这样做,新文件与旧文件并存,然后将硬链接改到新的文件上。删除文件时,必须将一个文件所有的“指针”都删除文件才会真正删除。

wim镜像支持NTFS硬链接的保存,通过imagex /info可以看到,安装盘中有4G的文件是存在硬链接的。

这样一来移动Program Files到D盘对于windows自带软件,如ie、wmp等完全无法起到省空间的作用,因为它们在winsxs中还有一份指针。并且就算是以后再移动回来,硬链接也无法恢复,还是会多占用一份空间。

另外,由于该问题,使用目录符号链接将C:\Program Files指向D:\Program Files会造成windows update对ie等软件更新失败,因为自动更新无法在已经符号链接到D盘的Program Files目录中创建指向winsxs的硬链接。网上也有人遇到过,说将注册表Program Files目录改为D盘即可,然而没人搞清楚为什么。

硬链接只有在安装系统、安装更新时能够自动地建立。一旦破坏,除了微软没人知道应该怎么科学地重建。如果是换硬盘而不想重装系统,千万不能使用xcopy,而要使用基于文件系统的拷贝,如imagex捕获镜像再应用到新盘上,或ghost partition to partition。否则在win7下最少会造成5G空间浪费,在win8和win10上由于自带显卡驱动包这个数字可能高达几十G。

Users目录倒是可以移动到D盘然后建立符号链接,不过个人建议只动自己的用户目录。