フォヌドを知らない、氎に入っおはいけない。 パヌト2

恐ろしいしprintf

今回は、printf関数に぀いおお話したす。 プログラムの脆匱性に぀いおは誰もが耳にしたしたが、printfのような機胜は犁止されおいたす。 ただし、これらの機胜を䜿甚しない方が良いこずを知っおおく必芁がありたす。 そしおたったく別の-理由を理解する。 この蚘事では、printf関連プログラムの2぀の叀兞的な脆匱性に぀いお説明したす。 その埌はハッカヌになるこずはありたせんが、コヌドを改めお確認しおください。 突然、気づかないうちに同様の脆匱な機胜を実装したす。



やめお 読者を埅っお、通過しないでください。 私はあなたが単語printfを芋たこずを知っおいたす。 そしお、この蚘事の著者は、関数が枡された匕数のタむプを制埡しないこずに぀いお、今や平凡な話をするこずになるず確信しおいたす。 いや 蚘事ではこれに぀いおではなく、脆匱性に぀いお説明したす。 読んでください。



前のメモはこちらです パヌト1



はじめに



この行を芋おください

 printf名前; 


圌女はシンプルで無害なようです。 䞀方、プログラムを攻撃するために少なくずも2぀の方法が隠されおいたす。



この行があるデモから蚘事を始めたしょう。 コヌドは奇劙に思えるかもしれたせん。 そうです。 埌で攻撃するプログラムを曞くのはそれほど簡単ではないこずがわかりたした。 ポむントは、コンパむラが生成する最適化です。 あたりにも単玔なプログラムを䜜成するず、コンパむラヌは、砎壊するものがないようなコヌドを䜜成するこずがわかりたす。 デヌタを保存するためにスタックではなくレゞスタを䜿甚し、関数などを埋め蟌みたす。 コンパむラに十分な空きレゞスタがなく、デヌタをスタックにプッシュし始めるように、䞍必芁なアクションずルヌプを䜿甚しおコヌドを曞くこずができたす。 残念ながら、この䟋は倧きすぎおわかりにくいです。 これらすべおに぀いお別の探偵小説を曞くこずはできたすが、できたせん。



提瀺された䟋は、耇雑さず、コンパむラが単玔すぎお「䜕にも厩壊しない」こずを防ぐ必芁性ずの劥協案です。 ずにかく、私は少し助けたした。 Visual Studio 2010で䜿甚される最適化の皮類の䞀郚を無効にしたした。最初に、/ GLプログラム党䜓の最適化スむッチが無効になりたした。 次に、__ declspecnoinline属性を䜿甚したした。



このような長い玹介をおaびしたす。 プログラムコヌドの䞍噚甚さを明確にしたかったのです。 たた、このコヌドをより適切に䜜成できるずいうトピックに関する議論をすぐに䞭止しおください。 私はあなたができるこずを知っおいたす。 ただし、同時にコヌドを短くするこずはできないため、脆匱性を瀺すこずができたす。



デモ



Visual Studio 2010の完党なコヌドずプロゞェクトはこちらから入手できたす 。

  const size_t MAX_NAME_LEN = 60;
列挙型ErrorStatus {
   E_ToShortName、E_ToShortPass、E_BigName、E_OK
 };

 void PrintNormalizedNameconst char * raw_name
 {
  文字名[MAX_NAME_LEN + 1];
   strcpy名前、raw_name;

   forsize_t i = 0; name [i]= '\ 0'; ++ i
     name [i] = tolowername [i];
   name [0] = touppername [0];

   printf名前;
 }

 ErrorStatus IsCorrectPassword
   const char * universalPassword、
   BOOLretIsOkPass
 {
  文字列名、パスワヌド。
   printf "Name";  cin >>名前;
   printf "パスワヌド";  cin >>パスワヌド;
   ifname.length<1return E_ToShortName;
   ifname.length> MAX_NAME_LENreturn E_BigName;
   ifpassword.length<1return E_ToShortPass;

   retIsOkPass = 
     universalPassword= NULL &&
     strcmppassword.c_str、universalPassword== 0;
   ifretIsOkPass
     retIsOkPass =名前[0] ==パスワヌド[0];

   printf "Hello、";
   PrintNormalizedNamename.c_str;

   return E_OK;
 }

 int _tmainint、char * []
 {
   _set_printf_count_output1;
   char universal [] = "_Universal_Pass_";
   BOOL isOkPassword = FALSE;
   ErrorStatus status =
     IsCorrectPasswordナニバヌサル、isOkPassword;
   ifstatus == E_OK && isOkPassword
     printf "\ nPasswordOK \ n";
  他に
     printf "\ nPasswordERROR \ n";
   0を返したす。
 } 


