首页
社区
课程
招聘
[原创]消失!消失!再不消失就把你"关掉"!(已添加下载连接)
发表于: 2008-11-10 18:32 18607

[原创]消失!消失!再不消失就把你"关掉"!(已添加下载连接)

2008-11-10 18:32
18607
Unlocker的编程”探险”及工作原理

关键字:文件对象,NT用户态,内核态


    Unlocker是偶写的一个文件解锁小工具,原来GUI用的是C# 2005编写,功能逻辑用的是纯汇编加少量的C语言编写。现在为了不依赖于.Net Framework 平台,CUI用VB6.0重写,而功能逻辑全部用C语言改写。





    VB6对于GUI的快速开发以及”便携绿色化”还是比较优秀的一款工具,虽然他
对漂亮的XP皮肤支持有限(比如一些控件无法XP Skin化),甚至有些人会认为她是
一款早已过时的IDE,但目前来说,她还是可以很好的满足偶的需求,既然可以满
足需求那么足以。


[PART0  : 关于文件解锁方法的浅谈]

    NT下的文件解锁,我知道的主要有3种方法,我分别写了3个函数对应:

extern WINAPI int CloseHandleByDH(DWORD pid,HANDLE hfile);
extern WINAPI int CloseHandleByRT(DWORD pid,HANDLE hfile);
extern WINAPI int CloseHandleByCore(DWORD pid,HANDLE handle);
[LEFT]



这些都是NT下编程中较基本的知识点,相信大多数Coder们看到这心中已经明白了。下面我逐一作简单说明:

1.CloseHandleByDH

使用DuplicateHandle的DUPLICATE_CLOSE_SOURCE 选项,该选项的作用是不但将源
进程中的对象句柄“拷贝”到目标进程(其实是将句柄指向Object的连接添加到目
标进程的对象表中。),而且同时关闭源进程中的对象句柄。这种方法效果还是很
好的,可以unlock大多数文件句柄,只要取得SE_DEBUG特权,甚至连System进程中
一些句柄都可以关闭。很多文件解锁工具用的都是这种方法。

2.CloseHandleByRT

这种方法的原理是向源进程中插入RemoteThread,同时将远线程的入口设置为
CloseHandle,并将源进程中要关闭的句柄传递给它。以前在汇编中我是写了一个
所谓的naked函数,还要在源进程中分配地址空间,然后将naked函数copy过去,最
后在该函数中调用CloseHandle。其实没这么复杂,对于只有一个参数的 ”知名”
API,完全可以用一个CreateRemoteThread搞定。但这种RemoteThread方法效果不
是很好,如果源进程不允许在用户态插入远线程(比如System,smss等),则这种方
法就会失效。RemoteThread效果大大不如第一种方法。

3.CloseHandleByCore

前两种方法对于有些内核对象来说没有效果-------原汤化原食,这时还得从内核
里想办法。所以有了CloseHandleByCore 方法。对于某些内核对象可以简单的在
Ring0中用ZwClose 关闭,然而另一些内核对象称之为PERMANENT对象,这种对象
要先使用ZwMakeTemporaryObject将其“转性”然后再将其关闭(但据偶观察还未见
到File类型的永久对象。)。然而在内核中做动作仍需小心,否则”必蓝”。这个
问题在后面还要提及。

以上列出了关闭句柄的几种方法,还没说如何获得活动文件对象的句柄表。偶用的
还是比较“正统”的NtQuerySystemInformation 方法,该函数返回系统中全部活动对象的信息表,其中每一项结构定义如下:

typedef struct _SYSTEM_HANDLE_INFORMATION {

       ULONG  ProcessId;
       UCHAR  ObjectTypeNumber;
       UCHAR  Flags;
       USHORT  Handle;
       PVOID  Object;
       ACCESS_MASK  GrantedAccess;
}SYSTEM_HANDLE_INFORMATION,*PSYSTEM_HANDLE_INFORMATION;
为了便于VB与C的信息传递,偶定义了相关的OpenFile结构:

typedef struct _OPENED_FILE_INFO
{
        char       ProcessName[MAX_PATH];       //进程名全称
        char       FileName[MAX_PATH];          //文件全名称
        HANDLE      hFile;                      //文件句柄
        DWORD       PID;                        //进程ID
        DWORD       Flags;                      //句柄标志
        DWORD       GrantedAccess;              //句柄访问授权
        PVOID       Object;                     //对象体指针
        int          CurrentIndex;              //当前句柄项在句柄表中的索引
}OPENED_FILE_INFO,*POPENED_FILE_INFO;

