简体   繁体   English

C ++缓冲区溢出

[英]C++ Buffer Overflow

I'm trying to teach myself about buffer overflows and exploitation in C++. 我正在尝试自学C ++中的缓冲区溢出和利用。 I'm an intermediate C++ guy, at best, so bear with me. 我是一个中级C ++家伙,充其量,所以请耐心等待。 I've followed a few tutorials, but here's some example code to illustrate my question: 我已经按照一些教程,但这里有一些示例代码来说明我的问题:

#include <string>
#include <iostream>

using namespace std; 

int main()
{
  begin:
  int authentication = 0;
  char cUsername[10], cPassword[10];
  char cUser[10], cPass[10];

  cout << "Username: ";
  cin >> cUser;

  cout << "Pass: ";
  cin >> cPass;

  strcpy(cUsername, cUser);
  strcpy(cPassword, cPass);

  if(strcmp(cUsername, "admin") == 0 && strcmp(cPassword, "adminpass") == 0)
  {
    authentication = 1;
  }
  if(authentication)
  {
    cout << "Access granted\n";
    cout << (char)authentication;
  } 
  else 
  {
    cout << "Wrong username and password\n";
  }

  system("pause");
  goto begin;
}

I know there's all kinds of bad juju in here with cin << String , etc... Anyhow, when I enter too many letters (a ton of A 's for instance) into cUser and cPass , I just get an Access Violation from Visual Studio. 我知道这里有各种各样的坏juju与cin << String等...无论如何,当我输入太多字母(例如一吨A )进入cUsercPass ,我只是从一个访问冲突中获取视觉工作室。 If, however, I type 20ish A 's, then a space, then another A into cUser , it skips asking me for cPass (assuming because it's been filled after the space character caused the previous call to cin to return) and just grants me access. 但是,如果我输入20的A ,然后是空格,然后是另一个A进入cUser ,它会跳过向我询问cPass (假设因为它在空格字符导致之前调用cin返回后被填充)并且只是授予我访问。

At what point, and why, is data overflowing into "authentication" and why does it only happen when I have the space and not when I have a million A 's... I never get the "Access Violation" when I use a space in the input for cUser . 在什么时候,以及为什么,数据溢出到“身份验证”,为什么它只发生在我有空间而不是我有百万A的时候......我从来没有得到“访问冲突”当我使用cUser输入中的cUser

I modified your program a little bit to make it more illustrative: 我稍微修改了你的程序,使其更具说明性:

#include <iostream>

int main( void )
{
 int authentication = 0;
 char cUsername[ 10 ];
 char cPassword[ 10 ];

 std::cout << "Username: ";
 std::cin >> cUsername;

 std::cout << "Pass: ";
 std::cin >> cPassword;

 if( std::strcmp( cUsername, "admin" ) == 0 && std::strcmp( cPassword, "adminpass" ) == 0 )
 {
  authentication = 1;
 }
 if( authentication )
 {
  std::cout << "Access granted\n";
  std::cout << ( char )authentication;
 }
 else
 {
  std::cout << "Wrong username and password\n";
 }

 return ( 0 );
}