_tmain関数は、IsCorrectPassword関数を呌び出したす。 パスワヌドが正しい堎合、たたはマゞックワヌド「_Universal_Pass_」ず䞀臎する堎合、プログラムは文字列「PasswordOK」を衚瀺したす。 攻撃の目的は、プログラムがこの行を正確に衚瀺するようにするこずです。



IsCorrectPassword関数は、ナヌザヌにナヌザヌ名ずパスワヌドを芁求したす。 パスワヌドは、関数に枡されるマゞックワヌドず䞀臎する堎合、正しいず芋なされたす。 パスワヌドの最初の文字が名前の最初の文字ず䞀臎する堎合も正しいです。



正しいパスワヌドが入力されたかどうかに関係なく、プログラムはナヌザヌを歓迎したす。 これを行うには、PrintNormalizedName関数が呌び出されたす。



PrintNormalizedName関数には、すべおの楜しみがありたす。 その䞭に、説明されおいる「printfname;」がありたす。 この行を䜿甚しおプログラムをだたす方法に぀いお考えおください。 方法がわかれば、さらに読むこずはできたせん。



PrintNormalizedName関数は䜕をしたすか 圌女は名前を印刷し、最初の文字を倧文字にし、残りを小さくしおいたす。 たずえば、「andREy2008」ずいう名前を入力するず、「Andrey2008」ず出力されたす。



最初の攻撃



正しいパスワヌドがわからないずしたす。 しかし、どこか特定の魔法のパスワヌドがあるこずを知っおいたす。 printfを䜿甚しお怜玢しおみたしょう。 このパスワヌドのアドレスがスタック䞊のどこかにある堎合、成功する可胜性がありたす。 このパスワヌドを画面に衚瀺する方法はありたすか



ヒントをあげたす。 printf関数は、可倉数の匕数を持぀関数ファミリヌを参照したす。 このような機胜はこのように機胜したす。 任意の量のデヌタがスタックに曞き蟌たれたす。 printf関数は、スタックに曞き蟌たれるデヌタの量ずそのタむプを知りたせん。 曞匏文字列によっおのみガむドされたす。 「ds」が曞き蟌たれおいる堎合は、int型の1぀の倀ず1぀のポむンタヌをスタックから抜出する必芁があるこずを意味したす。 printf関数は、枡された匕数の数を知らないため、スタックをより深く芗き蟌み、それずは関係のないデヌタを出力できたす。 原則ずしお、これはアクセス違反たたはゎミの印刷に぀ながりたす 。 ただし、このゎミは䜿甚できたす。



printf関数を呌び出すず、スタックがどのように芋えるかを考えおください。

図1.スタックのデヌタの抂略レむアりト。

図1.スタック䞊のデヌタの抂略レむアりト。



関数呌び出し「printfname;」には、匕数が1぀だけありたす。これはフォヌマット文字列です。 これは、名前の代わりに「d」を入力するず、スタックにあるデヌタをPrintNormalizedName関数の戻りアドレスに出力するこずを意味したす。 詊しおみたしょう



名前d

パスワヌド1

こんにちは37

パスワヌド゚ラヌ



このアクションは無意味ですが。 少なくずも、最初に戻りアドレスず、スタックにもある文字名バッファ[MAX_NAME_LEN + 1];の内容党䜓を出力する必芁がありたす。 そしおその埌、おそらく、私たちは䜕か面癜いものに到達したす。



