diff --git a/doc/2.7_pwntools.md b/doc/2.7_pwntools.md index 6579223..365d999 100644 --- a/doc/2.7_pwntools.md +++ b/doc/2.7_pwntools.md @@ -40,12 +40,47 @@ $ yaourt -S python2-pwntools $ yaourt -S python2-pwntools-git ``` +但是由于 Arch 没有 PPA 源,如果想要支持更多的体系结构(如 arm, aarch64 等),只能手动编译安装相应的 binutils,使用下面的脚本,注意将变量 `V` 和 `ARCH` 换成你需要的。[binutils](https://ftp.gnu.org/gnu/binutils/) +```bash +#!/usr/bin/env bash + +V = 2.29 # binutils version +ARCH = arm # target architecture + +cd /tmp +wget -nc https://ftp.gnu.org/gnu/binutils/binutils-$V.tar.xz +wget -nc https://ftp.gnu.org/gnu/binutils/binutils-$V.tar.xz.sig + +# gpg --keyserver keys.gnupg.net --recv-keys C3126D3B4AE55E93 +# gpg --verify binutils-$V.tar.xz.sig + +tar xf binutils-$V.tar.xz + +mkdir binutils-build +cd binutils-build + +export AR=ar +export AS=as + +../binutils-$V/configure \ + --prefix=/usr/local \ + --target=$ARCH-unknown-linux-gnu \ + --disable-static \ + --disable-multilib \ + --disable-werror \ + --disable-nls + +make +sudo make install +``` 测试安装是否成功: ```python ->>> import pwn ->>> pwn.asm("xor eax,eax") -'1\xc0' +>>> from pwn import * +>>> asm('nop') +'\x90' +>>> asm('nop', arch='arm') +'\x00\xf0 \xe3' ``` @@ -68,7 +103,7 @@ Pwntools 分为两个模块,一个是 `pwn`,简单地使用 `from pwn import - `libcdb`:libc 数据库 - `log`:日志记录 - **`memleak`**:用于内存泄露 -- `rop`:ROP 利用模块,包括 rop 和 srop +- **`rop`**:ROP 利用模块,包括 rop 和 srop - `runner`:运行 shellcode - **`shellcraft`**:shellcode 生成器 - `term`:终端处理 @@ -107,106 +142,310 @@ Pwntools 分为两个模块,一个是 `pwn`,简单地使用 `from pwn import - `close()`:关闭管道 下面是一个例子,先使用 listen 开启一个本地的监听端口,然后使用 remote 开启一个套接字管道与之交互: -``` -In [1]: from pwn import * - -In [2]: l = listen() +```text +>>> from pwn import * +>>> l = listen() [x] Trying to bind to 0.0.0.0 on port 0 [x] Trying to bind to 0.0.0.0 on port 0: Trying 0.0.0.0 [+] Trying to bind to 0.0.0.0 on port 0: Done -[x] Waiting for connections on 0.0.0.0:35117 +[x] Waiting for connections on 0.0.0.0:46147 +>>> r = remote('localhost', l.lport) +[x] Opening connection to localhost on port 46147 +[x] Opening connection to localhost on port 46147: Trying ::1 +[x] Opening connection to localhost on port 46147: Trying 127.0.0.1 +[+] Opening connection to localhost on port 46147: Done +>>> [+] Waiting for connections on 0.0.0.0:46147: Got connection from 127.0.0.1 on port 38684 -In [3]: r = remote('localhost', l.lport) -[x] Opening connection to localhost on port 35117 -[x] Opening connection to localhost on port 35117: Trying ::1 -[x] Opening connection to localhost on port 35117: Trying 127.0.0.1 -[+] Opening connection to localhost on port 35117: Done - -[+] Waiting for connections on 0.0.0.0:35117: Got connection from 127.0.0.1 on port 36966 -In [4]: c = l.wait_for_connection() - -In [5]: r.send('hello\n') - -In [6]: c.recv() -Out[6]: 'hello\n' - -In [7]: r.send('hello\n') - -In [8]: c.recvline() -Out[8]: 'hello\n' - -In [9]: r.sendline('hello') - -In [10]: c.recv() -Out[10]: 'hello\n' - -In [11]: r.sendline('hello') - -In [12]: c.recvline() -Out[12]: 'hello\n' - -In [13]: r.sendline('hello') - -In [14]: c.recvline(keepends=False) -Out[14]: 'hello' - -In [15]: r.send('hello world') - -In [16]: c.recvuntil('hello') -Out[16]: 'hello' - -In [17]: c.recv() -Out[17]: ' world' - -In [18]: c.close() -[*] Closed connection to 127.0.0.1 port 36966 - -In [19]: r.close() -[*] Closed connection to localhost port 35117 +>>> c = l.wait_for_connection() +>>> r.send('hello\n') +>>> c.recv() +'hello\n' +>>> r.send('hello\n') +>>> c.recvline() +'hello\n' +>>> r.sendline('hello') +>>> c.recv() +'hello\n' +>>> r.sendline('hello') +>>> c.recvline() +'hello\n' +>>> r.sendline('hello') +>>> c.recvline(keepends=False) +'hello' +>>> r.send('hello world') +>>> c.recvuntil('hello') +'hello' +>>> c.recv() +' world' +>>> c.close() +[*] Closed connection to 127.0.0.1 port 38684 +>>> r.close() +[*] Closed connection to localhost port 46147 ``` 下面是一个与进程交互的例子: -``` -In [1]: p = process('/bin/sh') +```text +>>> p = process('/bin/sh') [x] Starting local process '/bin/sh' -[+] Starting local process '/bin/sh': pid 32165 - -In [2]: p.sendline('sleep 3; echo hello world;') - -In [3]: p.recvline(timeout=1) -Out[3]: 'hello world\n' - -In [4]: p.sendline('sleep 3; echo hello world;') - -In [5]: p.recvline(timeout=1) -Out[5]: '' - -In [6]: p.recvline(timeout=5) -Out[6]: 'hello world\n' - -In [7]: p.interactive() +[+] Starting local process '/bin/sh': pid 26481 +>>> p.sendline('sleep 3; echo hello world;') +>>> p.recvline(timeout=1) +'hello world\n' +>>> p.sendline('sleep 3; echo hello world;') +>>> p.recvline(timeout=1) +'' +>>> p.recvline(timeout=5) +'hello world\n' +>>> p.interactive() [*] Switching to interactive mode whoami firmy ^C[*] Interrupted +>>> p.close() +[*] Stopped process '/bin/sh' (pid 26481) +``` -In [8]: p.close() -[*] Stopped process '/bin/sh' (pid 32165) +#### shellcraft +使用 shellcraft 模块可以生成对应架构和 shellcode 代码,直接使用链式调用的方法就可以得到,首先指定体系结构,再指定操作系统: +``` +>>> print shellcraft.i386.nop().strip('\n') + nop +>>> print shellcraft.i386.linux.sh() + /* execve(path='/bin///sh', argv=['sh'], envp=0) */ + /* push '/bin///sh\x00' */ + push 0x68 + push 0x732f2f2f + push 0x6e69622f + mov ebx, esp + /* push argument array ['sh\x00'] */ + /* push 'sh\x00\x00' */ + push 0x1010101 + xor dword ptr [esp], 0x1016972 + xor ecx, ecx + push ecx /* null terminate */ + push 4 + pop ecx + add ecx, esp + push ecx /* 'sh\x00' */ + mov ecx, esp + xor edx, edx + /* call execve() */ + push SYS_execve /* 0xb */ + pop eax + int 0x80 ``` #### asm +该模块用于汇编和反汇编代码。 -#### dynelf +体系结构,端序和字长需要在 `asm()` 和 `disasm()` 中设置,但为了避免重复,运行时变量最好使用 `pwnlib.context` 来设置。 + +汇编:(`pwnlib.asm.asm`) +```text +>>> asm('nop') +'\x90' +>>> asm(shellcraft.nop()) +'\x90' +>>> asm('nop', arch='arm') +'\x00\xf0 \xe3' +>>> context.arch = 'arm' +>>> context.os = 'linux' +>>> context.endian = 'little' +>>> context.word_size = 32 +>>> context +ContextType(arch = 'arm', bits = 32, endian = 'little', os = 'linux') +>>> asm('nop') +'\x00\xf0 \xe3' +``` +``` +>>> asm('mov eax, 1') +'\xb8\x01\x00\x00\x00' +>>> asm('mov eax, 1').encode('hex') +'b801000000' +``` +请注意,这里我们生成了 i386 和 arm 两种不同体系结构的 `nop`,当你使用不同与本机平台的汇编时,需要安装该平台的 binutils,方法在上面已经介绍过了。 + +反汇编:(`pwnlib.asm.disasm`) +```text +>>> print disasm('\xb8\x01\x00\x00\x00') + 0: b8 01 00 00 00 mov eax,0x1 +>>> print disasm('6a0258cd80ebf9'.decode('hex')) + 0: 6a 02 push 0x2 + 2: 58 pop eax + 3: cd 80 int 0x80 + 5: eb f9 jmp 0x0 +``` + +构建具有指定二进制数据的 ELF 文件:(`pwnlib.asm.make_elf`) +```text +>>> context.clear(arch='amd64') +>>> context +ContextType(arch = 'amd64', bits = 64, endian = 'little') +>>> bin_sh = asm(shellcraft.amd64.linux.sh()) +>>> bin_sh +'jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05' +>>> filename = make_elf(bin_sh, extract=False) +>>> filename +'/tmp/pwn-asm-V4GWGN/step3-elf' +>>> p = process(filename) +[x] Starting local process '/tmp/pwn-asm-V4GWGN/step3-elf' +[+] Starting local process '/tmp/pwn-asm-V4GWGN/step3-elf': pid 28323 +>>> p.sendline('echo hello') +>>> p.recv() +'hello\n' +``` +这里我们生成了 amd64,即 64 位 `/bin/sh` 的 shellcode,配合上 asm 函数,即可通过 `make_elf` 得到 ELF 文件。 + +另一个函数 `pwnlib.asm.make_elf_from_assembly` 允许你构建具有指定汇编代码的 ELF 文件: +```text +>>> asm_sh = shellcraft.amd64.linux.sh() +>>> print asm_sh + /* execve(path='/bin///sh', argv=['sh'], envp=0) */ + /* push '/bin///sh\x00' */ + push 0x68 + mov rax, 0x732f2f2f6e69622f + push rax + mov rdi, rsp + /* push argument array ['sh\x00'] */ + /* push 'sh\x00' */ + push 0x1010101 ^ 0x6873 + xor dword ptr [rsp], 0x1010101 + xor esi, esi /* 0 */ + push rsi /* null terminate */ + push 8 + pop rsi + add rsi, rsp + push rsi /* 'sh\x00' */ + mov rsi, rsp + xor edx, edx /* 0 */ + /* call execve() */ + push SYS_execve /* 0x3b */ + pop rax + syscall + +>>> filename = make_elf_from_assembly(asm_sh) +>>> filename +'/tmp/pwn-asm-ApZ4_p/step3' +>>> p = process(filename) +[x] Starting local process '/tmp/pwn-asm-ApZ4_p/step3' +[+] Starting local process '/tmp/pwn-asm-ApZ4_p/step3': pid 28429 +>>> p.sendline('echo hello') +>>> p.recv() +'hello\n' +``` +与上一个函数不同的是,`make_elf_from_assembly` 直接从汇编生成 ELF 文件,并且保留了所有的符号,例如标签和局部变量等。 #### elf +该模块用于 ELF 二进制文件的操作,包括符号查找、虚拟内存、文件偏移,以及修改和保存二进制文件等功能。(`pwnlib.elf.elf.ELF`) +```text +>>> e = ELF('/bin/cat') +[*] '/bin/cat' + Arch: amd64-64-little + RELRO: Full RELRO + Stack: Canary found + NX: NX enabled + PIE: PIE enabled +>>> print hex(e.address) +0x400000 +>>> print hex(e.symbols['write']) +0x401680 +>>> print hex(e.got['write']) +0x60b070 +>>> print hex(e.plt['write']) +0x401680 +``` +上面的代码分别获得了 ELF 文件装载的基地址、函数地址、GOT 表地址和 PLT 表地址。 + +我们常常用它打开一个 libc.so,从而得到 system 函数的位置,这在 CTF 中是非常有用的: +```text +>>> e = ELF('/usr/lib/libc.so.6') +[*] '/usr/lib/libc.so.6' + Arch: amd64-64-little + RELRO: Full RELRO + Stack: Canary found + NX: NX enabled + PIE: PIE enabled +>>> print hex(e.symbols['system']) +0x42010 +``` + +我们甚至可以修改 ELF 文件的代码: +```text +>>> e = ELF('/bin/cat') +>>> e.read(e.address+1, 3) +'ELF' +>>> e.asm(e.address, 'ret') +>>> e.save('/tmp/quiet-cat') +>>> disasm(file('/tmp/quiet-cat','rb').read(1)) +' 0: c3 ret' +``` + +下面是一些常用函数: +- `asm(address, assembly)`:汇编指定指令并插入到 ELF 的指定地址处,需要使用 ELF.save() 保存 +- `bss(offset)`:返回 `.bss` 段加上 `offset` 后的地址 +- `checksec()`:打印出文件使用的安全保护 +- `disable_nx()`:关闭 NX +- `disasm(address, n_bytes)`:返回对指定虚拟地址进行反汇编后的字符串 +- `offset_to_vaddr(offset)`:将指定偏移转换为虚拟地址 +- `vaddr_to_offset(address)`:将指定虚拟地址转换为文件偏移 +- `read(address, count)`:从指定虚拟地址读取 `count` 个字节的数据 +- `write(address, data)`:在指定虚拟地址处写入 `data` +- `section(name)`:获取 `name` 段的数据 +- `debug()`:使用 `gdb.debug()` 进行调试 + +最后还要注意一下 `pwnlib.elf.corefile`,它用于处理核心转储文件(Core Dump),当我们在写利用代码时,核心转储文件是非常有用的,关于它更详细的内容已经在前面 Linux基础一章中讲过,这里我们还是使用那一章中的示例代码,但使用 pwntools 来操作。 +``` +>>> core = Corefile('/tmp/core-a.out-30555-1507796886') +[x] Parsing corefile... +[*] '/tmp/core-a.out-30555-1507796886' + Arch: i386-32-little + EIP: 0x565cd57b + ESP: 0x4141413d + Exe: '/home/firmy/a.out' (0x565cd000) + Fault: 0x4141413d +[+] Parsing corefile...: Done +>>> core.registers +{'xds': 43, 'eip': 1448924539, 'xss': 43, 'esp': 1094795581, 'xgs': 99, 'edi': 0, 'orig_eax': 4294967295, 'xcs': 35, 'eax': 1, 'ebp': 1094795585, 'xes': 43, 'eflags': 66182, 'edx': 4151195744, 'ebx': 1094795585, 'xfs': 0, 'esi': 4151189032, 'ecx': 1094795585} +>>> print core.maps +565cd000-565ce000 r-xp 1000 /home/firmy/a.out +565ce000-565cf000 r--p 1000 /home/firmy/a.out +565cf000-565d0000 rw-p 1000 /home/firmy/a.out +57b3c000-57b5e000 rw-p 22000 +f7510000-f76df000 r-xp 1cf000 /usr/lib32/libc-2.26.so +f76df000-f76e0000 ---p 1000 /usr/lib32/libc-2.26.so +f76e0000-f76e2000 r--p 2000 /usr/lib32/libc-2.26.so +f76e2000-f76e3000 rw-p 1000 /usr/lib32/libc-2.26.so +f76e3000-f76e6000 rw-p 3000 +f7722000-f7724000 rw-p 2000 +f7724000-f7726000 r--p 2000 [vvar] +f7726000-f7728000 r-xp 2000 [vdso] +f7728000-f774d000 r-xp 25000 /usr/lib32/ld-2.26.so +f774d000-f774e000 r--p 1000 /usr/lib32/ld-2.26.so +f774e000-f774f000 rw-p 1000 /usr/lib32/ld-2.26.so +ffe37000-ffe58000 rw-p 21000 [stack] +>>> print hex(core.fault_addr) +0x4141413d +>>> print hex(core.pc) +0x565cd57b +>>> print core.libc +f7510000-f76df000 r-xp 1cf000 /usr/lib32/libc-2.26.so +``` + +#### dynelf +`pwnlib.dynelf.DynELF` + +该模块是专门用来应对无 libc 情况下的漏洞利用。它首先找到 glibc 的基地址,然后使用符号表和字符串表对所有符号进行解析,直到找到我们需要的函数的符号。这是一个有趣的话题,我们会专门开一个章节去讲解它。详见 *4.4 使用 DynELF 泄露函数地址* #### fmtstr +`pwnlib.fmtstr.FmtStr`,`pwnlib.fmtstr.fmtstr_payload` + +该模块用于格式化字符串漏洞的利用,格式化字符串漏洞是 CTF 中一种常见的题型,我们会在后面的章节中详细讲述,关于该模块的使用也会留到那儿。详见 *3.3.1 格式化字符串漏洞* #### gdb #### memleak -#### shellcraft +#### rop #### util diff --git a/src/Others/2.7_pwntools.sh b/src/Others/2.7_pwntools.sh new file mode 100644 index 0000000..e8ee311 --- /dev/null +++ b/src/Others/2.7_pwntools.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +V = 2.29 # binutils version +ARCH = arm # target architecture + +cd /tmp +wget -nc https://ftp.gnu.org/gnu/binutils/binutils-$V.tar.xz +wget -nc https://ftp.gnu.org/gnu/binutils/binutils-$V.tar.xz.sig + +# gpg --keyserver keys.gnupg.net --recv-keys C3126D3B4AE55E93 +# gpg --verify binutils-$V.tar.xz.sig + +tar xf binutils-$V.tar.xz + +mkdir binutils-build +cd binutils-build + +export AR=ar +export AS=as + +../binutils-$V/configure \ + --prefix=/usr/local \ + --target=$ARCH-unknown-linux-gnu \ + --disable-static \ + --disable-multilib \ + --disable-werror \ + --disable-nls + +make +sudo make install