VB6中与其定义的结构是:


Type OPENED_FILE_INFO
    ProcessName As String * MAX_PATH
    FileName As String * MAX_PATH
    hfile As Long
    pid As Long
    Flags As Long
    GrantedAccess As Long
    Object As Long
    CurrentIndex As Long
End Type


[PART1  : 一个内核态中的严重漏洞!]

在用户模式(UserMode)中,使用不到Object对象指针,但在内核中往往需要传递
Object去完成某些操作。这本来也无可厚非,但有一个严重的漏洞存在:

在内核中使用该Object时不能确保它是否还处在有效状态!


前面用NtQuerySystemInformation 取得系统句柄表,只是系统在某个时间段里的
“快照”,谁也没有保证这些句柄和对象在后面仍然有效!如果我们在Ring3级中
引用一个失效的句柄,那顶多也就返回无效句柄之类的错误。但在Ring0级则情况
大有不同,在内核态(KernelMode)中引用任何无效的内存都有可能引发“严重问题
”。在编码过程中我发现即使ObReferenceObjectByPointer之类的函数返回 STATUS_SUCCESS ,仍不能确保该对象是一个有效对象,貌似
ObReferenceObjectByPointer 不管三七二十一,只是简单的将对象头
(Object_Header)结构中的PointerCount值加1。经过若干次的“蓝屏”,用
Softice总结如下:

若文件对象的引用计数和句柄计数都为零,则基本上可以确定该对象已不
存在了。为了保险系数更高,我又增加判定第3个条件:对象的Type
字段总为0xBAD0????。通过这3点,则可保证该对象已OVER!不用再处理了!
(其实这也不是所谓的“数学证明”式的保证。虽然在各个系统上2K,
XP-SP2,XP-SP3,2K3-SP2 都没有出问题,但我因未查NT源码,也不敢拍着胸
脯说在各位的系统上不会出问题,如果我的“保证”哪里有错误,请毫不
犹豫的指出,谢谢!)即有:


if(poh->PointerCount == 0 &&\
            poh->u0.HandleCount == 0 &&\
            (DWORD)(poh->Type)>>16 == 0xBAD0)
        {
            DbgPrint("[%s] Bad Object!\n",__func__);
            //__asm("int $3");
        }



[PART2  : 另一个"不成熟"的思路]

(另一个思路:这里说一下另一个思路,如何在内核关闭对象而不用切入到其进程
空间中去?我开始这样想,只要该对象不是Bad Object,则若将其句柄计数置0,
引用计数置1,然后调用ObDereferenceObject(pfo)让对象管理器将它干掉,这正是所谓“狠毒”的“借刀杀人” 一招:


DbgPrint("[%s]Obj : %p ,PointerCount : %u ,"
    "HandleCount : %u ,Flags : %02x\n",\
    __func__,pfo,poh->PointerCount,\
    poh->u0.HandleCount,(UCHAR)(poh->Flags));

poh->u0.HandleCount = 0;
poh->PointerCount = 1;
ObDereferenceObject(pfo);
bSuccess = true;
至于为什么会这样,我想各位即使不看NT内核揭秘之类的书也可以猜出个八九不
离十来。但实际上,这种方法大有问题,我试了几次都“蓝了”。我分析的
原因是(未验证):尽管对象管理器可能将该对象“Free”了,但所有引用该对
象的进程都不知道他们指向该对象的句柄已经失效了,如果再用这些对象的话,
蓝屏也是可想而知的。)

[PART3  : 如何通过文件句柄取得文件名称]

    下面再来聊聊偶是如何通过文件对象句柄得到文件名字的,这个偶开始参考了
网上一些代码,他们无外乎采用2种方法:

    1 使用 NtQueryInformationFile

    2 或者使用 NtQueryObject (如果没记错的话)


