4

私はC++でのバッファオーバーフローと悪用について自分自身に教えようとしています。私はせいぜい中級のC++の人なので、我慢してください。私はいくつかのチュートリアルに従いましたが、ここに私の質問を説明するためのいくつかのサンプルコードがあります:

#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;
}

ここには、さまざまな種類の悪いjujuが含まれていることを知っています。とにかく、とに入力する文字が多すぎると(たとえばcin << String、1トン) 、VisualStudioからアクセス違反が発生します。ただし、20ishを入力し、次にスペースを入力し、次に別のを入力すると、 (スペース文字が前の呼び出しを返した後に埋められたと仮定して)要求をスキップし、アクセスを許可します。AcUsercPassAAcUsercPasscin

どの時点で、なぜ、データが「認証」にオーバーフローするのか、そしてなぜそれは私がスペースを持っているときにのみ発生し、100万を持っているときは発生しないAのですか...スペースを使用すると「アクセス違反」が発生することはありませんの入力でcUser

4

6 に答える 6

29

プログラムを少し変更して、わかりやすくしました。

#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 );
}

x64コンパイラのコマンドラインMSコンパイラでコンパイルしましたが、最適化はしていません。これで、「ハック」したいexeファイルができました。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

これで、x64スタックがどのように機能するかがわかったので、「ハッキング」を開始できます。RSPはスタックポインタ、関数スタックはRSP値を超えるアドレスです(スタックはより小さなアドレスに成長します)。つまり、RSP+28hここでcUsernameRSP+38hauthentication、、でRSP+40hありcPassword、28h、38h、および40hは16進オフセットであることがわかります。これは説明するための小さな画像です:

-----> 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

ここから何が見えますか?コンパイラがデータを8バイト境界に整列させていることがわかります。たとえば、に10バイトを割り当てるように要求しましたcUsernameが、16バイトを取得しました。当然、x64ビットスタックは8バイト境界に整列されています。つまり、書き込むには、16バイト(シンボル)以上authenticationに書き込む必要があります。cUsernameまた、コンパイラがcPasswordそれよりも高く設定していることにも注意してくださいauthentication-を使用して上書きすることはできません。。のみauthenticationを使用します。cPasswordcUsername

これで、プログラムを実行して入力しますUsername: 0123456789abcdef10123456789abcdef= 16バイト、次1はの下位バイトに入れられますauthentication-私たちにとっては十分です:

Username: 0123456789abcdef1
Pass: whatever
Access granted
1
于 2012-01-09T02:03:16.193 に答える
2

変数を上書きしていauthenticationます。これはauthentication、コードがユーザー名とパスワードをチェックする前であっても、それが肯定的であることを意味します。これを確認するには、確認の前に認証を印刷してください。

もう少し詳しく説明します。入力するユーザー名が非常に長い場合、その長いユーザー名は、によって、にコピーされstrcpyますcUsername。その変数cUsernameは直後にauthenticationあるため、長すぎるユーザー名で上書きされます。

非常に長いユーザー名を入力すると、(再び)認証変数が上書きされます。ただし、戻り値など、スタックのさらに上の項目も上書きされます。プログラムがスタックの上位で上書きしすぎると、非常にひどく壊れて、何かが起こる可能性があります。基本的に、この時点でランダムコードを実行します。

于 2012-01-09T01:10:10.137 に答える
1

を使用する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";
  }      
}

編集:

最近の質問に関しては、デフォルトでcin >> stringは、最初の空白文字(つまり、スペース)で読み取りが停止するため、スペースを入力するcinと、データが破損する前に停止し、アクセス違反が発生しないと思います。 。スペースを読み取れるgetlineようにする場合は、スペースを含めてテキストの行全体を読み取るように、上記のように使用する必要があります。

于 2012-01-09T01:27:24.380 に答える
1

memcpy、memset、strcpyのNULLポインターとバッファーオーバーフローを事前に検出し、問題が発生した場所(file:line)を出力するための提案されたソリューション:

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

于 2016-10-07T15:03:03.867 に答える
0

プログラムをコンパイルすると、コンパイラがデータをメモリに配置する方法を決定します。プログラムにチェックされていない配列アクセスが含まれている場合、メモリ内のデータの配置を知っている悪意のあるユーザーが重要な変数を上書きする方法を理解できるため、悪用される可能性があります。

ただし、C ++では、スタック上での配置方法を完全に制御することはできません。ローカル変数は、メモリ内で任意の順序で表示できます。

バッファオーバーフローの悪用を理解するには、プログラムを分解してマシンコードを詳しく調べる必要があります。これにより、すべての重要な差出人住所を含むスタックのレイアウトが得られます。

ちなみに、「アクセス違反」はVisual Studioではなく、プログラムから発生しています。リバースエンジニアリングに入る前に、おそらく「フォワード」エンジニアリングの経験がもっと必要です。

于 2012-01-09T01:04:20.520 に答える
0

charを10か所(NULL文字を含む)に設定しているので、それ以上のものはにオーバーフローしAuthenticationます。これを修正する方法はたくさんありますが、最も明白なのは、単に文字を大きくすることです。他の方法は、ユーザーが登録時に入力する文字の数を制限することです(これがWebサイトサーバー上にあると仮定します)。strlen(cUsername)また、char配列の長さをカウントし、より少ない文字数でユーザー名の再入力を要求するために使用することもできます。
編集:
わかりました。だからあなたがしたいのはgetline(cin,cUser)代わりに使うことです。cin空白が最初に出現すると読み取りを停止します。getline()スペースの有無にかかわらず、文字列全体を読み取ります。

于 2012-01-09T01:18:04.023 に答える