|
用户名:EmZoo 笔名:EmZoo 地区: 上海 行业:其他 |
| 日 | 一 | 二 | 三 | 四 | 五 | 六 |
深入腹地, 探个究竟
PPC_Linux启动流程及移植(原创)——2、内核引导前传
(作者置顶)
PPC_Linux启动流程及移植(原创)1--zImage的解压缩
(作者置顶)
对于VIVIBootLoader的分析(原创)
(作者置顶)
Build Android for Moto Milestone
代码注入的三种方法
代码注入的三种方法
作者:Robert Kuster
编译:VCKBASE
原文出处:Three
Ways to Inject Your Code into Another Process
下载源代码
简介
本文将讨论如何把代码注入不同的进程地址空间,然后在该进程的上下文中执行注入的代码。 我们在网上可以查到一些窗口/密码侦测的应用例子,网上的这些程序大多都依赖 Windows 钩子技术来实现。本文将讨论除了使用 Windows 钩子技术以外的其它技术来实现这个功能。如图一所示:

图一 WinSpy 密码侦测程序
为了找到解决问题的方法。首先让我们简单回顾一下问题背景。
要“读取”某个控件的内容——无论这个控件是否属于当前的应用程序——通常都是发送 WM_GETTEXT
消息来实现。这个技术也同样应用到编辑控件,但是如果该编辑控件属于另外一个进程并设置了 ES_PASSWORD
式样,那么上面讲的方法就行不通了。用 WM_GETTEXT
来获取控件的内容只适用于进程“拥有”密码控件的情况。所以我们的问题变成了如何在另外一个进程的地址空间执行:
::SendMessage( hPwdEdit, WM_GETTEXT, nMaxChars, psBuffer );
通常有三种可能性来解决这个问题。
第一部分: Windows 钩子
范例程序——参见HookSpy 和HookInjEx
Windows
钩子主要作用是监控某些线程的消息流。通常我们将钩子分为本地钩子和远程钩子以及系统级钩子,本地钩子一般监控属于本进程的线程的消息流,远程钩子是线程专用的,用于监控属于另外进程的线程消息流。系统级钩子监控运行在当前系统中的所有线程的消息流。
如果钩子作用的线程属于另外的进程,那么你的钩子过程必须驻留在某个动态链接库(DLL)中。然后系统映射包含钩子过程的DLL到钩子作用的线程的地址空间。Windows将映射整个
DLL,而不仅仅是钩子过程。这就是为什么 Windows 钩子能被用于将代码注入到别的进程地址空间的原因。
本文我不打算涉及钩子的具体细节(关于钩子的细节请参见 MSDN 库中的 SetWindowHookEx
API),但我在此要给出两个很有用心得,在相关文档中你是找不到这些内容的:
BOOL APIENTRY DllMain( HANDLE hModule,那么会发生什么呢?首先我们通过Windows 钩子将DLL映射到远程进程。然后,在DLL被实际映射之后,我们解开钩子。通常当第一个消息到达钩子作用线程时,DLL此时也不会被映射。这里的处理技巧是调用LoadLibrary通过增加 DLLs的引用计数来防止映射不成功。
DWORD ul_reason_for_call,
LPVOID lpReserved )
{
if( ul_reason_for_call == DLL_PROCESS_ATTACH )
{
// Increase reference count via LoadLibrary
char lib_name[MAX_PATH];
::GetModuleFileName( hModule, lib_name, MAX_PATH );
::LoadLibrary( lib_name );
// Safely remove hook
::UnhookWindowsHookEx( g_hHook );
}
return TRUE;
}
目前只使用了钩子来从处理远程进程中DLL的映射和解除映射。在此“作用于线程的”钩子对性能没有影响。
下面我们将讨论另外一种方法,这个方法与 LoadLibrary 技术的不同之处是DLL的映射机制不会干预目标进程。相对LoadLibrary
技术,这部分描述的方法适用于 WinNT和Win9x。
但是,什么时候使用这个技巧呢?答案是当DLL必须在远程进程中驻留较长时间(即如果你子类化某个属于另外一个进程的控件时)以及你想尽可能少的干涉目标进程时。我在
HookSpy 中没有使用它,因为注入DLL 的时间并不长——注入时间只要足够得到密码即可。我提供了另外一个例子程序——HookInjEx——来示范。HookInjEx
将DLL映射到资源管理器“explorer.exe”,并从中/解除影射,它子类化“开始”按钮,并交换鼠标左右键单击“开始”按钮的功能。
HookSpy 和 HookInjEx 的源代码都可以从本文的下载源代码中获得。
第二部分:CreateRemoteThread 和 LoadLibrary 技术
范例程序——LibSpy
通常,任何进程都可以通过 LoadLibrary API
动态加载DLL。但是,如何强制一个外部进程调用这个函数呢?答案是:CreateRemoteThread。
首先,让我们看一下 LoadLibrary 和FreeLibrary API 的声明:
HINSTANCE LoadLibrary(
LPCTSTR lpLibFileName // 库模块文件名的地址
);
BOOL FreeLibrary(
HMODULE hLibModule // 要加载的库模块的句柄
);
现在将它们与传递到 CreateRemoteThread 的线程例程——ThreadProc 的声明进行比较。
DWORD WINAPI ThreadProc(
LPVOID lpParameter // 线程数据
);
你可以看到,所有函数都使用相同的调用规范并都接受 32位参数,返回值的大小都相同。也就是说,我们可以传递一个指针到LoadLibrary/FreeLibrary 作为到 CreateRemoteThread 的线程例程。但这里有两个问题,请看下面对CreateRemoteThread 的描述:
第一个问题实际上是由它自己解决的。LoadLibrary 和 FreeLibray 两个函数都在 kernel32.dll
中。因为必须保证kernel32存在并且在每个“常规”进程中的加载地址要相同,LoadLibrary/FreeLibray
的地址在每个进程中的地址要相同,这就保证了有效的指针被传递到远程进程。
第二个问题也很容易解决。只要通过 WriteProcessMemory 将 DLL
模块名(LoadLibrary需要的DLL模块名)拷贝到远程进程即可。
所以,为了使用CreateRemoteThread 和 LoadLibrary 技术,需要按照下列步骤来做:
此外,处理完成后不要忘了关闭所有句柄,包括在第四步和第八步创建的两个线程以及在第一步获取的远程线程句柄。现在让我们看一下 LibSpy 的部分代码,为了简单起见,上述步骤的实现细节中的错误处理以及 UNICODE 支持部分被略掉。
HANDLE hThread;假设我们实际想要注入的代码——SendMessage ——被放在DllMain (DLL_PROCESS_ATTACH)中,现在它已经被执行。那么现在应该从目标进程中将DLL 卸载:
char szLibPath[_MAX_PATH]; // “LibSpy.dll”模块的名称 (包括全路径);
void* pLibRemote; // 远程进程中的地址,szLibPath 将被拷贝到此处;
DWORD hLibModule; // 要加载的模块的基地址(HMODULE)
HMODULE hKernel32 = ::GetModuleHandle("Kernel32");
// 初始化szLibPath
//...
// 1. 在远程进程中为szLibPath 分配内存
// 2. 将szLibPath 写入分配的内存
pLibRemote = ::VirtualAllocEx( hProcess, NULL, sizeof(szLibPath),
MEM_COMMIT, PAGE_READWRITE );
::WriteProcessMemory( hProcess, pLibRemote, (void*)szLibPath,
sizeof(szLibPath), NULL );
// 将"LibSpy.dll" 加载到远程进程(使用CreateRemoteThread 和 LoadLibrary)
hThread = ::CreateRemoteThread( hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE) ::GetProcAddress( hKernel32,
"LoadLibraryA" ),
pLibRemote, 0, NULL );
::WaitForSingleObject( hThread, INFINITE );
// 获取所加载的模块的句柄
::GetExitCodeThread( hThread, &hLibModule );
// 清除
::CloseHandle( hThread );
::VirtualFreeEx( hProcess, pLibRemote, sizeof(szLibPath), MEM_RELEASE );
// 从目标进程中卸载"LibSpy.dll" (使用 CreateRemoteThread 和 FreeLibrary)进程间通信
hThread = ::CreateRemoteThread( hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE) ::GetProcAddress( hKernel32,
"FreeLibrary" ),
(void*)hLibModule, 0, NULL );
::WaitForSingleObject( hThread, INFINITE );
// 清除
::CloseHandle( hThread );
第三部分:CreateRemoteThread 和 WriteProcessMemory 技术
范例程序——WinSpy
另外一个将代码拷贝到另一个进程地址空间并在该进程上下文中执行的方法是使用远程线程和 WriteProcessMemory API。这种方法不用编写单独的DLL,而是用 WriteProcessMemory 直接将代码拷贝到远程进程——然后用 CreateRemoteThread 启动它执行。先来看看 CreateRemoteThread 的声明:
HANDLE CreateRemoteThread(如果你比较它与 CreateThread(MSDN)的声明,你会注意到如下的差别:
HANDLE hProcess, // 传入创建新线程的进程句柄
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 安全属性指针
DWORD dwStackSize, // 字节为单位的初始线程堆栈
LPTHREAD_START_ROUTINE lpStartAddress, // 指向线程函数的指针
LPVOID lpParameter, // 新线程使用的参数
DWORD dwCreationFlags, // 创建标志
LPDWORD lpThreadId // 指向返回的线程ID
);
综上所述,我们得按照如下的步骤来做:
ThreadFunc 必须要遵循的原则:
switch( expression ) {
case constant1: statement1; goto END;
case constant2: statement2; goto END;
case constant3: statement2; goto END;
}
switch( expression ) {
case constant4: statement4; goto END;
case constant5: statement5; goto END;
case constant6: statement6; goto END;
}
END:
或者将它们修改成一个 if-else if 结构语句(参见附录E)。
如果你没有按照这些规则来做,目标进程很可能会崩溃。所以务必牢记。在目标进程中不要假设任何事情都会像在本地进程中那样 (参见附录F)。
GetWindowTextRemote(A/W)
要想从“远程”编辑框获得密码,你需要做的就是将所有功能都封装在GetWindowTextRemot(A/W):中。
int GetWindowTextRemoteA( HANDLE hProcess, HWND hWnd, LPSTR lpString );
int GetWindowTextRemoteW( HANDLE hProcess, HWND hWnd, LPWSTR lpString );
参数说明:
hProcess:编辑框控件所属的进程句柄;
hWnd:包含密码的编辑框控件句柄;
lpString:接收文本的缓冲指针;
返回值:返回值是拷贝的字符数;
下面让我们看看它的部分代码——尤其是注入数据的代码——以便明白 GetWindowTextRemote 的工作原理。此处为简单起见,略掉了 UNICODE 支持部分。
INJDATA
typedef LRESULT (WINAPI *SENDMESSAGE)(HWND,UINT,WPARAM,LPARAM);
typedef struct {
HWND hwnd; // 编辑框句柄
SENDMESSAGE fnSendMessage; // 指向user32.dll 中 SendMessageA 的指针
char psText[128]; // 接收密码的缓冲
} INJDATA;
INJDATA 是一个被注入到远程进程的数据结构。但在注入之前,结构中指向 SendMessageA
的指针是在本地应用程序中初始化的。因为对于每个使用user32.dll的进程来说,user32.dll总是被映射到相同的地址,因此,SendMessageA
的地址也肯定是相同的。这就保证了被传递到远程进程的是一个有效的指针。
ThreadFunc函数
static DWORD WINAPI ThreadFunc (INJDATA *pData)
{
pData->fnSendMessage( pData->hwnd, WM_GETTEXT, // Get password
sizeof(pData->psText),
(LPARAM)pData->psText );
return 0;
}
// 该函数在ThreadFunc之后标记内存地址
// int cbCodeSize = (PBYTE) AfterThreadFunc - (PBYTE) ThreadFunc.
static void AfterThreadFunc (void)
{
}
ThradFunc 是被远程线程执行的代码。
范例程序——InjectEx
下面我们将讨论一些更复杂的内容,如何子类化属于另一个进程的控件。
首先,你得拷贝两个函数到远程进程来完成此任务
这里主要的问题是如何将数据传到远程窗口过程 NewProc,因为 NewProc
是一个回调函数,它必须遵循特定的规范和原则,我们不能简单地在参数中传递 INJDATA指针。幸运的是我找到了有两个方法来解决这个问题,只不过要借助汇编语言,所以不要忽略了汇编,关键时候它是很有用的!
方法一:
如下图所示:

