【问题标题】:memcpy() not working as expectedmemcpy() 没有按预期工作
【发布时间】:2015-06-01 21:08:31
【问题描述】:

我正在尝试在 Linux 上用 C/C++ 实现 Heartbleed Bug 的简单实现(在 vmplayer 上使用 ElementaryOS)。根据我对 heartbleed 错误的理解,它涉及客户端向服务器发送心跳请求,为包含的有效负载指定比有效负载的实际大小更大的大小,这导致服务器包含将布局的内存中的内容在本地负载缓冲区之后,在响应中。

到目前为止,我拥有的是一个客户端/服务器应用程序。客户端连接到服务器并发送一条敏感信息,在本例中为密码。接下来,客户端向服务器发送心跳请求,指定错误的有效负载大小。我这样声明我的局部变量:

        char password[30];// = new char[30];    //Some sensitve data, that will be accessed using the Heartbleed Bug
        char temp[15];// = new char[15];
        char payload[45];// = new char[45];

我像这样从心跳请求中检索内容:

        int payloadSize = atoi(strtok(buffer, " "));
        temp = strtok(NULL, "\n");

在心跳请求中,payload为“payload”,大小为45。然后我像这样调用memcpy():

        memcpy(payload, temp, payloadSize /* 45 in this case */);

我希望有效负载变量包含值“有效负载”,然后是密码变量中的内容。但是有效载荷仅包含值“有效载荷”。

        cout<<payload<<endl; //prints payload

谁能指出我做错了什么?

所有代码:

int main()
{
    ofstream log("ServerLog.txt");

    int listeningSocket = socket(AF_INET, SOCK_STREAM, 0);
    sockaddr_in serverAddress, clientAddress;
    socklen_t clientLen;

    serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1");
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = 54321;

    if(bind(listeningSocket, (sockaddr *) &serverAddress, sizeof(serverAddress)) < 0)
    {
        cout<<strerror(errno)<<endl;
    }

    else
    {
        cout<<"Socket bound to port 12345"<<endl;

        if(listen(listeningSocket, 5) < 0)
        {
            cout<<strerror(errno)<<endl;
        }

        else
        {
            log<<"Listening for connections now..."<<endl;
            int commSocket = accept(listeningSocket, (sockaddr *) &clientAddress, &clientLen);

            log<<"Now connected to a client..."<<endl;

            int N = 0;
            char buffer[100];// = new char[100];
            char password[30];// = new char[30];    //Some sensitve data, that will be accessed using the Heartbleed Bug
            char *temp = new char[15];
            char payload[45];// = new char[45];

            N = recv(commSocket, password, 100, 0);     //Receive sensitive data, in this case, a password

            if(N == 0)          //Check for remote socket close
            {
                cout<<"The remote connection has been closed."<<endl;
            }

            else if(N < 0)      //In case there is an error
            {
                cout<<strerror(errno)<<endl;
            }

            else        //All good, move on
            {
                //cout<<N<<" "<<password<<endl;
                log<<"Password received from client: "<<password<<" of length: "<<N<<endl;

                N = recv(commSocket, buffer, 100, 0);      //recv heartbeat request

                if(N == 0)          //Check for remote socket close
                {
                    cout<<"The remote connection has been closed."<<endl;
                }

                else if(N < 0)      //In case there is an error
                {
                    cout<<strerror(errno)<<endl;
                }

                else
                {
                    log<<"Heartbeat request received from client: "<<buffer<<endl;

                    int payloadSize = atoi(strtok(buffer, " "));
                    temp = strtok(NULL, "\n");

                    memcpy(payload, temp, 45);

                    if(N < 0)
                    {
                        cout<<strerror(errno)<<endl;
                    }
                }
            }
        }
    }

    return 0;
}

【问题讨论】:

  • 你如何检查memcpy() 之后的payload?作为字符串查看/打印(这对您没有帮助,因为"payload" 末尾的\0 将结束打印)?或者转储所有 45 个字节,这可能取决于您的堆栈安排?
  • 您是否打算让 memcpy 也接收 password
  • 不能保证password在内存中直接位于temp之后。在真正的 Heartbleed 的情况下,情况就是这样,但也许你的程序有不同的内存布局。
  • memcpy() 没有任何问题——它完全按照您的指示行事。
  • Paul Roub:使用简单的 cout。实际上我确实想到了这个,导致终止的空字符,所以我写了一个 for 循环来一个一个地打印出所有 45 个字符。它似乎没有帮助。 (得到相同的结果) Matt McNabb:是的 deviantfan:根据我对局部变量在内存中的布局方式的理解,变量按它们出现在堆栈上的顺序被推送,这应该保证密码在 temp 之后。

标签: c++ linux buffer-overflow heartbleed-bug network-security


【解决方案1】:

C 样式字符串以空终止符 (\0) 终止,cout 不知道它们存储的缓冲区长度。当您使用memcpy 时,它会复制空终止符和字符串之外的所有内容。缓冲区实际上保存了字符串后面的所有内容,但它位于空终止符之后,因此它不被视为字符串的一部分,这意味着它不是由cout 打印的。

缓冲区负载可能如下所示

{ 'p', 'a', 'y', 'l', 'o', 'a', 'd', '\0', 'g', 'a', 'r', 'b', 'a', 'g', 'e' }
                                     ^^^^ String ends here

因此,当您使用cout 打印它时,它只会打印到\0 字符。这意味着它将打印payload 而没有其他内容。

如果您想打印缓冲区中的所有值,您必须一一打印。你可以这样做:

for(int i = 0; i < 45; i++)
{
    cout << payload[i];
}
cout << endl;

这会将缓冲区的所有单个字符打印到控制台。

由于打印特殊字符可能会产生奇怪的输出,您可能希望打印出字符的数值。要打印字节的数值,您可以执行@MattMcNabb 建议使用 printf 的操作。然后代码将如下所示

for(int i = 0; i < 45; i++)
{
    printf("%02X", (unsigned char)payload[i]);
}

【讨论】:

  • 以这种方式打印控制字符或空字节可能会产生奇怪的输出,它可能对printf("%02X", (unsigned char)payload[i]); 或 cout 等效项更有用
【解决方案2】:

您使用strtok 的方式是错误的。如果int payloadSize = atoi(strtok(buffer, " ")); 为您提供payloadSize,那么当您执行temp = strtok(NULL, "\n"); 时,您将不会在temp 中拥有任何内容,因为您已在strtok 的上下文中将缓冲区指针移至末尾。

在 memcpy 之前检查 temp 中的内容

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-19
    • 2020-03-18
    • 2012-06-14
    • 2014-11-15
    • 1970-01-01
    • 2012-07-02
    • 2011-09-07
    相关资源
    最近更新 更多