卫星通信近几年逐渐火热,目前的低轨卫星通信大多数采用欧盟ETSI的数字卫星电视广播标准(DVB-S/DVB-S2 /S2X)并做改进。而通常它们用通用流封装,因此,在卫星安全的研究中,对一些数据的解析需要对GSE封装协议有所了解,所以近期对官方文档阅读并做出一些笔记。
通用流封装(GSE)协议可以在物理层对IP和其他网络层数据封装,在通用流上提供网络层数据包封装和分片功能。GSE 不仅可以灵活的分片和封装,而且能使用智能调度器来优化系统性能。不仅如此还:
PDU(协议数据单元)可以封装在一个GSE包中,也可以分成片段封装成几个GSE包。而GSE包的长度是动态可变的。GSE包可在不同基带桢中发送,基带桢长度是可变的。GSE没有独自的完整性校验机制。通常一段数据碎片化变成几个PDU片段,在最后一段加CRC-32。如图是DVB协议的GSE封装:
GSE包的头由固定长度(2bytes)和可变长度组成(0-11bytes)。固定长度主要有S 、E 、LT 、GSE Length等字段,可变部分主要是Fragment ID、Total Length、Protocol Type和Label等字段。如下图:
每个GS流可存放最多256个PDU碎片,每个GSE数据包的头有一个开始指示位(S)和结束指示位(E),开始指示位为“1”表示PDU开始,结束指示位为“1”表示PDU结束。如果都为1表示是一个完整的PDU。
而接下来有两位标签类型指示(主要用于寻址)。标签存在时,接收者可以删去标签不匹配的数据包。当为广播包时,接收者都应该处理该数据包。标签重复时,只有上一个GSE包的地址与现在匹配,接收者才去处理。所以标签重复主要用于同一个基带桢中。
变量 | 说明 |
---|---|
00 | 存在一个6字节标签 |
01 | 存在一个3字节标签 |
10 | 广播,不存在标签字段。 |
11 | 标签重复试用 |
接下来12位表示GSE包长度,最大2^12(4kb)。注:End包最后包含CRC-32。
Fragment ID只会在中间包出现,同一个PDU的GSE包有相同的ID。所以只有一个PDU传输完成,才可以有另一个PDU使用这个ID。
Total Length这个是保存PDU的总长度,所以通常在PDU碎片的第一个包中才有。总长度最高65536个字节,CRC-32不包含其中。
Protocol Type 0-0x5FF表示Next Header,0x600-0xFFFF表示 Ether Type。
为了防止PDU包的数据丢失,所以在PDU最后一个GSE包放入一个32位的CRC字段。定义的CRC多项式为
初始累积器为0xFFFFFFFF。然后PDU的字节、总长度、协议类型、标签、扩展头等计算。
Protocol Type定义了扩展头,扩展头属于数据的一部分,所以数据段的数据结构如下
如果有可选扩展头,跟在GSE头之后
如果有强制性扩展头,跟在可选扩展头之后
PDU跟在强制扩展头之后。
上面三部分都不是必须有的。
(如果PDU分为2个以上碎片时)
GSE封装器中的调度器,在基带桢中是智能放置,以提高效率。
如下图,PDU1、PDU2 和 PDU3 构成一个 PDU 序列,由调度器预先排定。(MODCOD 表示PDU 相关的调制格式和编码率)如果 MODCOD2 的效率高于 MODCOD1 的效率,那么我们应该采取 MODCOD1 对应的才会更稳定。PDU 被封装并由 GSE 封装器排入基带帧。
当 PDU 被分割时,像上图 PDU2 ,剩余的 PDU 片段被封装在一个独立的 GSE 包中,在下一个基带帧中传输。如果没有利用智能调度策略,PDU2剩余的包与PDU3的包封装在同一个基带桢里。基带桢不得不降级。而如下图操作,能实现更好的系统效率。
在实际应用中,卫星数据流可能受多种因素影响,可能会遇到数据丢失的情况,这对于数据解析产生一定的影响。我与朋友针对这一问题进行了探索,并在SpaceSec与Blackhat Arsenal上发表了解决方法,欢迎查看详细内容。
本文主要参考 ETSI 官方协议文档。
]]>pip install fridapip install frida-tools
也可以直接安装对应版本
pip install frida==x.x.xpip install frida-tools==x.x.x
直接进入 frida rlease 页面下载,这里要与安装的frida库版本对应,同时与手机架构对应。
可以通过命令frida-ps -R 检查是否成功。
import timeimport frida# 连接安卓机上的frida-serverdevice = frida.get_usb_device(10)# 启动`demo02`这个apppid = device.spawn(["com.minhal.demo2"])device.resume(pid)#通过pid重新启动time.sleep(1)session = device.attach(pid)# 加载a.js脚本with open("a.js") as f: script = session.create_script(f.read())#上一步连接到的session 去执行jsscript.load()# 脚本会持续运行等待输入input()
demo样例源代码。
package com.minhal.demo2;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;public class MainActivity extends AppCompatActivity { private String total = "@@@###@@@"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); while (true){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } fun(50,30); Log.d("Minhal.string" , fun("Fuck U!!!!!!!!!")); }} void fun(int x , int y ){ Log.d("Minhal.Sum" , String.valueOf(x+y)); } String fun(String x){ total +=x; return x.toLowerCase(); } String secret(){ return total; }}
把这段代码编译成apk后安装在测试机,连接到主机通过 可以查看系统日志。
然后接下来是js代码
console.log("Script loaded successfully ");Java.perform(function x() { console.log("Inside java perform function"); //定位类 console.log("begin"); Java.choose("com.minhal.demo2.MainActivity" , { onMatch : function(instance){ //该类有多少个实例,该回调就会被触发多少次 console.log("Found instance: "+instance); console.log("Result of secret func: " + instance.secret()); }, onComplete:function(){} }); console.log("end"); var my_class = Java.use("com.minhal.demo2.MainActivity"); var string_class = Java.use("java.lang.String"); //获取String类型 console.log("Java.Use.Successfully!");//定位类成功! //在这里更改类的方法的实现(implementation) my_class.fun.overload("int" , "int").implementation = function(x,y){ //打印替换前的参数 console.log( "original call: fun("+ x + ", " + y + ")"); //把参数替换成2和5,依旧调用原函数 var ret_value = this.fun(2, 5); return ret_value; } my_class.fun.overload("java.lang.String").implementation = function(x){ console.log("*************************************"); var my_string = string_class.$new("My TeSt String#####"); //new一个新字符串 console.log("Original arg: " +x ); var ret = this.fun(my_string); // 用新的参数替换旧的参数,然后调用原函数获取结果 console.log("Return value: "+ret); console.log("*************************************"); return ret; }; });
执行脚本
查看日志变化
这个实力主要是实现在py脚本中也可以调用secret函数。这里主要是使用的frida提供的RPC功能(Remote Procedure Call)
apk文件还是上一个样例的文件。
现在修改下js脚本。
console.log("Script loaded successfully ");function callsecretFun(){ Java.perform(function x() { console.log("Inside java perform function"); //定位类 console.log("begin"); Java.choose("com.minhal.demo2.MainActivity" , { onMatch : function(instance){ //该类有多少个实例,该回调就会被触发多少次 console.log("Found instance: "+instance); console.log("Result of secret func: " + instance.secret()); }, onComplete:function(){} }); console.log("end"); });}rpc.exports = { callsecretfunction:callsecretFun//把callSecretFun函数导出为callsecretfunction符号,导出名不可以有大写字母或者下划线};
然后修改对应的py代码
import timeimport frida# 连接安卓机上的frida-serverdevice = frida.get_usb_device(10)# 启动`demo02`这个apppid = device.spawn(["com.minhal.demo2"])device.resume(pid)time.sleep(1)session = device.attach(pid)# 加载s1.js脚本with open("a.js") as f: script = session.create_script(f.read())script.load()command = ""while 1 == 1: command = input("Enter command:\n1: Exit\n2: Call secret function\nchoice:") if command == "1": break elif command == "2": #在这里调用 script.exports.callsecretfunction()
运行python脚本得到下面内容:
这里主要实现的功能不仅仅是可以用python调用app的函数。还要做到把数据从app传到python程序中,通过python代码修改传回到app里。
app代码:
package com.minhal.demo3;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.util.Base64;import android.view.View;import android.widget.EditText;import android.widget.TextView;public class MainActivity extends AppCompatActivity { EditText username_et; EditText password_et; TextView message_tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); password_et = (EditText) this.findViewById(R.id.editText2); username_et = (EditText) this.findViewById(R.id.editText); message_tv = ((TextView) findViewById(R.id.textView)); this.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (username_et.getText().toString().compareTo("admin") == 0) { message_tv.setText("You cannot login as admin"); return; } //hook target message_tv.setText("Sending to the server :" + Base64.encodeToString((username_et.getText().toString() + ":" + password_et.getText().toString()).getBytes(), Base64.DEFAULT)); } }); }}
app界面
接下来操作是python代码获取输入内容,并修改输入内容然后传输到app,通过验证。(包括admin)
js代码主要实现是先截到输入内容,传输到python代码,然后等python传入新数据继续执行。
js代码
console.log("Script loaded successfully ");Java.perform(function () { var tv_class = Java.use("android.widget.TextView"); tv_class.setText.overload("java.lang.CharSequence").implementation = function (x) { var string_to_send = x.toString(); var string_to_recv; console.log("Script loaded successfully "); send(string_to_send); // 将数据发送给python的python代码 recv(function (received_json_object) { string_to_recv = received_json_object.my_data console.log("string_to_recv: " + string_to_recv); }).wait(); //收到数据之后,再执行下去 var my_string = Java.use("java.lang.String").$new(string_to_recv); this.setText(my_string); }});
python代码
import timeimport fridaimport base64def my_message_handler(message, payload): print(message) print(payload) if message["type"] == "send": print (message["payload"]) data = message["payload"].split(":")[1].strip() print(data) print ('message:', message) data = str(base64.b64decode(data)) user,pw = data.split(":") print("user:",user) data =str(base64.b64encode(("admin" + ":" + pw).encode())) print ("encoded data:", data) script.post({"my_data": data}) # 将JSON对象发送回去 print ("Modified data sent")# 连接安卓机上的frida-serverdevice = frida.get_usb_device(10)# 启动`demo02`这个apppid = device.spawn(["com.minhal.demo3"])device.resume(pid)time.sleep(1)session = device.attach(pid)# 加载a.js脚本with open("a.js") as f: script = session.create_script(f.read())script.on("message", my_message_handler)script.load()input()
直接运行python代码,然后输入新的用户名和密码,我们原程序是本来不可以输入admin的,我们本代码就是通过输入其他内容,通过frida更改他的用户名参数,使得输入内容用户名为admin。
执行结果如下:
然后就实现了动态内容的修改。
Android Application Security Study [https://github.com/r0ysue/AndroidSecurityStudy]
Frida Android hook[https://eternalsakura13.com/2020/07/04/frida/]
2020年过的有些仓促,这一年发生了很多事情。这一年或许是近些年的一个转折点,对于自己也算一直很重要的节点吧。一直想写个总结,总算开始敲起了流水账。2021新的一年,也要加油了。
“新冠”是2020最热的事件。2020年年初考完试兜兜转转最终9号还是踏上了回家的路。从知乎看到武汉有确诊不明传染病到回到家后的越来越严重。当时也没想到自己正在经历一场会影响全球的大事件。疫情让2020的春节不一样,不串门窝在家里看到的是大家晒吃的,那段时间闷不住了就是出去去自家后山上面走路散步。那段时间,时常由于焦虑感到胸闷,误以为自己得了新冠。
2020也是第一次体会到在“家里蹲”上大学,还记得第一课的计网,早早起床准备着上课,之后也逐渐变的懒散起来。由于疫情在家上课也使得自己体重上升,在家上课没有了想家的烦恼,过得格外快。在听到9月返校的时候,毫无犹豫订到了去往学校的车票。
国庆节,一个人去了武大的疫情纪念馆,看到了华西的医疗队伍,莫名的有些亲切感。看到几岁小朋友给护士写的信,忍不住落泪;看到一线医护人员穿过的战袍,用的器械。感受到的都是祖国的强大。
2021年,也希望疫情逐渐过去。在2020的尾声,国家卫健委也宣布了疫苗免费的好消息。赞!
2020年,参加了很多竞赛,虽然疫情原因很多竞赛取消了。从年初开始准备的国赛作品赛;虽然国赛作品赛被很多人称为PPT大赛,但还是收获颇多。项目开发,共同柔和;文字打磨,答辩等都是一次又一次的修改。在最终答辩“翻车”中,还是功夫不负有心人拿到了一等奖和国内仅2个的最具创新价值奖。通过这个奖被保推到了互联网+全国总决赛。9月份,就在互联网+的忙碌中度过。
9月几乎没上过课,一直在望江备战决赛。互联网+的比赛比起之前的技术类比赛显然风格不一样,所以经验不足。好在在学长的帮助组成了一个团队。这个月里,每天都挺累的。常常熬夜,偶尔也会通宵。最终比赛结果也没能进入围。但这个月确实收获颇多。这一个月如梦一般,相似体验了一次创业;了解到了很多公司的运作,产品模式。也见到了很多大佬。这一次无成本的“创业”经历也是大学里最难忘的吧。
作品赛之外主要就是参加CTF竞赛,省赛今年如愿拿到了一等奖;国赛不尽人意,有自己的失误,有比赛场上环境的变化,最终结果不是太好。如果明年有机会也可能重新冲回来。之后还在西湖论剑与很多朋友面基,第一次参加IoT竞赛经验还是不足,需要总结的还是很多。
这一年,协会发展也很不错。从年中开始,跟小伙伴一起把协会发展模式改变,办两次比赛也发现了后生力量。希望2021年,协会不出现断层现象吧,发展越来越好。
这一年的技术长进感觉不是很满意,很多目标也没怎么做好。知乎,公众号可能是很大一部分知识的来源;对于根基知识与知识架构的建立还是有很大的不足,在2021年要改变现状,多看一些书籍来构建知识网络。大学以来,也不怎么看技术外的书了,这一年也逐渐明白到技术不是唯一,在2021一年还是需要建起那些非技术书籍增加一些人文素养。
2020年已去,2021年出发!
]]>最近在逆向一个某工程性的APK文件,由于加了一些混淆和其他原因,需要动态调试理解一些关键代码,于是搭建了android studio调试环境。
APK的调试有很多方法,个人还是习惯Android Studio 配合JEB的伪代码来进行调试。
首先需要使用工具反编译apk。
可以直接用Apktool反编译
也也可以通过Android Killer反编译(原理其实就是集成Apttool)
还可以使用 java -jar apktool.jar d MyApp.apk 命令调用apktool。
Android Studio3.x已经自带反编译,所以可以直接导入。
如果没用自AS自带的反编译,可以选择Import Project导入
一直选择“Next“。
安装smaliidea插件,来对smali代码进行处理。
AndroidManifest.xml文件中在application中改为true(如果没有添加上):
android:debuggable=”true”
在AndroidStudio工程中右键点击smali文件夹,设定Mark Directory as -> Sources Root
如果之前改过代码,这步要重新编译签名程序。
java -jar apktool.jar b MyApp -o newMyApp.apk
启动DDMS,在Android Studio 3.x可能在tools菜单找不到DDMS,可以直接在terninal 输入 monitor启动。
打开后可能会遇到
这个时候可以按照提示改下端口号。
如果改了还不行可以先启动monitor,再打开Android Studio即可。
在AndroidStudio里面配置远程调试的选项,选择Run -> Edit Configurations
然后选择加号新建一个远程调试
然后进行配置
配置完成后即可调试,直接在想调试的地方下断点。在DDMS中选择要调试的程序。
然后Run->Debug ‘name’来启动调试。
如果通过模拟器来调试可以参考模拟器连接端口
一个mips64架构的Re题目。
检查文件是个一个mips64大端程序,穷人用不起IDA pro 7.5,只能上ghidra来进行反编译操作。
最后多出来的0A是个换行符。
ld =[s2]lc = [s1]for i in range(31): ld.append((rright(ld[i],8) + lc[i] ^ i)&0xffffffffffffffff ) lc.append(rright(lc[i],61) ^ ld[i+1])for i in range(31,-1,-1): x1 = rright(x1^x2,3) x2 = rright(((x2^lc[i])-x1)&0xffffffffffffffff,56) return x1,x2
while循环中,srand未知,输出结果已知,输入内容未知。所以我们逆向出这个运算,还是需要得到srand值,才可能解决题目
srand 可以采取爆破的形式,由于比赛flag形式为RCTF{xxxxx},所以我们可以根据第一组数据进行爆破,得到srand。每个srand255(无符号)种情况,255*255总共65535种情况。
for i in range(65536): s1 = i s2 = 0 s1,s2 = struct.unpack('QQ',struct.pack('>QQ',s1,s2)) x1,x2 = reverse(lists2[0],lists2[1],s1,s2) str1 = struct.pack('>Q',x1) if 'RCTF' in str1: print(i) break
得到srand,然后直接逆向解决就好了。
for i in range(len(lists2)/2): s1,s2 = struct.unpack('QQ',struct.pack('>QQ',4980,0)) x1,x2 = reverse(lists2[2*i],lists2[2*i+1],s1,s2) flag += struct.pack('>Q',x1) flag += struct.pack('>Q',x2)print (flag)
import structdef encrypt(a,b,c,d ): b = (rright(b,8) + a ^ c)&0xffffffffffffffff a = rright(a,61) ^ b for i in range(0x1f): d = (rright(d,8) + c ^ i)&0xffffffffffffffff c = rright(c,61) ^ d b = (rright(b,8) + a ^ c)&0xffffffffffffffff a = rright(a,61) ^ b return a,b def recerse(x1,x2,s1,s2): ld =[s2] lc = [s1] for i in range(31): ld.append((rright(ld[i],8) + lc[i] ^ i)&0xffffffffffffffff ) lc.append(rright(lc[i],61) ^ ld[i+1]) for i in range(31,-1,-1): x1 = rright(x1^x2,3) x2 = rright(((x2^lc[i])-x1)&0xffffffffffffffff,56) return x1,x2def rright(v,n): return ((v >> n) + (v << (64-n)))&0xfffffffffffffffflists = [0x2A, 0x00, 0xF8, 0x2B, 0xE1, 0x1D, 0x77, 0xC1, 0xC3, 0xB1, 0x71, 0xFC, 0x23, 0xD5, 0x91, 0xF4, 0x30, 0xF1, 0x1E, 0x8B, 0xC2, 0x88, 0x59, 0x57, 0xD5, 0x94, 0xAB, 0x77, 0x42, 0x2F, 0xEB, 0x75, 0xE1, 0x5D, 0x76, 0xF0, 0x46, 0x6E, 0x98, 0xB9, 0xB6, 0x51, 0xFD, 0xB5, 0x5D, 0x77, 0x36, 0xF2]lists2 =[]for i in lists: lists2.append(chr(i))lists2 = struct.unpack('>QQQQQQ',''.join(lists2))for i in range(65536): s1 = i s2 = 0 s1,s2 = struct.unpack('QQ',struct.pack('>QQ',s1,s2)) x1,x2 = reverse(lists2[0],lists2[1],s1,s2) str1 = struct.pack('>Q',x1) if 'RCTF' in str1: print(i) break flag = ''for i in range(len(lists2)/2): s1,s2 = struct.unpack('QQ',struct.pack('>QQ',4980,0)) x1,x2 = reverse(lists2[2*i],lists2[2*i+1],s1,s2) flag += struct.pack('>Q',x1) flag += struct.pack('>Q',x2)print (flag)
这个题目比较坑的两个地方是注意大小端,还有就是加号与异或运算优先级,如果这两个搞错,很容易被卡住的。
]]>本身作为签到题就没必要太刁难人,主要考察脱压缩壳(re选手基础技能),正好在4月的脱壳分享会也说了要出一道这种题。脱完壳后就打算搞个简单的加减乘除,但是还是出题时候考虑不周,出现了多解的情况。(按照正常思路一般都是一个解。)
问题主要出在当时做了一个除法的操作,因为C语言中5/2 与4/2都为2。
第一步,查壳,发现为upx。
直接可以用脱壳软件脱壳也可esp定律等手动脱壳。
脱壳后分析代码。
直接写脚本
#include<stdio.h>#include<string.h>int main(){ char fstr[17] = "pbm`KkL`dKQ2KeJLd"; char theflag[17];char flag[17] = "scu_ctf_f4k3_f14g";int i = 0; for(i = 0; i < 17; i++) theflag[i] = fstr[i]*2-flag[i]; for(i = 0; i < 17; i++) printf("%c",theflag[i]); }
import angrp = angr.Project('sign.exe', auto_load_libs=False)st = p.factory.call_state(addr=0x401520, add_options=angr.options.unicorn)sim = p.factory.simgr(st)sim.explore(find=0x40155e, avoid=0x40156c)print(sim.one_found.posix.dumps(0))
这个地方就可以看出,有多解情况了。
这题是由God sun出的,大概主要考察一个.net,加之让比赛变的更有趣一点,放了个小游戏上去。只要打完180个灰机(一个不落)控制台就会输出flag
(180个飞机,无需逆向,轻轻松松就可以打败。
经过分析可以得知,每击落一架分级,调用一次这个关键方法。由代码可以看到总共需要摧毁了180个。(其实总共也就180个)
写解题脚本
import hashlibmask =[49552,26516,15988,29987,52902,33151,8086,39920,3604,21497,19862,12268,50822,26111,35391,20661,6370,14029,26707,42890,19391,13836,61102,38705,45159,12927,47794,39183,20776,44532,18925,4854,60596,11941,28994,11166,57586,48918,13199,42006,62781,31480,50464,53893,21233,61456,55842,46591,10574,45253,50991,44866,45945,17105,27273,18925,41001,64310,51846,46279,14977,61079,26330,1192,61190,38989,36161,17001,38576,49567,55929,31759,54550,12759,13756,60929,36365,27308,57132,42483,42263,57086,55839,13568,37191,18388,34592,4189,65492,24673,27016,6941,33229,4180,35454,64874,36708,22948]l = len(mask)secret = "jFEQ6xFkUxKGzUbn"for i in range(1,181): secret = hashlib.md5((secret+str(mask[i%l])).encode()).hexdigest() if '6a37460f25c719a4' in secret: print (secret[0:16])
注意这里很多选手以为只调用一次,所以直接拿180%98 去处理,算出来的是错的。
这个题目出题主要想考察一下python的逆向,校内打校外比赛的不多,见得题目相对较少。所以本着拓宽学习的目的,出了这道还原字节码的题目。相对来说这道题不是太难,通过相关博客搜索,然后一步步分析还原,还原后dis检验。
首先直接还原python代码就好了,还原结果如下
inputs = input("please your flag:")inputs = inputs[7:-1]flag = "th31_scuctf_eXclus1v3"theflag = ""i = 0j =0 print(flag[0])if(len(flag) != len(inputs)): print("Error!")for i in range(0,len(flag)-14): theflag += (chr(ord(flag[i])+ord(inputs[i+8])))for i in range(10,len(flag)-6): theflag += (chr(ord(flag[i])+ord(inputs[i-8]))) j = i+1for i in range(j,len(flag)): theflag += (chr(ord(flag[i-3])+ord(inputs[i])))flags =list(theflag)for i in range(0,len(flags)//2): flags[i] = chr(ord(flags[i])+20)#Flag scuctf{}#The flag text starts with "d1" and the eighth bit is "3"flagt = flags[len(flags)//2:len(flags)]theflag = "".join(flagt)for k in range(0,len(flags)//2): theflag += "".join(flags[k])if(theflag == '×\x8bÙÍ\x8cÓÜî¤ú±¬¤¤úÖíÒ'): print("You win!")else: print("Error!!!")
接着就是逆向分析,写解题脚本
enflag = '×\x8bÙÍ\x8cÓÜî¤ú±¬¤¤úÖíÒ'flag = 'th31_scuctf_eXclus1v3'ans = 'd1' + '*' * 19step1 = enflag[9:] + enflag[0:9]theflag =''for i in range(0,9): theflag += chr(ord(step1[i]) - 20)theflag += step1[9:]inputs = list(ans)for i in range(0,7): inputs[i + 8] = chr(ord(theflag[i]) - ord(flag[i]))for i in range(10,15): inputs[i - 8] = chr(ord(theflag[i - 3]) - ord(flag[i]))for i in range(15,21): inputs[i] = chr(ord(theflag[i - 3]) - ord(flag[i - 3]))inputs[7] = '3'print('scuctf{' + ''.join(inputs) + '}')
from z3 import *flag = "th31_scuctf_eXclus1v3"dist = "×ÙÍÓÜî¤ú±¬¤¤úÖíÒ"inp = [BitVec(('x%s' % i), 8) for i in range(len(flag))]theflag = []for i in range(0, len(flag) - 14): theflag.append(ord(flag[i]) + inp[i + 8])for i in range(10, len(flag) - 6): theflag.append(ord(flag[i]) + inp[i - 8])for i in range(len(flag) - 6, len(flag)): theflag.append(ord(flag[i - 3]) + inp[i])flags = [_ for _ in theflag]for i in range(len(flags) // 2): flags[i] = flags[i] + 20theflag = theflag[len(flags) // 2:]for i in range(len(flags) // 2): theflag.append(flags[i])solver = Solver()for i in zip(theflag, dist): solver.append(i[0] == ord(i[1]))solver.check()model = solver.model()for i, v in enumerate(inp): try: print(chr(model[v].as_long()), end='') except: print(' ', end='')
本道题主要就是考察一个ollvm平坦化。也没想到这么惨烈。
文件拉到最后就可以看到编译器地址,直接把它pull下来,编译.s文件得到可执行文件。
拖入IDA分析,是个标准的平坦化。
参考<https://github.com/pcy190/deflat >去除平坦化
然后直接F5写解密脚本
from z3 import *from functools import reducetable = 'ZAnUX1W2oPNQ4sBMOd/+ChfGI5r8Hvt3uaLkbDgcyJYTipez6mxF0SEqRjVKwl97'coding = '5auRs6a4A2lEUObG5+uoPGuWHnimZLXtvkyEHxCFoal5'dist = map(lambda x: BitVecVal(table.find(x), 6), coding)flag = [BitVec('c%d' % i, 8) for i in range(32)]total = Concat(flag)s = [Extract(32 * (i + 1) - 1, 32 * i, total) for i in range(8)]temps = reduce(lambda x, y: x ^ y, s, 0)s = [i ^ temps for i in s]s.reverse()total = Concat(s)bits = [Extract(8 * (i + 1) - 1, 8 * i, total) for i in range(32)]bits = bits + [reduce(lambda x, y: x ^ y, bits)]tup = [bits[i:i + 3] for i in range(0, len(bits), 3)]outs = []padding = BitVecVal(0, 2)for i, v in enumerate(tup): t = Concat(v) s1 = Extract(23, 18, t) s2 = Extract(17, 12, t) s3 = Extract(11, 6, t) s4 = Extract(5, 0, t) outs.append(s1) outs.append(s2) outs.append(s3) outs.append(s4) for v2 in tup[i + 1:]: v2[0] = v2[0] ^ Concat(padding, s1) v2[1] = v2[1] ^ Concat(padding, s2) v2[2] = v2[2] ^ Concat(padding, s3)solve = Solver()for i, v in enumerate(dist): solve.add(outs[i] == v)solve.check()model = solve.model()print(''.join(map(lambda x: chr(model.eval(x, 8).as_long()),reversed(flag))))
既然要搞花样,当然少不当今最火的iot。采用腾讯TencentOS tiny 官方定制IoT开发板EVB_LX(暂时是限量的)编译环境: https://github.com/Tencent/TencentOS-tiny两个题目,都是考察找到被替换的base64密码表,由于考虑到直接上base有点难,所以出了一个easy_re过渡。
两个题目替换都涉及四段字符如下(把初始密码表拆分为四段):
“ABCDEFGHIJKLMNOPQRSTUVWXYZ”
“abcdefghijklmnopqrstuvwxyz”
“0123456789”
“+/“
easy_re是改变了这四段字符压栈顺序。没想到ida太过于智能化,显示结果即是正确压栈顺序。
easy_base考察偏难了,主要是对这四段字符进行了一些变换,如果逆向分析的话需要学习risc-v指令集。
当然,这两个题最简单的方法是把程序放入对应开发板里,他相应的串口也会输出字母表。
做题过程中也发现一些选手拿到题目直接猜测arm架构,拿着ida 当arm分析,还原的内容是错的,无从下手。如果拿到文件后File一下也会知道是risc-v架构。不至于走偏。
首先,ida默认不支持risc-v,所以需要下载相关插件。https://github.com/lcq2/riscv-ida
然后,ida打开分析,直接就有正确的字母表压栈顺序,(原本是想让选手分析简单指令来确定或者爆破)
得到 字母表就很容易解出来了
import base64str1 = "PalXPrhnOrLZT6PVQJ1oNr9dSqDVTbo=="string1 ="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+/abcdefghijklmnopqrstuvwxyz"string2 ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"print(base64.b64decode(str1.translate(str.maketrans(string1, string2))))
easy_base 的话就需要分析指令得出具体操作或者直接开发板跑一下得到输出
看到大多数人解题无果,比赛最后放出了一个risc-v 64位的附件(代码一样),通过docker跑即可得到table。
然后直接解密得到flag
import base64str1 = "UoH+U/DJV/YlQdUOU94JPYxJgdHMUWK="string1 ="a0b1c2d3e4f5g6h7i8j9ZYXWVUTSRQPON+klmnopqrABCDEFGHIJKLM/stuvwxyz"string2 ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"print(base64.b64decode(str1.translate(str.maketrans(string1, string2))))
如果对题目感兴趣的,可以之后再研究。我附上题目主文件源代码
#include "mcu_init.h"#include "tos_k.h"#define TASK_SIZE 1024k_task_t k_task_task1;k_task_t k_task_task2;uint8_t k_task1_stk[TASK_SIZE];uint8_t k_task2_stk[TASK_SIZE];int share = 0xCBA7F9;k_sem_t sem;unsigned char *scuctf_flag_base64="UoH+U/DJV/YlQdUOU94JPYxJgdHMUWK=";unsigned char base64_right[65]="";void scuctf_base64(void){unsigned char base64_1[26]="ABCDEFGHIJKLMNOPQRSTUVWXYZ";unsigned char base64_2[26]="abcdefghijklmnopqrstuvwxyz";unsigned char base64_3[10]="0123456789";unsigned char base64_4[2]="+/";int i=0,j=0,k=0,q=25,r=10,n=0;for(i=0;i<20;){base64_right[i]=base64_2[j];j++;base64_right[i+1]=base64_3[k];k++;i+=2;}for(i=20;i<33;i++){base64_right[i]=base64_1[q];q--;}for(i=33;i<42;i++){if(i==33){base64_right[33]=base64_4[0];}else{base64_right[i]=base64_2[r];r++;}}for(i=42;i<64;i++){if(n<13){base64_right[i]=base64_1[n];n++;}else{if(i==55){base64_right[i]=base64_4[1];}else{base64_right[i]=base64_2[r];r++;}}}}void task1(void *pdata){ int task_cnt1 = 0; while (1) { printf("welcome scuctf from %s cnt: %d\n", __func__, task_cnt1++); tos_sem_pend(&sem, ~0U); gpio_bit_write(GPIOA, GPIO_PIN_7, share % 2); }}void task2(void *pdata){ int task_cnt2 = 0; scuctf_base64(); while (1) { share++; for(int i=0; i<5; i++) { printf("Where is scuctf_base64? %s cnt: %08x\n%s", __func__, task_cnt2--,base64_right); tos_task_delay(50); } tos_sem_post(&sem); }}void main(void) { board_init(); usart0_init(115200); tos_knl_init(); tos_task_create(&k_task_task1, "task1", task1, NULL, 3, k_task1_stk, TASK_SIZE, 0); tos_task_create(&k_task_task2, "task2", task2, NULL, 3, k_task2_stk, TASK_SIZE, 0); k_err_t err = tos_sem_create(&sem, 1); if (err != K_ERR_NONE) { goto die; } tos_knl_start();die: while (1) { asm("wfi;"); }}int _put_char(int ch){ usart_data_transmit(USART0, (uint8_t) ch ); while (usart_flag_get(USART0, USART_FLAG_TBE)== RESET){ } return ch;}
由于是普通校赛,题目也没出过分难,个人感觉难以把握还算可以。这次题目主要也本着打破传统scuctf 常规题目,一丢丢小小的创新。 .NET,risc-v,ollvm,apk,python等。即使这些可能在全国ctf中是常见题目,但是感觉校内还是几乎没出的。比赛过程中也发生了很多趣味东西,比如第一题一题多解,flag设置时候多加了空格导致选手提交报错等好多问题。
总之希望scuctf越来越有趣,参与人数越来越多吧!
]]>年轻爱折腾,从wordpress—>typecho—>hexo,追求越来越轻量。
网站托管于zeit+github,(zeit真香,速度相对快一些)
sudo apt-get install git
sudo apt-get install nodejssudo apt-get install npm
npm install -g hexo-cli
初始化hexo
blog是文件名
hexo init blog
进入blog文件
npm install
直接通过下面命令本地查看效果
hexo ghexo server
github 新建公开仓库
githubname.github.io
git config --global user.name "yourname"git config --global user.email "youremail"##检查git config user.namegit config user.emailssh-keygen -t rsa -C "youremail"
生成.ssh 文件
id_rsa.pub复制到github—>setting—>SSH keys
下面命令检查成功与否
ssh -T git@github.com
_config.yml文件打开,最下面修改
# Docs: https://hexo.io/docs/deployment.htmldeploy: type: git repository: git@github.com:githubname/githubname.github.io.git branch: master
安装deploy-git
npm install hexo-deployer-git --save
# 清除生成hexo clean#生成静态文件hexo g#部署到githubhexo d
npm install hexo-generator-sitemap --save #sitemap.xml googlenpm install hexo-generator-baidu-sitemap --save #baidusitemap.xml百度
在themes\next\layout\_partials\head.swing
中添加百度站长验证代码
谷歌在https://search.google.com/search-console 添加站点信息
_config.yml 添加
sitemap:path: sitemap.xmlbaidusitemap:path: baidusitemap.xml
npm install hexo-generator-feed###_config.ymlplugins: hexo-generator-feed#Feed Atomfeed: type: atom path: atom.xml limit: 20
为什么用zeit?
github国内访问相对较慢,所以直接托管到了zeit上面。
通过ZEIT使用github登陆
Import Project 导入github博客仓库。
接着全部默认即可。
最后生成一个xxx.now.sh域名
cname 添加记录绑定自己域名
详细介绍可以参考帮助文档
最开始,博客的图床是随便在百度找的,后来发现一些小图床平台不稳。
换了七牛云,用来用去。还是觉得github免费香。
然后在github个人设置生成token,记得保存token
picgo直接到https://github.com/Molunerfinn/PicGo/releases下载安装
接着到图床设置—>github图床设置。
- 仓库名为ID/仓库名
- 分支默认master(github现在默认分支可能为main)
- token之前申请的
- 路径可以自己设置
- 自定域名可以使用jsdelivr加速,设置方法https://cdn.jsdelivr.net/gh+用户id+仓库名
这个方法使用后,可能导致你的github工作日志一片绿。(经常上传图片)
可能还有一个问题,这个属不属于github仓库滥用呢。v2ex有朋友询问了官方。贴上官方回信。
]]>Hi Haoxun Zhan,
Thanks for your question! We’ve reviewed your project and, in addition to uploading files, it appears to assist in generating rawgit URLs. Is that correct?
If that’s the case, your project doesn’t appear to violate GitHub’s Terms of Service, though you may want to check in with the owner of rawgit if you haven’t already done so.
Of course, any individual who decided to use your code would be responsible for making sure their usage and content didn’t violate our Terms.
Please let me know if you have any other questions.
Best,
Elizabeth
博客再次迁移!
]]>