在远程进程中,INJDATA 被放在NewProc 之前,这样 NewProc 在编译时便知道 INJDATA 在远程进程地址空间中的内存位置。更确切地说,它知道相对于其自身位置的 INJDATA 的地址,我们需要所有这些信息。下面是 NewProc 的代码:
static LRESULT CALLBACK NewProc(但这里还有一个问题,见第一行代码:
HWND hwnd, // 窗口句柄
UINT uMsg, // 消息标示符
WPARAM wParam, // 第一个消息参数
LPARAM lParam ) // 第二个消息参数
{
INJDATA* pData = (INJDATA*) NewProc; // pData 指向 NewProc
pData--; // 现在pData 指向INJDATA;
// 回想一下INJDATA 被置于远程进程NewProc之前;
//-----------------------------
// 此处是子类化代码
// ........
//-----------------------------
// 调用原窗口过程;
// fnOldProc (由SetWindowLong 返回) 被(远程)ThreadFunc初始化
// 并被保存在(远程)INJDATA;中
return pData->fnCallWindowProc( pData->fnOldProc,
hwnd,uMsg,wParam,lParam );
}
INJDATA* pData = (INJDATA*) NewProc;
这种方式 pData得到的是硬编码值(在我们的进程中是原 NewProc
的内存地址)。这不是我们十分想要的。在远程进程中,NewProc
“当前”拷贝的内存地址与它被移到的实际位置是无关的,换句话说,我们会需要某种类型的“this 指针”。
虽然用 C/C++ 无法解决这个问题,但借助内联汇编可以解决,下面是对 NewProc的修改:
static LRESULT CALLBACK NewProc(那么,接下来该怎么办呢?事实上,每个进程都有一个特殊的寄存器,它指向下一条要执行的指令的内存位置。即所谓的指令指针,在32位 Intel 和 AMD 处理器上被表示为 EIP。因为 EIP是一个专用寄存器,你无法象操作一般常规存储器(如:EAX,EBX等)那样通过编程存取它。也就是说没有操作代码来寻址 EIP,以便直接读取或修改其内容。但是,EIP 仍然还是可以通过间接方法修改的(并且随时可以修改),通过JMP,CALL和RET这些指令实现。下面我们就通过例子来解释通过 CALL/RET 子例程调用机制在32位 Intel 和 AMD 处理器上是如何工作的。
HWND hwnd, // 窗口句柄
UINT uMsg, // 消息标示符
WPARAM wParam, // 第一个消息参数
LPARAM lParam ) // 第二个消息参数
{
// 计算INJDATA 结构的位置
// 在远程进程中记住这个INJDATA
// 被放在NewProc之前
INJDATA* pData;
_asm {
call dummy
dummy:
pop ecx // <- ECX 包含当前的EIP
sub ecx, 9 // <- ECX 包含NewProc的地址
mov pData, ecx
}
pData--;
//-----------------------------
// 此处是子类化代码
// ........
//-----------------------------
// 调用原来的窗口过程
return pData->fnCallWindowProc( pData->fnOldProc,
hwnd,uMsg,wParam,lParam );
}
Address OpCode/Params Decoded instruction
--------------------------------------------------
:00401000 55 push ebp ; entry point of
; NewProc
:00401001 8BEC mov ebp, esp
:00401003 51 push ecx
:00401004 E800000000 call 00401009 ; *a* call dummy
:00401009 59 pop ecx ; *b*
:0040100A 83E909 sub ecx, 00000009 ; *c*
:0040100D 894DFC mov [ebp-04], ecx ; mov pData, ECX
:00401010 8B45FC mov eax, [ebp-04]
:00401013 83E814 sub eax, 00000014 ; pData--;
.....
.....
:0040102D 8BE5 mov esp, ebp
:0040102F 5D pop ebp
:00401030 C21000 ret 0010
这样一来,不管 NewProc 被移到什么地方,它总能计算出其自己的地址。但是,NewProc 的入口点和 “POP ECX”之间的距离可能会随着你对编译/链接选项的改变而变化,由此造成 RELEASE和DEBUG版本之间也会有差别。但关键是你仍然确切地知道编译时的值。
此即为 InjecEx 中使用的解决方案,类似于 HookInjEx,交换鼠标点击“开始”左右键时的功能。
方法二:
对于我们的问题,在远程进程地址空间中将 INJDATA 放在 NewProc 前面不是唯一的解决办法。看下面 NewProc的变异版本:
static LRESULT CALLBACK NewProc(此处 0xA0B0C0D0 只是远程进程地址空间中真实(绝对)INJDATA地址的占位符。前面讲过,你无法在编译时知道该地址。但你可以在调用 VirtualAllocEx (为INJDATA)之后得到 INJDATA 在远程进程中的位置。 编译我们的 NewProc 后,可以得到如下结果:
HWND hwnd, // 窗口句柄
UINT uMsg, // 消息标示符
WPARAM wParam, // 第一个消息参数
LPARAM lParam ) // 第二个消息参数
{
INJDATA* pData = 0xA0B0C0D0; // 虚构值
//-----------------------------
// 子类化代码
// ........
//-----------------------------
// 调用原来的窗口过程
return pData->fnCallWindowProc( pData->fnOldProc,
hwnd,uMsg,wParam,lParam );
}
Address OpCode/Params Decoded instruction因此,其编译的代码(十六进制)将是:
--------------------------------------------------
:00401000 55 push ebp
:00401001 8BEC mov ebp, esp
:00401003 C745FCD0C0B0A0 mov [ebp-04], A0B0C0D0
:0040100A ...
....
:0040102D 8BE5 mov esp, ebp
:0040102F 5D pop ebp
:00401030 C21000 ret 0010
558BECC745FCD0C0B0A0......8BE55DC21000.现在你可以象下面这样继续:
558BECC745FCD0C0B0A0......8BE55DC21000 <- 原来的NewProc (注1)也就是说,你用真正的 INJDATA(注2) 地址替代了虚拟值 A0B0C0D0(注2)。
558BECC745FC00008A00......8BE55DC21000 <- 修改后的NewProc,使用的是INJDATA的实际地址。
何时使用 CreateRemoteThread 和 WriteProcessMemory 技术
与其它方法比较,使用 CreateRemoteThread 和 WriteProcessMemory
技术进行代码注入更灵活,这种方法不需要额外的 dll,不幸的是,该方法更复杂并且风险更大,只要ThreadFunc出现哪怕一丁点错误,很容易就让(并且最大可能地会)使远程进程崩溃(参见附录
F),因为调试远程 ThreadFunc
将是一个可怕的梦魇,只有在注入的指令数很少时,你才应该考虑使用这种技术进行注入,对于大块的代码注入,最好用 I.和II 部分讨论的方法。
WinSpy 以及 InjectEx 请从这里下载源代码。
到目前为止,有几个问题是我们未提及的,现总结如下:
| 解决方案 | OS | 进程 |
| I、Hooks | Win9x 和 WinNT | 仅仅与 USER32.DLL (注3)链接的进程 |
| II、CreateRemoteThread & LoadLibrary | 仅 WinNT(注4) | 所有进程(注5), 包括系统服务(注6) |
| III、CreateRemoteThread & WriteProcessMemory |
仅 WinNT | 所有进程, 包括系统服务 |
最后,有几件事情一定要了然于心:你的注入代码很容易摧毁目标进程,尤其是注入代码本身出错的时候,所以要记住:权力带来责任!
因为本文中的许多例子是关于密码的,你也许还读过 Zhefu Zhang 写的另外一篇文章“Super
Password Spy++” ,在该文中,他解释了如何获取IE 密码框中的内容,此外,他还示范了如何保护你的密码控件免受类似的攻击。
附录A:
为什么 kernel32.dll 和user32.dll 总是被映射到相同的地址。
我的假定:因为Microsoft
的程序员认为这样做有助于速度优化,为什么呢?我的解释是——通常一个可执行程序是由几个部分组成,其中包括“.reloc” 。当链接器创建 EXE
或者
DLL文件时,它对文件被映射到哪个内存地址做了一个假设。这就是所谓的首选加载/基地址。在映像文件中所有绝对地址都是基于链接器首选的加载地址,如果
由于某种原因,映像文件没有被加载到该地址,那么这时“.reloc”就起作用了,它包含映像文件中的所有地址的清单,这个清单中的地址反映了链接器首选
加载地址和实际加载地址的差别(无论如何,要注意编译器产生的大多数指令使用某种相对地址寻址,因此,并没有你想象的那么多地址可供重新分配),另一方
面,如果加载器能够按照链接器首选地址加载映像文件,那么“.reloc”就被完全忽略掉了。
但kernel32.dll 和user32.dll 及其加载地址为何要以这种方式加载呢?因为每一个 Win32
程序都需要kernel32.dll,并且大多数Win32 程序也需要 user32.dll,那么总是将它们(kernel32.dll
和user32.dll)映射到首选地址可以改进所有可执行程序的加载时间。这样一来,加载器绝不能修改kernel32.dll and
user32.dll.中的任何(绝对)地址。我们用下面的例子来说明:
将某个应用程序 App.exe 的映像基地址设置成 KERNEL32的地址(/base:"0x77e80000")或
USER32的首选基地址(/base:"0x77e10000"),如果 App.exe 不是从 USER32 导入方式来使用
USER32,而是通过LoadLibrary 加载,那么编译并运行App.exe 后,会报出错误信息("Illegal System DLL
Relocation"——非法系统DLL地址重分配),App.exe 加载失败。
为什么会这样呢?当创建进程时,Win 2000、Win XP 和Win 2003系统的加载器要检查 kernel32.dll
和user32.dll 是否被映射到首选基地址(实际上,它们的名字都被硬编码进了加载器),如果没有被加载到首选基地址,将发出错误。在
WinNT4中,也会检查ole32.dll,在WinNT 3.51
和较低版本的Windows中,由于不会做这样的检查,所以kernel32.dll 和user32.dll可以被加载任何地方。只有ntdll.dll总是被加载到其基地址,加载器不进行检查,一旦ntdll.dll没有在其基地址,进程就无法创建。
总之,对于 WinNT 4 和较高的版本中
附录B:
/GZ 编译器开关
在生成 Debug 版本时,/GZ
编译器特性是默认打开的。你可以用它来捕获某些错误(具体细节请参考相关文档)。但对我们的可执行程序意味着什么呢?
当打开 /GZ
开关,编译器会添加一些额外的代码到可执行程序中每个函数所在的地方,包括一个函数调用(被加到每个函数的最后)——检查已经被我们的函数修改的
ESP堆栈指针。什么!难道有一个函数调用被添加到 ThreadFunc 吗?那将导致灾难。ThreadFunc
的远程拷贝将调用一个在远程进程中不存在的函数(至少是在相同的地址空间中不存在)
静态函数和增量链接
增量链接主要作用是在生成应用程序时缩短链接时间。常规链接和增量链接的可执行程序之间的差别是——增量链接时,每个函数调用经由一个额外的JMP指令,该指令由链接器发出(该规则的一个例外是函数声明为静态)。这些 JMP 指令允许链接器在内存中移动函数,这种移动无需修改引用函数的 CALL指令。但这些JMP指令也确实导致了一些问题:如 ThreadFunc 和 AfterThreadFunc 将指向JMP指令而不是实际的代码。所以当计算ThreadFunc 的大小时:
const int cbCodeSize = ((LPBYTE) AfterThreadFunc - (LPBYTE) ThreadFunc)你实际上计算的是指向 ThreadFunc 的JMPs 和AfterThreadFunc之间的“距离” (通常它们会紧挨着,不用考虑距离问题)。现在假设 ThreadFunc 的地址位于004014C0 而伴随的 JMP指令位于 00401020。
:00401020 jmp 004014C0那么
...
:004014C0 push EBP ; ThreadFunc 的实际地址
:004014C1 mov EBP, ESP
...
WriteProcessMemory( .., &ThreadFunc, cbCodeSize, ..);将拷贝“JMP 004014C0”指令(以及随后cbCodeSize范围内的所有指令)到远程进程——不是实际的 ThreadFunc。远程进程要执行的第一件事情将是“JMP 004014C0” 。它将会在其最后几条指令当中——远程进程和所有进程均如此。但 JMP指令的这个“规则”也有例外。如果某个函数被声明为静态的,它将会被直接调用,即使增量链接也是如此。这就是为什么规则#4要将 ThreadFunc 和 AfterThreadFunc 声明为静态或禁用增量链接的缘故。(有关增量链接的其它信息参见 Matt Pietrek的文章“Remove Fatty Deposits from Your Applications Using Our 32-bit Liposuction Tools” )
附录D:
为什么 ThreadFunc的局部变量只有 4k?
局部变量总是存储在堆栈中,如果某个函数有256个字节的局部变量,当进入该函数时,堆栈指针就减少256个字节(更精确地说,在函数开始处)。例如,下面这个函数:
void Dummy(void) {
BYTE var[256];
var[0] = 0;
var[1] = 1;
var[255] = 255;
}
编译后的汇编如下:
:00401000 push ebp注意上述例子中,堆栈指针是如何被修改的?而如果某个函数需要4KB以上局部变量内存空间又会怎么样呢?其实,堆栈指针并不是被直接修改,而是通过另一个函数调用来修改的。就是这个额外的函数调用使得我们的 ThreadFunc “被破坏”了,因为其远程拷贝会调用一个不存在的东西。
:00401001 mov ebp, esp
:00401003 sub esp, 00000100 ; change ESP as storage for
; local variables is needed
:00401006 mov byte ptr [esp], 00 ; var[0] = 0;
:0040100A mov byte ptr [esp+01], 01 ; var[1] = 1;
:0040100F mov byte ptr [esp+FF], FF ; var[255] = 255;
:00401017 mov esp, ebp ; restore stack pointer
:00401019 pop ebp
:0040101A ret
sub esp, 0x1000 ; "分配" 第一次 4 Kb注意4KB堆栈指针是如何被修改的,更重要的是,每一步之后堆栈底是如何被“触及”(要经过检查)。这样保证在“分配”(承诺)另一页面之前,当前页面承诺的范围也包含堆栈底。
test [esp], eax ; 承诺一个新页内存(如果还没有承诺)
sub esp, 0x1000 ; "分配" 第二次4 Kb
test [esp], eax ; ...
sub esp, 0x1000
test [esp], eax
注意事项
“每一个线程到达其自己的堆栈空间,默认情况下,此空间由承诺的以及预留的内存组成,每个线程使用 1
MB预留的内存,以及一页承诺的内存,系统将根据需要从预留的堆栈内存中承诺一页内存区域” (参见 MSDN CreateThread >
dwStackSize > Thread Stack Size)
还应该清楚为什么有关 /GS 的文档说在堆栈探针在 Win32 应用程序和Windows NT虚拟内存管理器之间进行谨慎调整。
现在回到我们的ThreadFunc以及 4KB 限制
虽然你可以用 /Gs 防止调用堆栈探测例程,但在文档对于这样的做法给出了警告,此外,文件描述可以用 #pragma check_stack
指令关闭或打开堆栈探测。但是这个指令好像一点作用都没有(要么这个文档是垃圾,要么我疏忽了其它一些信息?)。总之,CreateRemoteThread
和 WriteProcessMemory 技术只能用于注入小块代码,所以你的局部变量应该尽量少耗费一些内存字节,最好不要超过 4KB限制。
为什么要将开关语句拆分成三个以上?
用下面这个例子很容易解释这个问题,假设有如下这么一个函数:
int Dummy( int arg1 )编译后变成下面这个样子:
{
int ret =0;
switch( arg1 ) {
case 1: ret = 1; break;
case 2: ret = 2; break;
case 3: ret = 3; break;
case 4: ret = 0xA0B0; break;
}
return ret;
}
地址 操作码/参数 解释后的指令注意如何实现这个开关语句?
--------------------------------------------------
; arg1 -> ECX
:00401000 8B4C2404 mov ecx, dword ptr [esp+04]
:00401004 33C0 xor eax, eax ; EAX = 0
:00401006 49 dec ecx ; ECX --
:00401007 83F903 cmp ecx, 00000003
:0040100A 771E ja 0040102A
; JMP 到表***中的地址之一
; 注意 ECX 包含的偏移
:0040100C FF248D2C104000 jmp dword ptr [4*ecx+0040102C]
:00401013 B801000000 mov eax, 00000001 ; case 1: eax = 1;
:00401018 C3 ret
:00401019 B802000000 mov eax, 00000002 ; case 2: eax = 2;
:0040101E C3 ret
:0040101F B803000000 mov eax, 00000003 ; case 3: eax = 3;
:00401024 C3 ret
:00401025 B8B0A00000 mov eax, 0000A0B0 ; case 4: eax = 0xA0B0;
:0040102A C3 ret
:0040102B 90 nop
; 地址表***
:0040102C 13104000 DWORD 00401013 ; jump to case 1
:00401030 19104000 DWORD 00401019 ; jump to case 2
:00401034 1F104000 DWORD 0040101F ; jump to case 3
:00401038 25104000 DWORD 00401025 ; jump to case 4
现在,你也许认为出现上述情况只是因为CASE常量被有意选择为连续的(1,2,3,4)。幸运的是,它的这个方案可以应用于大多数现实例子中,只有偏移量的计算稍微有些复杂。但有两个例外:
显然,单独判断每个的CASE常量的话,结果代码繁琐耗时,但使用CMP和JMP指令则使得结果代码的执行就像普通的if-else 语句。
有趣的地方:如果你不明白CASE语句使用常量表达式的理由,那么现在应该弄明白了吧。为了创建地址表,显然在编译时就应该知道相关地址。
现在回到问题!
注意到地址 0040100C 处的JMP指令了吗?我们来看看Intel关于十六进制操作码 FF 的文档是怎么说的:
操作码 指令 描述
FF /4 JMP r/m32 Jump near, absolute indirect,
address given in r/m32
原来JMP 使用了一种绝对寻址方式,也就是说,它的操作数(CASE语句中的 0040102C)表示一个绝对地址。还用我说什么吗?远程 ThreadFunc 会盲目地认为地址表中开关地址是 0040102C,JMP到一个错误的地方,造成远程进程崩溃。
附录F:为什么远程进程会崩溃呢?
当远程进程崩溃时,它总是会因为下面这些原因:
:004014C0 push EBP ; ThreadFunc 的入口点如果 CALL 是由编译器添加的指令(因为某些“禁忌” 开关如/GZ是打开的),它将被定位在 ThreadFunc 的开始的某个地方或者结尾处。
:004014C1 mov EBP, ESP
...
:004014C5 call 0041550 ; 这里将使远程进程崩溃
...
:00401502 ret
不管哪种情况,你都要小心翼翼地使用 CreateRemoteThread 和 WriteProcessMemory 技术。尤其要注意你的编译器/链接器选项,一不小心它们就会在 ThreadFunc 添加内容。
参考资料:
[内核注入]之交换ID[可怕的特性]
牢记邓小平的一句话:到了21世纪,中国就强大了
How to Debug Applets in Java Plug-in
This section covers the following topics:
Debugging Java applets in Java Plug-in has not been simple in the past, partly because the applets use many services and facilities from the Java 2 Runtime Environment, Java Plug-in, and the browser itself. If the applet does not work, the developer needs to spend time diagnosing the problem.
The purpose of this document is to simplify the debugging process. It provides techniques and suggestions for developing applets in Java Plug-in and describes some common mistakes in applet development.
In order to debug applets, you must have the appropriate version of the JDK
installed on your machine. Also make sure to compile
your .java files with -g option with javac.
To begin debugging your applet:
-Djava.compiler=NONE
-Xnoagent
-Xdebug
-Xrunjdwp:transport=dt_shmem,address=
<connection-address>,server=y,suspend=n
The <connection-address> could be any string which is used by the java debugger later to connect to the JVM. For example,
-Djava.compiler=NONE
-Xnoagent
-Xdebug
-Xrunjdwp:transport=dt_shmem,address=2502,server=y,suspend=n
See JPDA Connection and Invocation for the details on the possible runtime parameters for debugging.
-g option with javac.
jdb -attach <connection address>
in a DOS command prompt. <connection address> is the
name mentioned in the step 1. For example, if <connection address>
is 2502, you will run the command as
jdb -attach 2502To learn more about the Java Debugger (jdb), see The Java Debugger.
When debugging applets in Java Plug-in, make sure that only one instance of the browser is being used for debugging using the same connection address at the same time. Otherwise, it will result in a conflict, since the Java Runtime for each instance of the browser will try to gain exclusive access to the connection address. To debug applets in both Internet Explorer and Netscape Navigator, run either Internet Explorer or Netscape Navigator with Java Plug-in—but not both at the same time.
Debugging applets in Java Plug-in with Active Desktop is discouraged because an instance of Internet Explorer will always be running in the desktop process during the lifetime of the user session.
You can use other Java 2 debuggers, like Inprise's JBuilder or Symantac's VisualCafe, instead of jdb. To use these debuggers, you will need to change the project option in these IDEs to attach Java Plug-in in the browser process on the same machine or remote machine. Different Java Runtime Parameters may also be required in the Java Control Panel. For more information, consult your Java 2 debugger or IDE manuals.
One of the most powerful tools in Java Plug-in is the Java
Plug-in Console. It is a simple console window for redirecting all the System.out
and System.err messages. The console window is disabled by
default; it can be enabled from the Java Control Panel or the task bar.
If the console is enabled, you will see the console window appear when Java
Plug-in is used in the browser.
Similar to the Java Plug-in Console, this is a file that records all the System.out
and System.err messages. The trace file is disabled by default
but is automatically enabled when the Java Plug-in Console is enabled. The trace
file is normally located in user.dir, and the file is called .plugin<version>.trace.
For example, in Windows NT this file is located in C:\WINNT\Profiles\<username>\.plugin<version>.trace.
javaplugin.trace property This property controls whether Java Plug-in prints its trace messages during
execution. This is useful to applet developers to determine what is occuring
within Java Plug-in. The possible values are true or false.
By default this property is false. To enable this property, set
-Djavaplugin.trace=true in the Java Runtime Parameters field accessed from the Java tab of the Java Control Panel.
java.security.debug propertyThis property controls whether the security system of the Java 2 Runtime Environment prints its trace messages during execution. This is usful when a security exception is thrown in an applet or when a signed applet is not working. The following options are supported:
access — print all checkPermission
resultsjar —
print jar verification informationpolicy — print policy informationscl —
print permissions SecureClassLoader assigns The following options can be used with access:
stack — include stack tracedomain — dumps all domains in contextfailure — before throwing exception, dump the stack and
domain that didn't have permission For example, to print all checkPermission results and dump all
domains in context, set -Djava.security.debug=access:stack in the
Java Runtime Parameters dialog box access from the Java tab of the Java Control Panel.
Java Plug-in provides a rich set of documentation to help developers use the various features of Java Plug-in. The documentation includes a FAQ, which includes some of the most frequently asked questions by developers. Make sure you read and understand these documents before applet development, as it may save you hundreds of hours in debugging.
Although Java Plug-in provides the Java 2 Runtime Environment within Internet Explorer and Netscape Navigator, most of the facilities are provided by the Java 2 Runtime itself, rather than by Java Plug-in. Therefore, if a problem occurs in Java Plug-in, it may be either a problem in Java Plug-in, the Java 2 Runtime itself or a user error. It is extremely important to determine where bugs originate, as it will affect the speed of bug evaluation and fixing. Here are some suggestions for isolating bugs:
appletviewer. Java Plug-in is mainly derived
from appletviewer and has inherited problems from appletviewer
as well. This step should be performed only if the applet doesn't require
specific browser facilities that Java Plug-in provides, like HTTPS or RSA
signing.appletviewer, it is likely the problem
is in the Java 2 Runtime Environment—and not in Java Plug-in. appletviewer,
it could be either a Java Plug-in problem or user error. Please examine the
applet code to see if it makes any assumptions about the execution environment.
For example, in appletviewer the current directory is set to
the current directory in the shell when appletviewer is launched,
whereas the current directory in Java Plug-in may be set to the browser's
directory. Therefore, loading resources from the current directory may work
in appletviewer but not in Java Plug-in.To submit a bug report, go to the Java Development Connection's BugParade. Before submitting a bug, search the BugParade to determine if the bug has already been reported. In some cases, a workaround may also have been suggested. If the bug is not already reported, submit a new bug report to the Java Plug-in team. In the bug report, include the following information:
appletviewer;To submit a feature request, do so through the Report A Bug or Request a Feature page.In the feature request, please make sure the following information is included:
The purpose of the Java Plug-in Feedback alias, java-plugin-feedback@sun.com, is for customers to provide feedback on product features and the product in general. This alias is not intended for bug report submission. To submit a bug report, please follow the instructions given above.
DIY资料FTP地址公布

上海社保之粗略定量分析
| 退休时的养老金 | = | 基础养老金 | + | 个人帐户养老金 |
其中,"个人帐户养老金"来自个人帐户,"基础养老金"来自统筹部分。
?
"个人帐户养老金"的大概规则(是否享受利息不清楚)为:
假设在x岁退休,当地平均寿命为y岁,退休时个人帐户中已累积有z元(未承诺利率水平)
那么,这个部分每月可以领取 z/((y-x)*12)元。
按照当前标准,基数为6705,个人每月交536.4,那么连续交30年后,假设60岁退休,平均寿命是80,那么每月可以领取536.4*12*30/20/12 = 804.6。按照2%的年复利(姑且不论目前社保基金的年收益),每个月可领取536.4*12*(1-1.02^30)/(1-1.02)/20/12=1088,每个月可以多领取约25%,这还是不计领取保险金期间的收益的情况下。
所以,个人帐户到底可以享受如何的利率水平很值得关注。
?
再来看统筹部分的规则:
| 基础养老金 | = | (当地上年度在岗职工月平均工资 + 本人指数化月平均工资)/2 | * | (缴费年数/100) |
RTTI解释
驱动开发资源列表
SoftICE (DriverStdio)下载地址
Kernel System Calls
Write a Linux Hardware Device Driver
VoIP通讯协议及4大术语
StarDict词库
编译FC5内核源代码
visual slickedit 11 11.0.1 11.0.2的破解
哈哈,终于在Linux下成功驱动D-Link DWL G-122.
Linux下面的socket编程,如何获得本机IP地址
ViewML交叉编译
./configure --with-microwin=[Microwindows dir] --with-fltk=[FLTK dir] --host=[prefix]Microwindows dir 是microwindws 所在目錄
Specifying the System Typeconfig.sub 是...,看了後,好像要設arm-linux :
There may be some features configure can not figure out automatically, but needs to determine by the type of host the package will run on. Usually configure can figure that out, but if it prints a message saying it can not guess the host type, give it the `--host=TYPE' option. TYPE can either be a short name for the system type, such as `sun4', or a canonical name with three fields:
CPU-COMPANY-SYSTEM
See the file `config.sub' for the possible values of each field. If `config.sub' isn't included in this package, then this package doesn't need to know the host type.
If you are building compiler tools for cross-compiling, you can also use the `--target=TYPE' option to select the type of system they will produce code for and the `--build=TYPE' option to select the type of system on which you are compiling the package.
./configure --target=arm-linuxbuild 出來後,看看Makefile,好像沒又設對gcc。所以用:<>CC=arm-elf-gcc ./configure --target=arm-linux卻看到 cannot find host,所以host也要指定。因為不知道要指定那一種所以用./configure看看...host system type... i686-pc-linux-gnu。
CC=arm-elf-gcc ./configure --target=arm-linux --host=i686-pc-linux-gnu試試..但是output中checking for gcc 還是gcc,不是arm-elf-gcc,所以check cross-compiler 還是no.
$ echo ${CC-cc}
arm-elf-gcc這個是正確的。$ makeError :
Making all in xmltok是share library的問題?
arm-elf-gcc -shared -Wl,-soname -Wl,libxmltok.so.0 -o .libs/libxmltok.so.0.1.0 xmlrole.lo xmltok.lo -lc
/home/charles/sigma/armutils_2.5.91.0/toolchain/lib/gcc-lib/arm-elf/2.95.3/libc.a(__uClibc_main.o): In function `__uClibc_start_main':
__uClibc_main.o(.text+0xf8): undefined reference to `main'
collect2: ld returned 1 exit status
./configure --host=i686-pc-linux-gnu --prefix=~/libwww --disable-shared 2>&1 | tee outputOK!! 所以應該是shared library的問題。
CC=arm-elf-gcc CXX=arm-elf-g++ ./configure --host=i686-pc-linux-gnu --target=arm-linux --prefix=/home/charles/magsi/libwww --disable-shared 2>&1 | tee output就會發生...
configure: error: can not run test program while cross compilingGoogle的結果,這一篇似乎有一個解決的方法...
ac_cv_sizeof_char=1找一下所有這個message出現的地方,修改對應參數到正確的值...
因為資質弩鈍,所以有些type不知道,像arm的long type size是....?分別印出
所以只好真的build一個test program在taget board 上跑來試試..
使用的command是arm-elf-gcc trysize.c -o trysize -Wl,-elf2flt="-s32768"
輸出的結果是:
#include
#include
#include
int isbigendian(void)
{
union
{
long l;
char c[sizeof(long)];
} u;
u.l=1;
return u.c[sizeof(long) -1] == 1;
}
int charisunsigned(void)
{
volatile char c= 255;
return c <0;
}
main()
{
printf("char:%d\n",sizeof(char));
printf("char*:%d\n",sizeof(char*));
printf("int:%d\n",sizeof(int));
printf("long:%d\n",sizeof(long));
printf("time_t:%d\n",sizeof(time_t));
printf("size_t:%d\n",sizeof(size_t));
printf("bigendian:%d\n",isbigendian());
printf("charisunsigned:%d\n",charisunsigned());
printf("double:%d\n",sizeof(double));
printf("long double:%d\n",sizeof(long double));
}
char:1實際上還是不知道long_double這一個選項是要設成yes還是no...
char*:4
int:4
long:4
time_t:4
size_t:4
bigendian:0
charisunsigned:0
double:8
long double:8
$ CXX=arm-elf-g++OK。正確check出cross compile。
$ CC=arm-elf-gcc
$ ./configure --host=i686-pc-linux-gnu --preifx=/home/charles/libflnx
$ makeError !!!
=== making fluid ===這個....
make[1]: Entering directory `/home/charles/sigma/flnx/fluid'
.....
make[1]: *** No rule to make target `../lib/libfltk.a', needed by `fluid'. Stop.
make[1]: Leaving directory `/home/charles/sigma/flnx/fluid'
=== making test ===
make[1]: Entering directory `/home/charles/sigma/flnx/test'
make[1]: *** No rule to make target `../lib/libfltk.a', needed by `valuators'. Stop.
make[1]: Leaving directory `/home/charles/sigma/flnx/test'
make: *** [all] Error 2
但是還是一樣的錯。
./configure --host=i686-pc-linux-gnu --with-microwin=/home/charles/magsi/armutils_2.5.91.0/build_arm/src/microwin/src
-I/home/charles/magsi/armutils_2.5.91.0/build_arm/STLport-4.5.3/stlportOK。但是出現putenv( ) undeclare!!!。
-I/home/charles/sigma/armutils_2.5.91.0/build_arm/uClibc -0.9.26/include再build.. 還是有錯,說是filename_list.cxx中有一個cast不正確。到 source file中去看。是platform dependent code。在uclinux下應該要用linux 的code吧,需要define "linux",所以再到makeinclude中加入..
-DlinuxOK!! src folder build OK.
internal error--unrecognizable insn:Google 一下...
$ arm-elf-ar t libm列出libm中所有module,沒有s_rint.o <-這是rint()所在module。
-Wl,-elf2flt="-s32768".rebuild,注意exe file要chmod a+x 才能執行。
Microwindows在基于单片机嵌入式系统中的移植
Framebuffer编程How-to
准备移植一个Nano + viewml到EM8621L上面(原创)
ftp://ftp.pixil.org/pub/pixil/
ftp://microwindows.censoft.com/pub/microwindows/
http://www.microwindows.org/Nano-XTutorial.html
http://www.edaboard.com/forum90.html
嵌入式GUI
Microwindows由Century Software的CEO Greg Haerr主持开发的一个公开源码(LGPL)的项目。Microwindows致力于为一些小型设备和平台提供现代图形窗口环境。Microwindows支持许多硬件平台,移植性很强。Microwindows的主要目的之一便是运行在嵌入式Linux上,并且提供了基于Win32/X的两套API接口。
http://www.microwindows.org
http://microwindows.org
MiniGUI由原清华大学教师魏永明先生开发,是中国人做的得较好的自由软件之一。MiniGUI 是一种面向嵌入式系统或者实时系统的公开源码(LGPL)的图形用户界面支持系统。它主要运行于Linux控制台,实际可以运行在任何一种具有POSIX线程支持的POSIX兼容系统上。
http://www.minigui.org
飞漫软件的MiniGUI项目
http://www.minigui.com/company/cindex.shtml
Qt/Embedded是著名的QT库开发商Trolltech正在进行的面向嵌入式系统的QT版本。Qt/Embedded对于各种硬件接口到GUI工具包提供了完整的图形栈。Qt/Embedded的API同Qt/X11和Qt/Windows的相同,但它并不是基于X11库的。Qt/Embedded是公开源码(LGPL)项目。
http://www.trolltech.com
OpenGUI基于一个用汇编实现的x86图形内核,提供了一个快速的、32位的、高层的C/C++图形接口。OpenGUI也是一个公开源码(LGPL)项目。OpenGUI提供了二维绘图原语,消息驱动的API和BMP文件格式支持。
http://www.tutok.sk/fastgl
PicoGUI是一个可以工作在包括手持式设备等各种硬件上的小型的、可移植的、基于客户/服务器结构的GUI。同X Window系统一样,它具有客户—服务器结构的灵活性,但又不同于X Window系统,它将字体、BMP文件、控件以及一些应用程序所需要的其它的一些资源直接集成在服务器。虽然减少了系统的灵活性,但在速度上有了很大的提高,并且减小了程序大小。
http://picogui.org
Tiny-X是一个为嵌入式系统而开发的紧缩型的X Window服务器。它由SuSE赞助,由XFree86的核心成员Keith Packard开发。Tiny-X的目标是可以在小内存或几乎无内存的情况下良好运行。
http://www.pps.jussieu.fr/~jch/software/kdrive.html
PIXIL提供嵌入式在高级因特网中应用程序的应用。它虽然是为商业化准备的,但它提供GPL协议下的版本,区别是没有技术支持。
http://www.pixil.org
NxZilla - Mozilla on NanoX
http://nxzilla.sourceforge.net
Simple DirectMedia Layer is a cross-platform multimedia library designed to provide low level access to audio, keyboard, mouse, joystick, 3D hardware via OpenGL, and 2D video framebuffer. It is used by MPEG playback software, emulators, and many popular games, including the award winning Linux port of "Civilization: Call To Power."
http://www.libsdl.org/index.php
GtkFB: GTK+ for the Linux Framebuffer
http://www.linuxdevices.com/articles/AT9024868021.html