I compiled it with x64 compiler command-line MS compiler, no optimizations. 我用x64编译器命令行MS编译器编译它,没有优化。 So now we have an exe that we want to "hack". 所以现在我们有一个我们想要“破解”的exe。 We load the program with WinDbg (really good debugger) and take a look at the disassembly (notice, I've supplied full debug info, for clarity): 我们使用WinDbg(非常好的调试器)加载程序并查看反汇编(注意,为了清楚起见,我提供了完整的调试信息):

00000001`3f1f1710 4883ec68        sub     rsp,68h
00000001`3f1f1714 488b0515db0300  mov     rax,qword ptr [Prototype_Console!__security_cookie (00000001`3f22f230)]
00000001`3f1f171b 4833c4          xor     rax,rsp
00000001`3f1f171e 4889442450      mov     qword ptr [rsp+50h],rax
00000001`3f1f1723 c744243800000000 mov     dword ptr [rsp+38h],0  // This gives us address of "authentication" on stack.
00000001`3f1f172b 488d156e1c0300  lea     rdx,[Prototype_Console!std::_Iosb<int>::end+0x78 (00000001`3f2233a0)]
00000001`3f1f1732 488d0d47f00300  lea     rcx,[Prototype_Console!std::cout (00000001`3f230780)]
00000001`3f1f1739 e8fdf9ffff      call    Prototype_Console!ILT+310(??$?6U?$char_traitsDstdstdYAAEAV?$basic_ostreamDU?$char_traitsDstd (00000001`3f1f113b)
00000001`3f1f173e 488d542428      lea     rdx,[rsp+28h] // This gives us address of "cUsername" on stack.
00000001`3f1f1743 488d0df6f00300  lea     rcx,[Prototype_Console!std::cin (00000001`3f230840)]
00000001`3f1f174a e823faffff      call    Prototype_Console!ILT+365(??$?5DU?$char_traitsDstdstdYAAEAV?$basic_istreamDU?$char_traitsDstd (00000001`3f1f1172)
00000001`3f1f174f 488d153e1c0300  lea     rdx,[Prototype_Console!std::_Iosb<int>::end+0x6c (00000001`3f223394)]
00000001`3f1f1756 488d0d23f00300  lea     rcx,[Prototype_Console!std::cout (00000001`3f230780)]
00000001`3f1f175d e8d9f9ffff      call    Prototype_Console!ILT+310(??$?6U?$char_traitsDstdstdYAAEAV?$basic_ostreamDU?$char_traitsDstd (00000001`3f1f113b)
00000001`3f1f1762 488d542440      lea     rdx,[rsp+40h] // This gives us address of "cPassword" on stack.
00000001`3f1f1767 488d0dd2f00300  lea     rcx,[Prototype_Console!std::cin (00000001`3f230840)]
00000001`3f1f176e e8fff9ffff      call    Prototype_Console!ILT+365(??$?5DU?$char_traitsDstdstdYAAEAV?$basic_istreamDU?$char_traitsDstd (00000001`3f1f1172)
00000001`3f1f1773 488d15321c0300  lea     rdx,[Prototype_Console!std::_Iosb<int>::end+0x84 (00000001`3f2233ac)]
00000001`3f1f177a 488d4c2428      lea     rcx,[rsp+28h]
00000001`3f1f177f e86c420000      call    Prototype_Console!strcmp (00000001`3f1f59f0)
00000001`3f1f1784 85c0            test    eax,eax
00000001`3f1f1786 751d            jne     Prototype_Console!main+0x95 (00000001`3f1f17a5)
00000001`3f1f1788 488d15291c0300  lea     rdx,[Prototype_Console!std::_Iosb<int>::end+0x90 (00000001`3f2233b8)]
00000001`3f1f178f 488d4c2440      lea     rcx,[rsp+40h]
00000001`3f1f1794 e857420000      call    Prototype_Console!strcmp (00000001`3f1f59f0)
00000001`3f1f1799 85c0            test    eax,eax
00000001`3f1f179b 7508            jne     Prototype_Console!main+0x95 (00000001`3f1f17a5)
00000001`3f1f179d c744243801000000 mov     dword ptr [rsp+38h],1
00000001`3f1f17a5 837c243800      cmp     dword ptr [rsp+38h],0
00000001`3f1f17aa 7426            je      Prototype_Console!main+0xc2 (00000001`3f1f17d2)
00000001`3f1f17ac 488d15151c0300  lea     rdx,[Prototype_Console!std::_Iosb<int>::end+0xa0 (00000001`3f2233c8)]
00000001`3f1f17b3 488d0dc6ef0300  lea     rcx,[Prototype_Console!std::cout (00000001`3f230780)]
00000001`3f1f17ba e87cf9ffff      call    Prototype_Console!ILT+310(??$?6U?$char_traitsDstdstdYAAEAV?$basic_ostreamDU?$char_traitsDstd (00000001`3f1f113b)
00000001`3f1f17bf 0fb6542438      movzx   edx,byte ptr [rsp+38h]
00000001`3f1f17c4 488d0db5ef0300  lea     rcx,[Prototype_Console!std::cout (00000001`3f230780)]
00000001`3f1f17cb e825f9ffff      call    Prototype_Console!ILT+240(??$?6U?$char_traitsDstdstdYAAEAV?$basic_ostreamDU?$char_traitsDstd (00000001`3f1f10f5)
00000001`3f1f17d0 eb13            jmp     Prototype_Console!main+0xd5 (00000001`3f1f17e5)
00000001`3f1f17d2 488d15ff1b0300  lea     rdx,[Prototype_Console!std::_Iosb<int>::end+0xb0 (00000001`3f2233d8)]
00000001`3f1f17d9 488d0da0ef0300  lea     rcx,[Prototype_Console!std::cout (00000001`3f230780)]
00000001`3f1f17e0 e856f9ffff      call    Prototype_Console!ILT+310(??$?6U?$char_traitsDstdstdYAAEAV?$basic_ostreamDU?$char_traitsDstd (00000001`3f1f113b)
00000001`3f1f17e5 33c0            xor     eax,eax
00000001`3f1f17e7 488b4c2450      mov     rcx,qword ptr [rsp+50h]
00000001`3f1f17ec 4833cc          xor     rcx,rsp
00000001`3f1f17ef e8bc420000      call    Prototype_Console!__security_check_cookie (00000001`3f1f5ab0)
00000001`3f1f17f4 4883c468        add     rsp,68h
00000001`3f1f17f8 c3              ret

Now, since we know how x64 stack works we can start "hacking". 现在,既然我们知道x64堆栈如何工作,我们可以开始“黑客攻击”。 RSP is stack pointer, function stack is addresses above RSP value (stack grows into smaller addresses). RSP是堆栈指针,函数堆栈是RSP值以上的地址(堆栈增长到较小的地址)。 So, we see that RSP+28h is where cUsername , RSP+38h is authentication , and RSP+40h is cPassword , where 28h, 38h and 40h are hexadecimal offsets. 因此,我们看到RSP+28hcUsernameRSP+38hauthenticationRSP+40hcPassword ,其中28h,38h和40h是十六进制偏移。 Here is little image to illustrate: 这是一个小图片来说明:

-----> old RSP value // Stack frame of caller of `main` is above, stack frame of main is below 

      16 bytes of
      "cPassword"
+40h
     8 bytes of "authentication"
+38h
      16 bytes of
      "cUsername"
+28h   


-----> RSP value = old RSP-68h

What do we see from here? 我们从这里看到了什么? We see that compiler aligned data on 8 byte boundary: for example, we asked to allocate 10 bytes for cUsername , but we got 16 bytes - x64 bit stack is aligned on 8-byte boundary, naturally. 我们看到编译器在8字节边界上对齐数据:例如,我们要求为cUsername分配10个字节,但我们有16个字节 - 自然地,x64位堆栈在8字节边界上对齐。 That means in order to write into authentication we need to write into cUsername MORE that 16 bytes (symbols). 这意味着为了写入authentication我们需要写入更多16个字节(符号)的cUsername Notice also, that compiler put cPassword higher that authentication - we cannot overwrite authentication using cPassword , only cUsername . 另请注意,编译器将cPassword置于更高的authentication - 我们无法使用cPassword覆盖authentication ,只能覆盖cUsername

So now we run our program and input Username: 0123456789abcdef1 . 所以现在我们运行我们的程序并输入Username: 0123456789abcdef1 0123456789abcdef = 16 bytes, the next 1 is going to be put into lower byte of authentication - good enough for us: 0123456789abcdef = 16个字节,下一个1将被置于authentication低位字节 - 对我们来说足够好了:

Username: 0123456789abcdef1
Pass: whatever
Access granted
1

It is overwriting your authentication variable. 它覆盖了您的authentication变量。 This means that authentication is positive even before your code checks the username and password. 这意味着即使在您的代码检查用户名和密码之前, authentication也是肯定的。 To check this, print out authentication prior to the checks. 要检查此项,请在检查之前打印出身份验证。

I'll expand a little further: When you type is a very long username, that long username is copied, by your strcpy , into cUsername . 我将进一步扩展:当你键入一个很长的用户名时,你的strcpy cUsername这个长用户名复制到cUsername That variable cUsername is immediately after authentication and hence it is overwritten by the overly-long username. 该变量cUsernameauthentication后立即生效,因此被过长的用户名覆盖。

If you type a very very long username, then (again) the authentication variable will be overwritten. 如果您键入一个非常长的用户名,那么(再次)将覆盖身份验证变量。 But also now the items further up the stack, such as the return value, will be overwritten. 但是现在堆栈中的项目(例如返回值)将被覆盖。 If your program overwrites too high up the stack, then it will be very badly broken and anything can happen. 如果你的程序覆盖了太高的堆栈,那么它将被严重破坏,任何事情都可能发生。 You essentially execute random code at this point. 你基本上执行随机代码。

Proposed solution to detect NULL pointer and buffer overflow in memcpy, memset, strcpy before hand and print out the location (file:line) where the problem occurs: 建议的解决方案是在手工检测memcpy,memset,strcpy中的NULL指针和缓冲区溢出,并打印出问题发生的位置(文件:行):

http://htvdanh.blogspot.com/2016/09/proposed-solution-to-detect-null.html http://htvdanh.blogspot.com/2016/09/proposed-solution-to-detect-null.html

If you use std::string , you'll find that your program will be much simpler: 如果你使用std::string ,你会发现你的程序会简单得多:

int main()
{
  bool authenticated = false;

  while(!authenticated)
  {
    string username;
    string password;

    cout << "Username: ";
    getline(cin, username); // you may want to read input differently

    cout << "Pass: ";
    getline(cin, password); // same as above

    // you'll need to check cin.fail() to see whether the stream
    // had failed to read data, and exit the loop with "break".

    if(username == "admin" && password == "adminpass")
    {
      authenticated = true;
    }
    else
    {
      cout << "Wrong username and password, try again\n";
    }
  }

  if(authenticated)
  {
    cout << "Access granted\n";
  }      
}

Edit: 编辑:

With regards to your recent question, I think by default, cin >> string will stop reading at the first whitespace character (ie space), so if you input a space, cin will stop before it corrupts any data, and so you don't get the access violation. 关于你最近的问题,我认为默认情况下, cin >> string将停止读取第一个空格字符(即空格),所以如果你输入一个空格, cin会在它破坏任何数据之前停止,所以你不要t获取访问冲突。 If you want to be able to read spaces, then you'll need to use getline like I have above so that it will read the entire line of text, spaces included. 如果你想要能够阅读空格,那么你需要像我上面那样使用getline ,这样它就会读取整行文本,包括空格。

When you compile a program, the compiler decides how to arrange your data in memory. 编译程序时,编译器决定如何在内存中排列数据。 If a program contains unchecked array accesses, it may be exploitable as a malicious user with knowledge of the arrangement of data in memory can figure out how to overwrite critical variables. 如果一个程序包含未经检查的数组访问,它可能被利用为恶意用户,了解内存中数据的排列可以弄清楚如何覆盖关键变量。

However, C++ doesn't give you complete control over how things are laid out on the stack. 但是,C ++并不能让您完全控制堆栈上的布局。 Local variables may appear in any order in memory. 局部变量可以以任何顺序出现在内存中。

To understand buffer overflow exploits, you will have to disassemble your program and delve into machine code. 要了解缓冲区溢出漏洞,您必须反汇编程序并深入研究机器代码。 This will give you the layout of the stack, including the all-important return addresses. 这将为您提供堆栈的布局,包括所有重要的返回地址。

By the way, the "Access Violation" is coming from your program, not Visual Studio. 顺便说一句,“访问冲突”来自您的程序,而不是Visual Studio。 You probably need more experience with "forward" engineering before getting into reverse engineering. 在进入逆向工程之前,您可能需要更多的“前进”工程经验。

Because you have your char set to 10 places (including the NULL character), anything longer will overflow onto Authentication . 因为您将char设置为10个位置(包括NULL字符),所以更长的时间会溢出到Authentication There are numerous ways to fix this, most obviously being so simply make the char bigger. 有很多方法可以解决这个问题,最明显的是如此简单地使char更大。 Other ways would be to limit the number of letters a user enters at registration (assuming this was on a website server). 其他方法是限制用户在注册时输入的字母数(假设这是在网站服务器上)。 You could also use strlen(cUsername) to count the length of the char array and ask for re-entry of the username with less characters. 您还可以使用strlen(cUsername)来计算char数组的长度,并要求用较少的字符重新输入用户名。
EDIT: 编辑:
Ok. 好。 So what you want to do is use getline(cin,cUser) instead. 所以你想要做的是使用getline(cin,cUser) cin stops reading at the first occurence of a whitespace. cin在空白的第一次出现时停止阅读。 getline() will read the entire string with or without spaces. getline()将使用或不使用空格读取整个字符串。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM