0

我正在尝试使用cryptopp,下面的代码导致stringsource函数的访问冲突。这可能是什么原因?我以前成功地运行过类似的代码,差别不大。

AesHelper.cpp

#include "dll.h"
#include "AesHelper.h"

#include "aes.h"
using CryptoPP::AES;
#include "ccm.h"
using CryptoPP::CBC_Mode;

#include "filters.h"
using CryptoPP::StringSink;
using CryptoPP::StringSource;
using CryptoPP::StreamTransformationFilter;

#include "hex.h"
using CryptoPP::HexEncoder;
using CryptoPP::HexDecoder;

#include <string>
using namespace std;

#include "osrng.h"
using CryptoPP::AutoSeededRandomPool;

byte AesHelper::_key[AES::DEFAULT_KEYLENGTH];
byte AesHelper::_iv[AES::BLOCKSIZE];

void AesHelper::encrypt(const char* str, char ** outIv, char ** encrypted )
{
    try
    {
        AutoSeededRandomPool prng;

        byte key[AES::DEFAULT_KEYLENGTH];
        prng.GenerateBlock(key, sizeof(key));

        byte iv[AES::BLOCKSIZE];
        prng.GenerateBlock(iv, sizeof(iv));

        string cipher, encoded;
        string plain = "CBC Test Mode";

        CBC_Mode<AES>::Encryption e;
        e.SetKeyWithIV(key, sizeof(key), iv);

        // The StreamTransformationFilter removes
        //  padding as required.
        StreamTransformationFilter *stf = new StreamTransformationFilter(e,
                new StringSink(cipher),
                CryptoPP::BlockPaddingSchemeDef::ZEROS_PADDING
            );

        StringSource s(plain, true, stf); // This line cause Access Violation

        StreamTransformationFilter filter(e);
        filter.Put((const byte*)plain.data(), plain.size());
        filter.MessageEnd();

        const size_t ret = filter.MaxRetrievable();
        cipher.resize(ret);
        filter.Get((byte*)cipher.data(), cipher.size());

        //encode the cipher to hexadecimal
        StringSource(cipher, true,
        new HexEncoder(
                new StringSink(encoded)
            ) // HexEncoder
        ); // StringSource


        //set the output parameter
        outIv = (char**)_iv;
        encrypted = (char**)cipher.c_str();
    }
    catch(const CryptoPP::Exception& e)
    {
        cerr << "exception : " << e.what() << endl;
        exit(1);
    }

}

错误

PaymentManager.exe 中 0x550714CA (cryptopp.dll) 处未处理的异常:0xC0000005:访问冲突读取位置 0x74736554。

cryptopp.dll!memcpy(unsigned char * dst, unsigned char * src, unsigned long count) 第 188 行未知

更新: 将 DLL 和 Exe 程序都设置为“发布”后问题解决。但现在又出现了新问题。在这条线上,问题也在 stringsource 函数中

StringSource(cipher, true,
            new HexEncoder(
                    new StringSink(encoded)
                ) // HexEncoder
            ); // StringSource

错误

PaymentManager.exe 已触发断点。

程序停在

void __cdecl _free_base (void * pBlock) {

        int retval = 0;


        if (pBlock == NULL)
            return;

        RTCCALLBACK(_RTC_Free_hook, (pBlock, 0));

        retval = HeapFree(_crtheap, 0, pBlock); // program stop at this line
        if (retval == 0)
        {
            errno = _get_errno_from_oserr(GetLastError());
        } }
4

3 回答 3

3

0x74736554 是四个 ASCII 字符"tseT"(big-endian) 或"Test"(little-endian) 的十六进制 - 后者正是您的string plain. StringSource构造函数试图读取该地址的事实表明您的可执行文件和 DLL 在 a 的std::string外观上存在分歧。特别是,该库正在从您传递它的对象的偏移量 4 处取消引用内存地址,但您传递它的对象在那里没有有效的指针值。

换句话说,string你正在传递的(或者可能是它的一些子对象)在内存中看起来像这样:

Offset    0      1      2      3      4      5      6      7
       +------+------+------+------+------+------+------+------+--
Value  | 0x43 | 0x42 | 0x43 | 0x20 | 0x54 | 0x65 | 0x73 | 0x74 | ...
       | 'C'  | 'B'  | 'C'  | ' '  | 'T'  | 'e'  | 's'  | 't'  | ...
       +------+------+------+------+------+------+------+------+--

但是,图书馆是这样对待它的:

Offset    0      1      2      3      4      5      6      7
       +------+------+------+------+------+------+------+------+--
Value  |           ?????           | Pointer to character data | ...
       +------+------+------+------+------+------+------+------+--

我只是意识到导致故障的地址完全由与源代码中的值匹配的 ASCII 值组成,我就弄清楚了这一切。

造成这种情况的原因几乎可以肯定是您的代码和库使用了不同的std::string实现,它们具有不同的对象布局。这与跨模块边界分配和释放内存的问题完全相同。为了在模块之间传递 C++ 对象(即主可执行文件和它加载的任何 DLL),两个模块需要就对象的布局方式达成一致。如果模块是在不同时间编译的,那么您需要更加努力地确保它们是针对相同的头文件编译的。

如果您从源代码编译 DLL,那么最简单的做法是确保 DLL 和您的可执行文件都使用相同的 C++ 标准库实现。如果您使用的 DLL 已经由其他人编译,那么您需要询问他们或查看文档以找到编译它的 C++ 标准库,然后针对同一个库编译您的可执行文件。

如果您不能这样做,那么下一个最佳解决方案是避免在所有情况下跨模块边界传递 C++ 对象——仅使用采用预定义数据类型(如整数和原始指针)或 DLL 头文件中定义的数据类型的 API . 这将完全避免该问题,但也会使您的代码更难编写,因为您无法再传递或接收std::string.

于 2013-03-19T04:28:43.617 回答
2

我不知道这个库,但我认为StringSourcegetstd::string作为第一个参数。在这种情况下,您应该绝对确定 DLL 和您的程序都使用完全相同的 STL 编译和链接,使用完全相同的编译器及其选项

于 2013-03-19T03:51:17.267 回答
0

除了鲍里斯和亚当的回答之外,我还看到这会导致 Linux 上出现问题(我意识到你在 Windows 上):

    StringSource(cipher, true,
    new HexEncoder(
            new StringSink(encoded)
        ) // HexEncoder
    ); // StringSource

匿名声明是合法的 C/C++。但是特定版本的 GCC(大约 4.3 或 4.4 或 4.5)会在生成的代码中过早开始运行析构函数。早在 2011 年,我就曾与 Jonathan Wakely 谈过此事,但我始终无法弄清楚原因。这是导致 m 开始查看它的线程:运行 RSA 示例和一些安装问题

解决方法很简单:

    StringSource ss(cipher, true,
    new HexEncoder(
            new StringSink(encoded)
        ) // HexEncoder
    ); // StringSource
于 2013-10-09T02:36:27.457 回答