用《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应该怎样写?谢谢大家!

解决方案 »

  1.   

    cout和printf完全不同, 它是一个ostream到 stdout的对象. 通过运算符重载来封装的.你可以看看Detours , 里面有对成员函数hook的实现.  如果你要得到stdout,你可以用重定向
      

  2.   

    貌似这里的<<不是ostream的成员函数,而是全局的操作符函数,这个函数的第一个参数才是ostream对象,是这样吧?重定向我只知道到文件的,请问有重定向到一个buffer的方法吗?
      

  3.   

    HOOK WriteConsole()函数应该可以。
      

  4.   


    这是Win32 API,是微软提供给用户的编程接口,而不是将数据打印到控制台的底层函数;一个有 cout << ... 的exe里,函数导入部分根本就不需要有WriteConsole的。
      

  5.   

    但是VC运行库还是要调用WIN32的API的。
      

  6.   

    但是用到 cout << ... 的exe的导入表里面没有WriteConsole哦,这个又怎么解释呢?个人觉得,ostream这些应该是C++函数库里的东东,感觉跟平台相关性不大啊,估计不会太依赖Windows的API。
      

  7.   

    这个是c++语言的标准输入输出,又不是系统api,挂个大头哦
      

  8.   

    我觉得应该能挂的,printf已经成功挂接了,printf同样不是Windows API。exe的MSVCP90D.dll模块的函数导入表隐约能看见cout和ostream等等相关的函数原型,关键是找到哪个对应哪个就可以挂接了。不知道函数的名字改编有没有一些什么固定的规则可循?
      

  9.   

    C++的运行库也只是一个中间层,最终还是要调用各个平台的API的。
    比如C++运行库里的_beginthreadex()最终也是调用CreateThread()来实现的。
    _beginthreadex()只是把CreateThread()封装了一下。你基本概念没弄明白.
      

  10.   

    好吧,可能是我没弄明白,我暂时还没看过这方面的书。但是WriteConsole()在exe的导入表里面没有呢,挂接又何从挂起?我已经写了代码调试了一下,在kernel32.dll和user32.dll模块里确实没有轮询到WriteConsole()这个函数。估计这个不是把字符打印到控制台的底层函数吧?作了进一步尝试,我把WriteConsole()函数加进代码里,导入表果然就有它了。这应该能证实 cout << ... 没调用WriteConsole()吧
      

  11.   

    运行库用WriteFile,不用WriteConsole
      

  12.   

    cout写数据最终应该调用的API WriteFile吧
      

  13.   

    今天回来公司看了一下,发现函数导入表的kernel32.dll模块里面也没有WriteFile()哦。另外,话说大家怎么都不关注我的原始问题 - -
      

  14.   

    你看的是哪个的导入表,如果你的程序用的dll版运行库是不会有的
      

  15.   

    ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z
    对应的是
     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
      

  16.   

    动态链接运行库的话则是
    MSVCP90D.dll 中的 
    ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@H@Z
      

  17.   


    限于水平,没能完全看懂你说的。我这个工程是Win32工程,使用标准 Windows 库,我是用Stud_PE工具查看exe的导入表的kernel32.dll模块,没找到WriteFile。
    请问你是用什么工具看到的呢?我用《Windows核心编程》的代码来调试,枚举了exe的导入表里kernel32.dll模块的函数,也没发现哦。。
      

  18.   

    工程不知道怎么发上来哦,我就发一些关键代码吧
    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,在下面的窗口才看到了。
      

  19.   

    还有啊,akirya,请问你是怎么找到??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z对应的是哪个函数的呢?
      

  20.   

    我又没说用到了WriteFile
    用的是WriteConsoleA WriteConsoleWVC自带的  undname.exe
      

  21.   

    哦,undname.exe,不会用 - -  不过我在DEPENDS.exe也没找到WriteConsole的导入。
      

  22.   

    函数挂接,要求函数是可寻址的。得到函数的地址至关重要。
    注意到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系列函数。
      

  23.   

    感谢林的详细解答!方法基本上应该都是对的,但就可惜我都用不上T_T方法0:还真没注意这是个inline函数,MSDN根本没写上inline,我看源码才知道。不过从导入表看来,有些改编过的函数名字真的很像这个 cout << ... 哦,估计是inline申请没有被编译器“审批”通过?不过关键问题啊,就是找不到哪个名字对应哪个函数唉。方法1:可惜我的工程不能用预编译头,因为我的工程是把一个老工程重新包装成新工程,而老工程没有预编译头的。方法2:这个方法获取的函数指针好像是指向自己定义的 << 函数。所以还是必须写在预编译头里来解决T_T。方法3:还是那句话喽,没在导入表找到这些函数,无从挂起- -
      

  24.   

    楼主可以挂接进程内所有模块导入表中的WriteConsoleA/W和WriteFileA/W,也可以使用detours挂接这些函数。
      

  25.   

    所有模块?我再找了一下,发现用DEPENDS.exe查看,可以发现kernel.dll会导入ntdll.dll?而ntdll.dll里面的NtWriteFile难道就是我要找的东东?不过可惜啊,微软好像对ntdll.dll的东东比较保密,MSDN没找到NtWriteFile的说明。我公司只能上有限的网站,比如CSDN。我今晚回去上网看看能不能找到这个函数的原型。
      

  26.   

    我使用detours Hook WriteConsoleA可以成功挂接printf和cout,但是发现在自定义My_WriteConsoleA函数里OutputDebugString((LPCSTR)lpBuffer);//printf正常显示,cout乱码,请大家讨论下啥原因。
    被挂接程序代码如下:#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;
    }
      

  27.   


    最终目的就是学习。想知道如何挂接 cout << ... 这些经过名字改编,而且是一个操作符的函数。
    原来如此。。谢谢啦!乱码的问题,会不会是挂错函数啦?我没用过Detours,不知道需不需要找到 cout << ... 的地址?还是要提供它在dll里的名字?请问你是如何找的?
      

  28.   


    不会挂错函数的,如果错的话,循环中必然会有输出内容,但是事实是,Hook之后循环中的printf和cout都没有输出了。想知道程序最终调用了哪些API,反汇编吧。
    另外推荐给你个API Hook辅助工具API Monitor,很不错的。
      

  29.   

    如果不是inline的,得到了函数地址就可以用inline hook解决using namespace std;void*Next;__declspec(naked) void Callback(int a)
    {
    __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
      

  30.   

    哦,谢谢jamseyang的推荐。Lactoferrin,你的代码很有深度。。我会努力的,哈哈!不过话说,我单步进入 cout << "abc";进入的函数真的是一个全局函数,而不是cout对象的成员函数——template<class _Traits> inline
    basic_ostream<char, _Traits>& __CLRCALL_OR_CDECL operator<<(
    basic_ostream<char, _Traits>& _Ostr,
    const char *_Val)
      

  31.   

    什么叫cout对象的成员函数?
    成员函数是属于类的,不是属于对象的
      

  32.   

    这个我倒知道。。好吧,我应该说,cout对象所属的类的成员函数。
      

  33.   

    编不过哦。。ostream&(ostream::*mptr_int)(int);
    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)
      

  34.   

    没有什么全局的,就是那样
    你用的什么编译器?
    我这里mptr_int=&ostream::operator<<;和mptr_int=cout.operator<<;是可以通过的
    “成员函数是属于类的,不是属于对象的”,编译器当然知道cout.operator<<就是那个类的
      

  35.   

    我用的是VC2008。cout << ... 调用的是成员函数?觉得不像啊。我看ostream这个文件。第一,这个函数的定义是在basic_ostream类的声明之外而且在定义那里没看到“::”域操作符;第二,这个函数有两个参数,如果是成员函数的话,第一个参数到哪里去了呢?
      

  36.   

    第一个参数就是this
    你是不是找错地方了,
    我这里的是
    _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); }
    直接在类定义里面
      

  37.   

    我是单步进去的哦——
    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”对象才是这个函数的第一个参数。很奇怪啊,不同平台有这么大的差别?!