貌似这2种方法的调用都在用户态解决战斗(意思是他们都在ntdll.dll中导出,
可以在用户态直接调用,但他们最终要不要进Ring0,则不予考虑。),倒也安全
稳定。其实这其中有点小问题,说小其实也不小,就是他们在枚举某些非命名管
道时(管道在内部也是以文件对象来实现,如果偶没理解错的话,嘿嘿),只有在
该管道有消息到达时才会返回,这就会发生“无限期”等待的问题。

    其实这也好解决,就是将这些函数调用放到一个线程中,然后用
WaitForSingleObject以一个超时来强制其返回,然后终结掉线程。不瞒大家说,
偶开始就是这样实现的,但这样会给进程带来严重的内存泄露!因为
可能NtQueryInformationFile在等待前申请了一块内存,然后等待消息,
这个等待发生在一个Thread中,你在TimeOut时将该线程强行结束,该内存不会
被释放(释放内存的代码得不到执行的机会),即使放在try…finally块中都无
效。这样程序执行几次搜索后,可以看到其占用的物理内存和虚拟内存都直线上
升,虚存“轻易”的就可以突破几百兆,其“后果”可想而知了。

    再有一点:
    用NtQueryInformationFile对于某些加载的OCX 文件只能枚举到空白文件名。
    这一点小缺陷也使得偶“芒刺在背”。      

    So ,偶的最终解决方法是进内核,直接从File_Object中取得文件名称,
这样不会造成任何等待,不会有延时,而且取得的文件名要比前者更准确,
File_Object结构定义如下:


typedef struct _FILE_OBJECT {
    CSHORT  Type;
    CSHORT  Size;
    PDEVICE_OBJECT  DeviceObject;
    PVPB  Vpb;
    PVOID  FsContext;
    PVOID  FsContext2;
    PSECTION_OBJECT_POINTERS  SectionObjectPointer;
    PVOID  PrivateCacheMap;
    NTSTATUS  FinalStatus;
    struct _FILE_OBJECT  *RelatedFileObject;
    BOOLEAN  LockOperation;
    BOOLEAN  DeletePending;
    BOOLEAN  ReadAccess;
    BOOLEAN  WriteAccess;
    BOOLEAN  DeleteAccess;
    BOOLEAN  SharedRead;
    BOOLEAN  SharedWrite;
    BOOLEAN  SharedDelete;
    ULONG  Flags;
    UNICODE_STRING  FileName;
    LARGE_INTEGER  CurrentByteOffset;
    ULONG  Waiters;
    ULONG  Busy;
    PVOID  LastLock;
    KEVENT  Lock;
    KEVENT  Event;
    PIO_COMPLETION_CONTEXT  CompletionContext;
    KSPIN_LOCK  IrpListLock;
    LIST_ENTRY  IrpList;
    PVOID  FileObjectExtension;
} FILE_OBJECT, *PFILE_OBJECT;
取文件名的Ring0代码如下:

DDKAPI DWORD CoreGetFileName(PFILE_OBJECT pfo,PWSTR pwstr)
{
    DWORD dwRet = 0;
    KIRQL oldirql;
    if(!pfo || !pwstr)
    {
        PRINT("[%s]error : pfo == NULL or pwstr == NULL!\n",\
            __func__);
        goto QUIT;
    }
    //__asm("int $3");
    KeRaiseIrql(DISPATCH_LEVEL,&oldirql);
    if(MmIsAddressValid(pfo->FileName.Buffer))
    {
        memcpy(pwstr,pfo->FileName.Buffer,pfo->FileName.Length);
        dwRet = pfo->FileName.Length;
    }
    else
    {
        PRINT("[%s]Memory Address %p is Invalid![pObj:%p]\n",\
                __func__,pfo->FileName.Buffer,pfo);
    }
    KeLowerIrql(oldirql);
QUIT:
    return dwRet;
}
为了“接应”Ring0中的CoreGetFileName,在Ring3种同样要有考虑:

DLLEXP WINAPI int CloseHandleByCore(DWORD pid,HANDLE handle)
{
    int iRet = 0;
    if(!pid || !handle)
    {
        PRINT("[%s]error : pid == 0 or handle == NULL\n",\
            __func__);
        goto QUIT;
    }
    
    byte Buf[1024] = {0};
    DWORD *pbuf = (DWORD*)Buf;
    *pbuf = pid;
    *(pbuf+1) = (DWORD)handle;
    
    if(!CallDrv(&g_shs,IOCTL_Ctl_CloseHnd,Buf,1024,NULL,0))
    {
        PRINT("[%s] CallDrv IOCTL_Ctl_CloseHnd Failed!\n",\
            __func__);
        PrintErr();
        goto QUIT;
    }
    iRet = 1;
    
QUIT:
    return iRet;
}


