绕过 C++ 中的函数以在目标进程中从 ExtTextOut 复制数据字符串时,malloc 会导致崩溃吗?

Posted

技术标签:

【中文标题】绕过 C++ 中的函数以在目标进程中从 ExtTextOut 复制数据字符串时,malloc 会导致崩溃吗?【英文标题】:Can malloc cause a crash when detouring a function in C++ to copy a data string from ExtTextOut in a target process? 【发布时间】:2013-06-11 18:56:48 【问题描述】:

我正在尝试使用 MSDN Detours 3.0 绕道而行,以注册由第三方软件由 ExtTextOut() 绘制的文本。我创建了一个 DLL,将其注入目标软件。当我绕过 ExtTextOut 时,我尝试复制发送到方法的字符串并将文本读取到修改格式的文本文件中。由于输入字符串是 UTF-16 并且我只对保持 ASCII 字符低于 127 感兴趣,因此我为此做了一些逻辑。

但问题是,我的程序在注入目标后不久就崩溃了。我怀疑它可能与 ma​​lloc 函数有关。

如果我使用 malloc 来定位进程中的内存,我可以保证这不会覆盖目标进程中的任何内存吗?如果是这样,我该如何创建一个函数来确保我的 malloc 不会干扰目标进程。

代码:

BOOL WINAPI Mine_ExtTextOut(HDC hdc, int X, int Y, UINT options, RECT* lprc, LPCWSTR text, UINT cbCount, INT* lpSpacingValues)


    // 
    if(reinterpret_cast<const char*>(text)[0] == '>') 

        //wstring_convert<codecvt_utf8_utf16<wchar_t>,wchar_t> convert;
        //string utf8_string = convert.to_bytes(text);
        //int n = utf8_string.length();

        int n = cbCount;

        char *buffer = (char*) malloc (n+1);
        char *bufferCopy = (char*) malloc (n+1);

        for (int i=0; i<n; i++)
            if((long) text[i] > 127)
                buffer[i] = ' ';
                continue;
            
            buffer[i]= (char) text[i];
        
        buffer[n]='\0';



        bool wasBlank = false;
        int ix = 0;
        for(int i = 0; i<n; ++i)
            if(buffer[i] == ' ')
                if(wasBlank || i < 2) continue;
                bufferCopy[ix++] = buffer[i];
                wasBlank = true;
                continue;
            
            wasBlank = false;
            if(buffer[i] == '>') continue;
            bufferCopy[ix++] = buffer[i];
        
        bufferCopy[ix]='\0';

        ofstream myFile;
        myFile.open("C:\\temp\\textHooking\\textHook\\example2.txt", ios::app);
        if(buffer[0] == '>')
            //myFile.write(reinterpret_cast<const char*>(text), cbCount*sizeof(*text));
            myFile.write(bufferCopy, ix*sizeof(*bufferCopy));
            myFile << endl;
        

        free(buffer);
        free(bufferCopy);

    
    BOOL rv = Real_ExtTextOut(hdc, X, Y, options, lprc, text, cbCount, lpSpacingValues);
    return rv;

【问题讨论】:

尝试关闭 myfile 并在写入之前检查失败标志。 很高兴您看到了这一点,但不幸的是,这并不能解决问题。 Actaully 如果我评论几乎所有内容,除了从 malloc 到原始调用。它仍然崩溃。但是,如果我注释掉所有内容并且只将此函数重定向到原始函数,它当然可以工作。 当它发生时你会得到什么崩溃信息? 目标软件只是死机,windows 说程序已经崩溃。不知何故,分配的内存似乎以某种方式覆盖了进程中已经存在的内存,因为如果我注释掉除 malloc 之外的所有代码,它仍然会崩溃。 @steven:我也是这么想的……有两个 c-runtime 库副本(在调用进程和 DLL 中)。而不是 malloc(n+1) 尝试 buffer= GlobalAlloc(GPTR, n+1); 【参考方案1】:

ExtTextOut()cbCount参数用字符表示,而malloc()的输入参数用字节表示。您正在挂钩ExtTextOut()(又名ExtTextOutW())的Unicode 版本,其中sizeof(WCHAR) 是2 个字节。您试图将输入字符串视为 Ansi,但事实并非如此,并且您没有考虑 UTF-16 代理项。

要执行您正在尝试的操作,您需要先将 UTF-16 数据实际解码为 Unicode 代码点,然后再决定保留哪些代码点,例如:

