25

ここでの説明に続いて、機密情報(パスワードなど)をメモリに保存するための安全なクラスが必要な場合は、次のことを行う必要があります。

  • memset/メモリを解放する前にクリアする
  • 再割り当ても同じルールに従う必要があります。reallocを使用する代わりに、mallocを使用して新しいメモリ領域を作成し、古いメモリを新しいメモリにコピーしてから、最後に解放する前に古いメモリをmemset/クリアします。

これは良さそうです。テストクラスを作成して、これが機能するかどうかを確認しました。そこで、「LOL」と「WUT」という単語を追加し続け、その後にこのセキュアバッファクラスに約1000回番号を追加して、そのオブジェクトを破棄してから、最終的にコアダンプを引き起こす簡単なテストケースを作成しました。

クラスは破棄する前にメモリを安全にクリアすることになっているので、コアダンプで「LOLWUT」を見つけることができないはずです。しかし、私はそれらをまだ見つけることができ、私の実装はバグがあるのではないかと思いました。ただし、CryptoPPライブラリのSecByteBlockを使用して同じことを試しました。

#include <cryptopp/osrng.h>
#include <cryptopp/dh.h>
#include <cryptopp/sha.h>
#include <cryptopp/aes.h>
#include <cryptopp/modes.h>
#include <cryptopp/filters.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
using namespace std;

int main(){
   {
      CryptoPP::SecByteBlock moo;

      int i;
      for(i = 0; i < 234; i++){
         moo += (CryptoPP::SecByteBlock((byte*)"LOL", 3));
         moo += (CryptoPP::SecByteBlock((byte*)"WUT", 3));

         char buffer[33];
         sprintf(buffer, "%d", i);
         string thenumber (buffer);

         moo += (CryptoPP::SecByteBlock((byte*)thenumber.c_str(), thenumber.size()));
      }

      moo.CleanNew(0);

   }

   sleep(1);

   *((int*)NULL) = 1;

   return 0;
}

そして、以下を使用してコンパイルします。

g++ clearer.cpp -lcryptopp -O0

そして、コアダンプを有効にします

ulimit -c 99999999

しかし、その後、コアダンプを有効にして実行します

./a.out ; grep LOLWUT core ; echo hello

次の出力が得られます

Segmentation fault (core dumped)
Binary file core matches
hello

これを引き起こしているのは何ですか?SecByteBlockの追加によって再割り当てが発生したため、アプリケーションのメモリ領域全体が再割り当てされましたか?

また、これはSecByteBlockのドキュメントです

編集:vimを使用してコアダンプをチェックした後、私はこれを手に入れました:http: //imgur.com/owkaw

edit2:より簡単にコンパイルできるようにコードを更新し、コンパイル手順

最終編集3:memcpyが原因のようです。mymemcpy以下の彼の答えについては、ラスムスの実装を参照してください。

4

5 に答える 5

25

コアダンプに表示されているにもかかわらず、バッファをクリアした後、パスワードは実際にはメモリにありません。問題はmemcpy、十分に長い文字列を使用すると、パスワードがSSEレジスタにリークし、それら がコアダンプに表示されることです。

size引数がmemcpy特定のしきい値(Macでは<a href="http://opensource.apple.com/source/Libc/Libc-825.25/x86_64/string/bcopy_sse42.s"> 80バイト)より大きい場合、 SSE命令は、メモリのコピーを行うために使用されます。これらの命令は、文字ごと、バイトごと、またはワードごとに移動する代わりに、一度に16バイトを並列にコピーできるため、より高速です。Mac上のLibcからのソースコードの重要な部分は 次のとおりです。

LAlignedLoop:               // loop over 64-byte chunks
    movdqa  (%rsi,%rcx),%xmm0
    movdqa  16(%rsi,%rcx),%xmm1
    movdqa  32(%rsi,%rcx),%xmm2
    movdqa  48(%rsi,%rcx),%xmm3

    movdqa  %xmm0,(%rdi,%rcx)
    movdqa  %xmm1,16(%rdi,%rcx)
    movdqa  %xmm2,32(%rdi,%rcx)
    movdqa  %xmm3,48(%rdi,%rcx)

    addq    $64,%rcx
    jnz     LAlignedLoop

    jmp     LShort                  // copy remaining 0..63 bytes and done

%rcxはループインデックスレジスタ、%rsiソースアドレスレジスタ、%rdi宛先アドレスレジスタです。ループを実行するたびに、64バイトがソースバッファから4つの16バイトSSEレジスタにコピーされ xmm{0,1,2,3}ます。次に、これらのレジスタの値が宛先バッファにコピーされます。

そのソースファイルには、コピーが整列されたアドレスでのみ発生するようにし、64バイトのチャンクを実行した後に残ったコピーの部分を埋め、ソースと宛先が重複する場合を処理するために、さらに多くのものがあります。

ただし、<strong> SSEレジスタは使用後にクリアされません!これは、コピーされたバッファの64バイトがまだxmm{0,1,2,3}レジスタに存在していることを意味します。

これを示すRasmusのプログラムの変更は次のとおりです。

#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <emmintrin.h>

inline void SecureWipeBuffer(char* buf, size_t n){
  volatile char* p = buf;
  asm volatile("rep stosb" : "+c"(n), "+D"(p) : "a"(0) : "memory");
}