在关闭了一个对象的句柄后如何确定这一点呢?我写了一个简单的C函数解决:

//检查是否特定进程中的句柄是否存在。
//返回1代表存在,返回0代表不存在。
DLLEXP WINAPI int FindHandle(DWORD pid,HANDLE hfile)
{
    int iRet = 0;
    if(!pid || !hfile)
    {
        PRINT("[%s]error : pid == 0 or hfile == 0\n",\
            __func__);
        goto QUIT;
    }
    
    if(!FlushSysInfoList())
    {
        PRINT("[%s]FlushSysInfoList Failed!\n",__func__);
        goto QUIT;
    }
    
    if(!lpSysInfoList || !FileType)
    {
        PRINT("[%s]error : lpSysInfoList == NULL or FileType == 0",\
            __func__);
        goto QUIT;
    }
    
    int *pcount = (int*)lpSysInfoList;
    int count = *pcount;
    PSYSTEM_HANDLE_INFORMATION pSHI = (PSYSTEM_HANDLE_INFORMATION)(++pcount);
    for(int i = 0;i < count;++i)
    {
        if(pid == pSHI[i].ProcessId && hfile == pSHI[i].Handle &&\
            FileType == (unsigned)(pSHI[i].ObjectTypeNumber))
        {
            iRet = 1;
            break;
        }
    }
QUIT:
    return iRet;
}
[PART4  : 一些其他问题]

取得特定进程的PE文件名相对而且比较容易,同样可以有多种方法,
比如使用Process32First,Process32Next之类的Win32 API 搞定,偶在这里采
用了其他方法:GetProcessImageFileName ,但这个API在2K中不能用,
遂换成GetModuleFileNameEx 解决。




为了便于重用还写了若干Driver加载函数,分别是:

extern bool NewDrv(PSvrHnds pSH);
extern bool DelDrv(PSvrHnds pSH);
extern bool StartDrv(PSvrHnds pSH);
extern bool DelDrvForce(void);
extern bool CallDrv(PSvrHnds pSH,DWORD Ctrl_Code,void *_in,\
    size_t cbin,void *_out,size_t cbout);
他们只是对Win32 服务API的2次包装,故省去解释。

[PART5  : 尾声+乱弹]

    总的来说,这个版本的unlocker比以前用C#写的功能更强大,效率和稳定性
更高,也更安全。汇编固然强大,但用起来也有繁琐的地方,VB6固然“落后”但
也有她方便贴心的一面,而C#固然先进,但我却偏想找找“麻烦”。难道Gcc就没
有不爽的地方了么?有啊!为啥不像VC那样来个naked函数呢?现在还不得用汇编
来做这件苦差啊。所以世上美没有十全十美的语言,也没有十全十美的人,正所
谓:
     人有悲欢离合,月有阴晴圆缺,此事古难全,但愿人长久,千里共婵娟。(严重跑题中…)。

临时下载地址:
http://g.zhubajie.com/urllink.php?id=32400790t31l4iblm80kdxv


                                侯佩|hopy
                             2008-11-10于电心


[/LEFT]

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 0
支持
分享
最新回复 (45)
雪    币: 442
活跃值: (107)
能力值: ( LV9,RANK:350 )
在线值:
发帖
回帖
粉丝
2
估计排版惨不忍睹,各位忍耐着看吧!
2008-11-10 18:35
0
雪    币: 399
活跃值: (38)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
3
还以为是水帖呢
暂时看不懂,先占个位
PS.看到hopy就想到猴皮(受happy影响 )
2008-11-10 18:38
0
雪    币: 207
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
4

一样
原来是技术贴。

不太懂编程的飘过
2008-11-10 18:44
0
雪    币: 354
活跃值: (10)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
5
电心是什么?
2008-11-10 19:16
0
雪    币: 214
活跃值: (46)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
6
这方法根本没意义,参考:

http://www.debugman.com/read.php?tid=2062