攻撃者がプログラムを逆アセンブルたたはデバッグする胜力を持っおいない堎合、スタック䞊で䜕かを芋぀けるかどうかを理解するこずは困難です。 ただし、次のように動䜜したす。



最初に「s」ず入力したす。 次に、「xs」ず入力したす。 次に「xxs」などを入力したす。 このハッカヌは、スタック䞊で順番にデヌタを反埩凊理し、それらを文字列ずしお出力しようずしたす。 ここでは、スタック内のすべおのデヌタが、少なくずも4バむトの境界で敎列されおいるこずが圹立ちたす。



正盎に蚀うず、このように振る舞うず、成功したせん。 有甚なものを䜕も印刷せずに、60文字の制限を超えたす。 二重の倀を印刷するための「F」は、私たちの助けになりたす。 したがっお、その助けを借りお、すぐに8バむトず぀スタックに沿っお移動できたす。



そしお、ここに圌女はいたす-埅望のラむン



ffffffffffffffffffffffxs



結果

図2.パスワヌドの印刷。画像をクリックしお拡倧倧したす。

図2.パスワヌドの印刷。 画像をクリックしお拡倧したす。



この行を魔法のパスワヌドずしお詊しおみたしょう。



名前Aaa

パスワヌド_Universal_Pass_

こんにちは、Aaa

パスワヌドOK



やった 私たちは個人デヌタを芋぀けお衚瀺するこずができたしたが、プログラムはアクセスを蚱可したせんでした。 さらに、このためには、バむナリプログラムコヌド自䜓にアクセスする必芁がないこずに泚意しおください。 十分な熱意ず忍耐。



最初の攻撃に関する結論



プラむベヌトデヌタを取埗する同様の方法をより広く怜蚎する必芁がありたす。 可倉数の匕数を持぀関数を含むプログラムを開発するずきは、デヌタがそれらを介しお倖郚に挏れる可胜性がある状況があるかどうかを考慮しおください。 これには、ログファむル、ネットワヌク経由で送信されたパケットなどがありたす。



考慮されたケヌスでは、printf関数の入力が制埡コマンドを含む可胜性のある文字列を受け取るずいう事実により、攻撃が可胜になりたした。 これを回避するには、次のように蚘述するだけで十分です。

  printf "s"、名前; 




二床目の攻撃



printfがメモリを倉曎できるこずを知っおいたすか おそらく、あなたはそれに぀いお読みたしたが、忘れおいたした。 これは修食子「n」です。 これにより、指定したアドレスにprintf関数がすでに印刷した文字数を曞き蟌むこずができたす。



正盎なずころ、n指定子に基づく攻撃は玔粋に歎史的なものです。 Visual Studio 2005以降では、「n」を䜿甚するオプションはデフォルトで無効になっおいたす。 この攻撃を行うには、この修食子を明瀺的に有効にする必芁がありたした。 魔法のアクションは次のずおりです。

  _set_printf_count_output1; 


より明確にするために、「n」の䜿甚䟋を瀺したす。

  int i;
 printf "12345n6789 \ n"、i;
 printf "i =d \ n"、i; 


プログラム出力



123456789

i = 5



スタック䞊の目的のポむンタヌに到達する方法は既に孊びたした。 そしお今、このポむンタによっおメモリを倉曎できるツヌルを手にしおいたす。



もちろん、これを䜿甚するのは䞍䟿です。 たず、䞀床に曞き蟌むこずができるのは4バむトのみですint型のサむズ。 倧きな数字が必芁な堎合は、たずprintf関数で倚くの文字を印刷する必芁がありたす。 これを回避するには、00u指定子が圹立ちたす。 指定子は、出力される珟圚のバむト数の倀に圱響したす。 詳现は説明したせん。



私たちの堎合、すべおがよりシンプルです。 isOkPassword倉数に0以倖の倀を曞き蟌むだけで十分です。この倉数のアドレスはIsCorrectPassword関数に枡されたす。぀たり、スタック䞊のどこかにあるずいうこずです。 倉数が参照ずしお枡されるず混同しないでください。 䜎レベルでは、リンクは通垞のポむンタヌです。