BOOL WINAPI Mine_ExtTextOut(HDC hdc, int X, int Y, UINT options, RECT* lprc, LPCWSTR text, UINT cbCount, INT* lpSpacingValues)

    if ((cbCount > 0) && (text != NULL) && (text[0] == L'>'))
     
        // worse case, every UTF-16 character is ASCII and will be kept,
        // so allocate enough memory for at least that many characters
        std::string buffer(cbCount);
        std::string bufferCopy(cbCount);

        int ix1 = 0;
        for (UINT i = 0; i < cbCount;)
        
            ULONG c;

            // is it a UTF-16 high surrogate?
            if ((text[i] >= 0xD800) && (text[i] <= 0xDBFF))
            
                // is it at the end of the string?
                if ((i+1) == cbCount)
                
                    // malformed surrogate
                    break;
                

                // is it followed by a UTF-16 low surrogate?
                if ((text[i+1] < 0xDC00) || (text[i+1] > 0xDFFF))
                
                    // malformed surrogate
                    break;
                

                // decode the surrogate and skip past it
                c = ((ULONG(text[i] - 0xD800) << 10) | ULONG(text[i+1] - 0xDC00)) + 0x10000;
                i += 2;
            

            // is it a UTF-16 low surrogate?
            else if (text[i] >= 0xDC00) && (text[i] <= 0xDFFF))
            
                // malformed surrogate
                break;
            

            // must be a non-surrogated character
            else
            
                c = (ULONG) text[i];
                ++i;
            

            // keep it?
            if( c > 127 )
                buffer[ix1] = ' ';
            else
                buffer[ix1] = (char) c;

            ++ix1;
        

        bool wasBlank = false;
        int ix2 = 0;
        for(int i = 0; i < ix1; ++i)
        
            if (buffer[i] == ' ')
            
                if (wasBlank || (i < 2)) continue;
                bufferCopy[ix2++] = buffer[i];
                wasBlank = true;
                continue;
            
            wasBlank = false;
            if (buffer[i] == '>') continue;
            bufferCopy[ix2++] = buffer[i];
        

        ofstream myFile;
        myFile.open("C:\\temp\\textHooking\\textHook\\example2.txt", ios::app);
        if (myFile)
        
            myFile.write(bufferCopy.c_str(), ix2);
            myFile << endl;
        
    

    return Real_ExtTextOut(hdc, X, Y, options, lprc, text, cbCount, lpSpacingValues);

【讨论】:

感谢您提供非常好的意见。我会明确地对此进行测试。但是有两个问题,如果发现格式错误的代理,为什么要打破 for 循环?之后的以下字符是否有可能有用?另外,语句if((i+1) == n),我猜是n = cbCount? 好的,现在我确定发生了一些非常奇怪的事情。如果我只是注释掉除了字符串的一些内存分配之外的所有内容,即 std::string(cbCount, ' ') 目标程序崩溃。但是,如果我也将分配注释掉,程序运行顺畅。我怀疑目标程序可能有一些防止 DLL 注入的工具。你怎么看?但是,奇怪的是我仍然可以绕道这个函数,在里面做一些小东西,加上两个数字,程序就不会崩溃。是否有任何检查内存的DLL注入方法? 代理对必须包含有效的高值和低值。如果您遇到格式错误的代理对,则文本数据已损坏,您无法真正正确地解释它。如果您愿意,您可以跳过格式错误的代理并继续处理文本的其余部分,但您可能会得到不好的结果。是的,n 应该是 cbCount 您可能会遇到冲突的内存管理器,因为您的代码是针对与目标应用程序不同的 RTL 编译的。如果您在迂回函数内分配内存时遇到问题,请尝试切换到LocalAlloc()LocalFree()(不是malloc()free()),或者至少将它们包装在您可以传递给std::string 的自定义分配器中.这个想法是操作系统,而不是 RTL,正在分配/释放内存。 bytes/wchar_t 问题可能是根本问题。竞争分配器也可能是一个问题。我相信代理是一条红鲱鱼,因为用户只关心与 ASCII 兼容的值,因此它们可以像任何其他高值一样被跳过。一个潜在的问题是这些值可能不是字符而是字体索引(如果在选项中设置了 ETO_GLYPH_INDEX 标志)。我不知道字体是否通常将索引与该范围内字符的 ASCII 值对齐。【参考方案2】:

根据 cmets,您可以采取一些措施来缩小范围:

替换 malloc:改为使用 buffer= (char *)GlobalAlloc(GPTR, n+1);GlobalFree(buffer)。这将确保拥有多个 c 运行时库不是问题。

或者,为了确保不会出现超出分配缓冲区边界的情况,请尝试更多地进行 malloc,例如buffer= malloc(n+1024); 看看是否能解决问题。如果是这样,如果超出缓冲区中的有效偏移量,则需要梳理代码。

类似的方法(用于测试)是使用比您预期的输入更长的缓冲区的堆栈版本,例如char buffer[1024]; 并像 malloc 缓冲区一样使用它们(当然不要释放它们),看看这有什么不同。

【讨论】:

以上是关于绕过 C++ 中的函数以在目标进程中从 ExtTextOut 复制数据字符串时,malloc 会导致崩溃吗?的主要内容,如果未能解决你的问题,请参考以下文章

直接访问 StageFright.so 以在 Android 中从 JNIlayer 解码 H.264 流

如何在构造函数中访问类变量以在不使用 C++ 中的 this 指针的情况下分配它们

在c ++中从另一个进程中解锁线程

在 WP8 中从 C++ 代码调用 C# 方法

有没有办法绕过 C++ 构造函数?

在 Linux 中从 C++ 库调用 C++ 函数