删除独占文件还是XCB大法最强
2008-11-10 19:31
0
雪    币: 442
活跃值: (107)
能力值: ( LV9,RANK:350 )
在线值:
发帖
回帖
粉丝
7
去观赏了,不过都说我用的是“正统”方法了。
2008-11-10 19:44
0
雪    币: 342
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
factious sometime is good ,but not me
2008-11-10 20:04
0
雪    币: 442
活跃值: (107)
能力值: ( LV9,RANK:350 )
在线值:
发帖
回帖
粉丝
9
//向你请教问题,还加了你QQ,可知你鸟都不鸟我
我一般很少在QQ上聊天,对所有人都一样,没什么不鸟某个人的。

//想不到你在这里发的这文章。
我在哪发文章是我的事,我想在哪发就在哪发,不用你来多事!

//SE_DEBUG特权System进程你回不来 强行终结掉线程 系统给你弄坏了不说,一会儿别的事就//出现。对于有些流忙软件或安全软件,你这二种就会失败
什么叫进程回不来???关闭System中的对象本来就有风险,我又没有鼓励大家删除System中的对象,再说我的unlocker又不是要和什么安全软件过不去

//对于这第一次不会蓝,第二次蓝,你一但调用你再也无能删除了,要不是蓝,就得重起os
所以说是不成熟的思路,思路而已,想想也错吗?

//你的系统要是用别的杀软或别的软件,不会蓝就是则白平。
不知你为什么老提360,就算360是你写的你也不用这样显摆。360也没什么了不起!你说我的软件在杀软下会蓝,但为什么在瑞星下反复测试没有任何问题呢?

我没有说我用了多么高深的技术,相反我用的都是最基本的东西。
我只是将我的思路说出来与大家分享,你觉得烂是你的事,你不要看就是了,我又没求着你看。
2008-11-10 20:18
0
雪    币: 347
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
10
路过......
2008-11-10 20:23
0
雪    币: 709
活跃值: (2420)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
11
学习学习~~~
我觉得能做华丽的界面+ 驱动,就很牛了.
2008-11-10 21:15
0
雪    币: 7651
活跃值: (523)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
12
楼主做的东西蛮不错啊~现在怎么流行吵架?
2008-11-10 22:21
0
雪    币: 7651
活跃值: (523)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
13
XCB=SCB=SCSI Control Block?仅作猜测…
2008-11-10 22:23
0
雪    币: 86
活跃值: (56)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
14
楼主的帖子很精彩,写的很详细了。其实能做出这样工具级的东西就很强了。
2008-11-10 22:26
0
雪    币: 214
活跃值: (46)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
15
管360什么事?没什么了不起的你给我写一个看看?菜鸟就不要大口气说话,小心被人抽
2008-11-10 22:40
0
雪    币: 347
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
16
继续路过。。。。
2008-11-10 23:25
0
雪    币: 367
活跃值: (20)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
17
应该感谢所有分享出来的朋友.

回haras,大家上班都忙,况且很多人并不开QQ.
2008-11-10 23:40
0
雪    币: 196
活跃值: (135)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
18
始终是赶不上牛人的步伐啊~
2008-11-11 09:14
0
雪    币: 321
活跃值: (271)
能力值: ( LV13,RANK:1050 )
在线值:
发帖
回帖
粉丝
19
有好的思路,尽管还不完善,但还是要鼓励的。
2008-11-11 09:15
0
雪    币: 342
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
factious
2008-11-11 12:08
0
雪    币: 466
活跃值: (175)
能力值: ( LV9,RANK:190 )
在线值:
发帖
回帖
粉丝
21
侯先生真冲。逐条地批驳诘问。人家菜鸟也没怎么着啊。
2008-11-11 12:33
0
雪    币: 233
活跃值: (15)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
22
看来这两天比较流行拌嘴

感谢分享
2008-11-11 13:12
0
雪    币: 214
活跃值: (46)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
23
XCB大法才是无敌的啊
2008-11-11 15:22
0
雪    币: 411
活跃值: (16)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
呵呵,好戏上演了,说实话我看不懂。
2008-11-11 15:38
0
雪    币: 8107
活跃值: (1955)
能力值: ( LV8,RANK:122 )
在线值:
发帖
回帖
粉丝
25
路过 进来看看
2008-11-11 16:28
0
游客
登录 | 注册 方可回帖
返回
//