IsCorrectPassword倉数を倉曎できるようにする行は次のずおりです。



fffffffffffffffffffffffn



修食子 "n"は、 "f"などの修食子を䜿甚しお出力される文字数を考慮したせん。 したがっお、「n」の前にスペヌスを1぀入れお、倀1をisOkPasswordに曞き蟌みたす。



私達は詊みたす

図3.メモリぞの曞き蟌みみ。画像をクリックしお拡倧したす。

図3.メモリぞの曞き蟌み。 画像をクリックしお拡倧したす。



印象的 しかし、それはすべおずは皋遠い。 ほずんど任意のアドレスで録音できたす。 出力行がスタック䞊にある堎合、目的の文字に移動しおアドレスずしお䜿甚できたす。



たずえば、「xF8」、「x32」、「x01」、「x7F」のコヌドで連続した文字を含む行を曞くこずができたす。 文字列にハヌドコヌドされた数倀があり、これは倀0x7F0132F8ず同等であるこずがわかりたす。 最埌に、修食子 "n"を配眮したす。 「x」たたは他の修食子を䜿甚しお、゚ンコヌドされた数倀0x7F0132F8に到達し、このアドレスに印刷された文字数を曞き蟌むこずができたす。 この方法には制限がありたすが、それでも非垞に興味がありたす。



2番目の攻撃に関する結論



第二皮の攻撃は今ではほずんど䞍可胜だず蚀えたす。 ご芧のずおり、最新のラむブラリでの「n」指定子のサポヌトはデフォルトでオフになっおいたす。 ただし、このタむプの脆匱性の玠因ずなる独自の自家補メカニズムを䜜成できたす。 倖郚から入力されたデヌタがメモリに䜕をどこに曞き蟌むかを制埡する堎合は泚意しおください。



具䜓的には、この堎合、次のように蚘述すれば、問題は再び発生したせん。

  printf "s"、名前; 




䞀般的な結論



ここでは、脆匱性の2぀の単玔な䟋を怜蚎したす。 もちろん、もっずたくさんありたす。 ここでは、それらを説明したり、少なくずもリストしたりする詊みは行いたせん。 この蚘事は、printf名前のような単玔な構造でさえ危険な堎合があるこずを瀺すこずを蚈画しおいたした。



これから重芁な結論が埗られたす。 セキュリティの専門家でない堎合は、圌らが曞いおいるすべおの掚奚事項に埓うこずが最善です。 掚奚事項の本質は、すべおの脅嚁を単独で評䟡するには薄すぎたす。 結局のずころ、おそらくprintfは危険な関数だずいうこずを読んでいるでしょう。 しかし、私はこの蚘事を読んだ倚くの人が最初にりサギの穎の深さに぀いお孊んだず確信しおいたす。



朜圚的に攻撃の暙的ずなる可胜性のあるアプリケヌションを䜜成しおいる堎合は、非垞に泚意しおください。 完党に無害なコヌドだず思うものには、脆匱性が含たれおいる可胜性がありたす。 コヌドにキャッチが衚瀺されない堎合でも、キャッチされおいるわけではありたせん。



文字列関数の曎新バヌゞョンを䜿甚するためのすべおのコンパむラガむドラむンに埓っおください。 これは、sprintfなどの代わりにsprintf_sを䜿甚するこずを意味したす。



さらに良いこず-䞀般に、文字列での䜎レベルの䜜業を拒吊したす。 これらの関数はC蚀語の遺産です。 珟圚、std :: stringがありたす。 boost :: formatやstd :: stringstreamなど、文字列を圢成する安党な方法がありたす。



PS結論を読んだ誰かが蚀った-「それはすでに明らかだった。」 しかし、正盎に蚀っおください。 この蚘事を読む前に、printfがメモリに曞き蟌むこずができるこずを知っおいたしたか しかし、これは倧きな脆匱性です。 少なくずも以前はそうでした。 今、他の人もいたすが、それは劣らずsidなものです。



All Articles