用《Windows核心编程》的例子,成功挂接printf。我用Stud_PE工具在MSVCR90D.dll里轻松找到了printf,然后按照《Windows核心编程》的示例,很容易就对其进行了进程内的API挂接。现在想挂接 cout << "123" 这个<<操作符,问题就很多了。首先,由于名字改编,在Stud_PE工具里看到的函数名很难找,比如我找到一个“??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z”,但是根本无法确定它是不是就是那个<<操作符的函数。还有一个问题就是我用来替换的函数的函数指针怎么声明?这里的<<操作符声明是这样的——template<class _Elem, class _Tr>
basic_ostream<_Elem, _Tr>& operator<<(
basic_ostream<_Elem, _Tr>& _Ostr,
const char *_Str
);这种操作符函数的函数指针用typedef应该怎样写?谢谢大家!
basic_ostream<_Elem, _Tr>& operator<<(
basic_ostream<_Elem, _Tr>& _Ostr,
const char *_Str
);这种操作符函数的函数指针用typedef应该怎样写?谢谢大家!
解决方案 »
- 大家都用xml做什么?
- 单文档mfc程序添加树形菜单?
- 大家看看,一个简单的窗口程序,编译连接生成都没有问题,为什么就是得不到正确的结果?
- 已知进程名,怎么获得他的handle,并发送点击鼠标事件 ?
- 请教:没有网络的情况下如何使用ado连接sql2000,在线等待
- 怎样解决闪的问题??
- 如何取得打印作业的所属的打印机名?
- 完成端口,udp方式,怎样获知对方的IP地址?
- 关于论坛规则:有人得一个三角形,有人得五个三角形,还有人得一个红星,还有人得两个红星,这是按照什么规则分类的?以信誉值,还是以分
- 急求基于dog算子的图像塔形分解的代码
- 请问一下RGB或十六进制如何转成HSL
- 一段资料书上的opengl入门实例,运行时只是黑屏,没有效果,急,帮忙看看,感激不尽
这是Win32 API,是微软提供给用户的编程接口,而不是将数据打印到控制台的底层函数;一个有 cout << ... 的exe里,函数导入部分根本就不需要有WriteConsole的。
比如C++运行库里的_beginthreadex()最终也是调用CreateThread()来实现的。
_beginthreadex()只是把CreateThread()封装了一下。你基本概念没弄明白.
对应的是
class std::basic_ostream<char,struct std::char_traits<char> > & __thiscallstd::basic_ostream<char,struct std::char_traits<char> >::operator<<(class std::basic_ostream<char,struct std::char_traits<char> > & (__cdecl*)(class std::basic_ostream<char,struct std::char_traits<char> > &))我这里静态链接的时候明明导入了WriteConsoleA WriteConsoleW
MSVCP90D.dll 中的
??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@H@Z
限于水平,没能完全看懂你说的。我这个工程是Win32工程,使用标准 Windows 库,我是用Stud_PE工具查看exe的导入表的kernel32.dll模块,没找到WriteFile。
请问你是用什么工具看到的呢?我用《Windows核心编程》的代码来调试,枚举了exe的导入表里kernel32.dll模块的函数,也没发现哦。。
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine); HookFunc(); cout << "123"; DialogBox(hInstance, MAKEINTRESOURCE(IDD_MAINDLG), NULL, MainDlgProc); return 0;
}typedef BOOL (WINAPI *PWRITECONSOLE)(
__in HANDLE hConsoleOutput,
__in const VOID* lpBuffer,
__in DWORD nNumberOfCharsToWrite,
__out LPDWORD lpNumberOfCharsWritten,
LPVOID lpReserved
);bool HookFunc(void)
{
PWRITECONSOLE pfnOrig = (PWRITECONSOLE)GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "WriteConsoleA");
HMODULE hmodCaller = GetModuleHandle(_T("TestProj.exe")); ReplaceIATEntryInOneMod("kernel32.dll", pfnOrig, MyWriteConsole, hmodCaller); return true;
}// API挂接
void ReplaceIATEntryInOneMod(PCSTR pszCalleeModName, PWRITECONSOLE pfnCurrent, PWRITECONSOLE pfnNew, HMODULE hModCaller)
{
ULONG ulSize;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)
ImageDirectoryEntryToData(hModCaller,TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT,&ulSize);
if (pImportDesc == NULL)
{
return;
}
for (;pImportDesc->Name;pImportDesc++)
{
PSTR pszModName = (PSTR)((PBYTE)hModCaller + pImportDesc->Name);
if (lstrcmpiA(pszModName,pszCalleeModName) == 0)
{
break;
}
}
if (pImportDesc->Name == 0)
{
return;
}
PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)((PBYTE) hModCaller+ pImportDesc->FirstThunk);
for (;pThunk->u1.Function;pThunk++)
{
PWRITECONSOLE * ppfn = (PWRITECONSOLE*) &pThunk->u1.Function;//我在这里下断点,一个一个看,没看到WriteFile
BOOL fFound = (*ppfn == pfnCurrent);
if (fFound)
{
DWORD dwRet = 0;
BOOL bRet = WriteProcessMemory(GetCurrentProcess(),ppfn,&pfnNew,sizeof(pfnNew),&dwRet);
return;
}
}
}
akirya,请问你是用DEPENDS.exe吗?我也看了,貌似对于一个dll,它右边会分上下两个窗口,只有上面是导入部分的,而下面应该是该dll的全部导出函数。我猜想是这样的。我用DEPENDS,在上面的窗口也没找到WriteFile,在下面的窗口才看到了。
用的是WriteConsoleA WriteConsoleWVC自带的 undname.exe
注意到cout<<"123";所使用的操作符(VS2010中)是
template<class _Traits> inline
basic_ostream<char, _Traits>& operator<<(
basic_ostream<char, _Traits>& _Ostr,
const char *_Val)
{
...
}
这个是一个inline函数,每次调用都应该会展开,没有固定地址,是不可寻址的,所以没有办法挂接。解决办法:
办法1:
直接使用c++重载机制,在一个全局头文件(比如stdafx.h)中定义
std::ostream& operator<<(std::ostream& _Ostr, const char *_Val) {
printf("hello %s", _Val);
return _Ostr;
}
这样就可以将头文件范围内的操作符重写了。
方法2:#include <iostream>
#include <vector>
#include <algorithm>std::ostream& operator<<(std::ostream& _Ostr, const char *_Val) {
printf(_Val);
return _Ostr;
}typedef std::ostream& (*pOpt)(std::ostream&, const char *);pOpt f = operator <<;int main() {
std::cout<<"hello"<<std::endl;
printf("0x%x\n", f);
exit(0);
return 0;
}方法3:
挂接windows的API,有WriteConsoleA/WriteConsoleW和WriteFileA/W系列函数。
被挂接程序代码如下:#include "stdafx.h"
#include <stdio.h>
#include <iostream>
#include <Windows.h>
using namespace std;int _tmain(int argc, _TCHAR* argv[])
{
int num = 0;
for (;;)
{
printf("Hello printf,%d\n", num);
cout<<"Hi cout"<<num<<"\n";
num++;
Sleep(1000*2);
}
return 0;
}
hook dll代码如下:#include "stdafx.h"
#include <detours.h>#pragma comment(lib,"detours.lib")
#pragma comment(lib,"detoured.lib")BOOL (WINAPI *Real_WriteConsoleA)(
HANDLE hConsoleOutput, // handle to screen buffer
CONST VOID *lpBuffer, // write buffer
DWORD nNumberOfCharsToWrite, // number of characters to write
LPDWORD lpNumberOfCharsWritten, // number of characters written
LPVOID lpReserved // reserved
) = WriteConsoleA;BOOL WINAPI My_WriteConsoleA(
IN HANDLE hConsoleOutput,
IN CONST VOID *lpBuffer,
IN DWORD nNumberOfCharsToWrite,
OUT LPDWORD lpNumberOfCharsWritten,
IN LPVOID lpReserved
)
{
OutputDebugString("--------- Begin My_WriteConsoleA ---------");
OutputDebugString((LPCSTR)lpBuffer);//printf正常显示,cout乱码
OutputDebugString("--------- End My_WriteConsoleA ---------");
return TRUE;
}
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
if (DLL_PROCESS_ATTACH==ul_reason_for_call)
{
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach((PVOID*)&Real_WriteConsoleA,My_WriteConsoleA);
DetourTransactionCommit();
}
else if (DLL_PROCESS_DETACH==ul_reason_for_call)
{
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach((PVOID*)&Real_WriteConsoleA,My_WriteConsoleA);
DetourTransactionCommit();
}
return TRUE;
}
最终目的就是学习。想知道如何挂接 cout << ... 这些经过名字改编,而且是一个操作符的函数。
原来如此。。谢谢啦!乱码的问题,会不会是挂错函数啦?我没用过Detours,不知道需不需要找到 cout << ... 的地址?还是要提供它在dll里的名字?请问你是如何找的?
不会挂错函数的,如果错的话,循环中必然会有输出内容,但是事实是,Hook之后循环中的printf和cout都没有输出了。想知道程序最终调用了哪些API,反汇编吧。
另外推荐给你个API Hook辅助工具API Monitor,很不错的。
{
__asm{
add [esp+4],3
jmp Next
}}int main()
{
ostream&(ostream::*mptr_int)(int);
mptr_int=cout.operator<<;
void*addr_int=*reinterpret_cast<void**>(&mptr_int);
cout<<hex<<"address is "<<addr_int<<dec<<endl;
void*pcout=&cout;
__asm {
mov ecx,pcout
push 123
call addr_int
}
cout<<endl;
Next=RedirectProcedure(Callback,addr_int);
cout<<1333<<endl;
getch();
return 0;
}vc6 multithreaded输出
address is 00401230
123
1336
vc6 multithreaded dll输出
address is 76004571
123
1336
basic_ostream<char, _Traits>& __CLRCALL_OR_CDECL operator<<(
basic_ostream<char, _Traits>& _Ostr,
const char *_Val)
成员函数是属于类的,不是属于对象的
mptr_int=cout.operator<<;error C3867: “std::basic_ostream<_Elem,_Traits>::operator <<”: 函数调用缺少参数列表;请使用“&std::basic_ostream<_Elem,_Traits>::operator <<”创建指向成员的指针“成员函数是属于类的,不是属于对象的”
所以应该是这样——ostream&(ostream::*mptr_int)(int);
mptr_int=&(ostream::operator<<);(*^__^*) 嘻嘻……
对了,话说全局的那个怎么获取地址呢?怎么写也写不对的——template<class _Traits> inline
basic_ostream<char, _Traits>& __CLRCALL_OR_CDECL operator<<(
basic_ostream<char, _Traits>& _Ostr,
const char *_Val)
你用的什么编译器?
我这里mptr_int=&ostream::operator<<;和mptr_int=cout.operator<<;是可以通过的
“成员函数是属于类的,不是属于对象的”,编译器当然知道cout.operator<<就是那个类的
你是不是找错地方了,
我这里的是
_Myt& operator<<(int _X)
{iostate _St = goodbit;
const sentry _Ok(*this);
if (_Ok)
{const _Nput& _Fac = _USE(getloc(), _Nput);
fmtflags _Bfl = flags() & basefield;
long _Y = (_Bfl == oct || _Bfl == hex)
? (long)(unsigned int)_X : (long)_X;
_TRY_IO_BEGIN
if (_Fac.put(_Iter(rdbuf()), *this,
fill(), _Y).failed())
_St |= badbit;
_CATCH_IO_END }
setstate(_St);
return (*this); }
直接在类定义里面
template<class _Traits> inline
basic_ostream<char, _Traits>& __CLRCALL_OR_CDECL operator<<(
basic_ostream<char, _Traits>& _Ostr,
const char *_Val)
{ // insert NTBS into char stream
typedef char _Elem;
typedef basic_ostream<_Elem, _Traits> _Myos;
ios_base::iostate _State = ios_base::goodbit;
streamsize _Count = (streamsize)_Traits::length(_Val); // may overflow
streamsize _Pad = _Ostr.width() <= 0 || _Ostr.width() <= _Count
? 0 : _Ostr.width() - _Count;
const typename _Myos::sentry _Ok(_Ostr); if (!_Ok)
_State |= ios_base::badbit;
else
{ // state okay, insert
_TRY_IO_BEGIN
if ((_Ostr.flags() & ios_base::adjustfield) != ios_base::left)
for (; 0 < _Pad; --_Pad) // pad on left
if (_Traits::eq_int_type(_Traits::eof(),
_Ostr.rdbuf()->sputc(_Ostr.fill())))
{ // insertion failed, quit
_State |= ios_base::badbit;
break;
} if (_State == ios_base::goodbit
&& _Ostr.rdbuf()->sputn(_Val, _Count) != _Count)
_State |= ios_base::badbit; if (_State == ios_base::goodbit)
for (; 0 < _Pad; --_Pad) // pad on right
if (_Traits::eq_int_type(_Traits::eof(),
_Ostr.rdbuf()->sputc(_Ostr.fill())))
{ // insertion failed, quit
_State |= ios_base::badbit;
break;
}
_Ostr.width(0);
_CATCH_IO_(_Ostr)
} _Ostr.setstate(_State);
return (_Ostr);
}
你的代码我没找到呢。估计我们是不同平台,哈哈。this是一个指针,而这个函数的第一个参数是一个引用呢;另外,“成员函数 == 全局函数 + this指针”应该是C++内部实现的吧,我们平时写成员函数是不需要写上this指针参数的呢。所以我很怀疑,我这边的实现是用全局函数的。“cout”对象才是这个函数的第一个参数。很奇怪啊,不同平台有这么大的差别?!