int main(){
  const size_t size1 = 200;
  const size_t size2 = 400;

  char* b = new char[size1];
  for(int j=0;j<size1-10;j+=10){
    memcpy(b+j, "LOL", 3);
    memcpy(b+j+3, "WUT", 3);
    sprintf((char*) (b+j+6), "%d", j);
  }
  char* nb = new char[size2];
  memcpy(nb, b, size1);
  SecureWipeBuffer(b,size1);
  SecureWipeBuffer(nb,size2);

  /* Password is now in SSE registers used by memcpy() */
  union {
    __m128i a[4];
    char c;
  };
  asm ("MOVDQA %%xmm0, %0": "=x"(a[0]));
  asm ("MOVDQA %%xmm1, %0": "=x"(a[1]));
  asm ("MOVDQA %%xmm2, %0": "=x"(a[2]));
  asm ("MOVDQA %%xmm3, %0": "=x"(a[3]));
  for (int i = 0; i < 64; i++) {
      char p = *(&c + i);
      if (isprint(p)) {
        putchar(p);
      } else {
          printf("\\%x", p);
      }
  }
  putchar('\n');

  return 0;
}

私のマックでは、これは次のように出力します。

0\0LOLWUT130\0LOLWUT140\0LOLWUT150\0LOLWUT160\0LOLWUT170\0LOLWUT180\0\0\0

ここで、コアダンプを調べると、パスワードは1回だけ発生し、その正確な0\0LOLWUT130\0...180\0\0\0文字列として発生します。コアダンプにはすべてのレジスタのコピーが含まれている必要があります。そのため、その文字列が存在します。これがxmm{0,1,2,4}レジスタの値です。

したがって、パスワードは呼び出し後に実際にはRAMに SecureWipeBuffer存在しなくなり、コアダンプにのみ表示される一部のレジスタに実際に存在するためにのみ表示されますmemcpyRAMのフリーズによって悪用される可能性のある脆弱性が心配な場合は 、もう心配する必要はありません。レジスターにパスワードのコピーがあるのが面倒な場合はmemcpy、SSE2レジスターを使用しない変更を使用するか、完了時にパスワードをクリアしてください。そして、これについて本当に妄想している場合は、コアダンプをテストし続けて、コンパイラがパスワードクリアコードを最適化していないことを確認してください。

于 2013-01-29T20:48:46.800 に答える
10

問題をより直接的に再現する別のプログラムを次に示します。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

inline void SecureWipeBuffer(char* buf, size_t n){
  volatile char* p = buf;
  asm volatile("rep stosb" : "+c"(n), "+D"(p) : "a"(0) : "memory");
}

void mymemcpy(char* b, const char* a, size_t n){
  char* s1 = b;
  const char* s2= a;
  for(; 0<n; --n) *s1++ = *s2++;
}

int main(){
  const size_t size1 = 200;
  const size_t size2 = 400;

  char* b = new char[size1];
  for(int j=0;j<size1-10;j+=10){
    memcpy(b+j, "LOL", 3);
    memcpy(b+j+3, "WUT", 3);
    sprintf((char*) (b+j+6), "%d", j);
  }
  char* nb = new char[size2];
  memcpy(nb, b, size1);
  //mymemcpy(nb, b, size1);
  SecureWipeBuffer(b,size1);
  SecureWipeBuffer(nb,size2);

  *((int*)NULL) = 1;

  return 0;    
}

より小さなサイズに置き換えるか使用するmemcpymymemcpy、問題は解決します。したがって、組み込みのmemcpyは、コピーされたデータの一部をメモリに残す何かを実行するのではないかと思います。

これは、システム全体に最初から設計されていない限り、機密データをメモリからクリアすることは事実上不可能であることを示していると思います。

于 2012-05-22T07:53:25.130 に答える
2

文字列リテラルはメモリに格納され、SecByteBlockクラスによって管理されません。

この他のSOの質問は、それを説明するのに適切な仕事を します。c++の文字列リテラルは静的メモリで作成されますか?

一致する数を確認することで、文字列リテラルによってgrepの一致を説明できるかどうかを確認できます。また、SecByteBlockバッファのメモリ位置を出力して、マーカーと一致するコアダンプ内の位置と一致するかどうかを確認することもできます。

于 2012-05-22T02:43:33.810 に答える
2

の詳細を調べずに、表示されているのは、小さなメモリバッファをコピーするためにmemcpy_s使用される一時的なスタックバッファであると思われます。memcpy_sこれは、デバッガーで実行し、LOLWUTスタックメモリを表示するときに表示されるかどうかを確認することで確認できます。

reallocate[ Crypto ++での実装は、memcpy_sメモリ割り当てのサイズを変更するときに使用します。そのため、メモリ内でいくつかの文字列を見つけることができLOLWUTます。また、そのダンプで多くの異なるLOLWUT文字列が重複しているという事実は、それが再利用されている一時的なバッファであることを示唆しています。]

そのカスタムバージョンは単純なループであり、カウンター以外の一時ストレージを必要としないため、実装memcpy方法よりも確実に安全です。memcpy_s

于 2013-01-29T00:14:41.723 に答える
0

その方法は、メモリ内のデータを暗号化することです。このようにして、データがまだメモリ内にあるかどうかに関係なく、データは常に安全です。もちろん、欠点は、データにアクセスするたびにデータを暗号化/復号化するという点でのオーバーヘッドです。

于 2013-01-29T14:12:10.413 に答える