来写写ByteCTF2021的misc部分的解题思路,有些是比赛的时候出的,有些带点脑洞是赛后复现的,总体来说质量还不错
HearingNotBelieving
音频题,看频谱图前半部分是二维码
手动补全即可
m4yB3_
后面音频听着像慢扫电视
继续手撸,纯体力活啦
故flag为:ByteCTF{m4yB3_U_kn0W_S57V}
BabyShark
看到baby,一开始试图全局搜索bytectf从而获得flag
结果找到一个这个,追踪进去发现是一个Kotlin的apk
导出后反编译失败,说没找到classes.dex
重新检查流量包的数据,发现多了数据WRTE…..
从而导致压缩包解析错误
从这,压缩包内的文件路径被WRTE块截断,从而不难看出wrte是24字节的冗余数据,手动剔除
手动删除完毕,再反编译
package com.bytectf.misc1;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.StrictMode;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import dalvik.system.DexClassLoader;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
public class MainActivity extends AppCompatActivity {
/* access modifiers changed from: protected */
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView((int) C0095R.layout.activity_main);
if (Build.VERSION.SDK_INT > 9) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().permitAll().build());
}
String decrypt = AesUtil.decrypt(getAESKey(getPBResp(), loadPBClass(getPBClass()).getMethods()[37]), "8939AA47D35006FB2B5FBDB9A810B25294B5D4D76E4204D33BA01F7B3F9D99B1");
}
public Class loadPBClass(String jarFilePath) {
try {
return new DexClassLoader(new File(jarFilePath).getAbsolutePath(), getDir("dex", 0).getAbsolutePath(), (String) null, getClassLoader()).loadClass("com.bytectf.misc1.KeyPB").getClasses()[0];
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public String getPBClass() {
if (ActivityCompat.checkSelfPermission(this, "android.permission.READ_EXTERNAL_STORAGE") != 0) {
ActivityCompat.requestPermissions(this, new String[]{"android.permission.READ_EXTERNAL_STORAGE"}, 1);
return HttpUrl.FRAGMENT_ENCODE_SET;
} else if (ActivityCompat.checkSelfPermission(this, "android.permission.WRITE_EXTERNAL_STORAGE") != 0) {
ActivityCompat.requestPermissions(this, new String[]{"android.permission.WRITE_EXTERNAL_STORAGE"}, 1);
return HttpUrl.FRAGMENT_ENCODE_SET;
} else if (Environment.getExternalStorageState().equals("mounted")) {
return Environment.getExternalStorageDirectory().getAbsolutePath() + "/PBClass.dex";
} else {
return HttpUrl.FRAGMENT_ENCODE_SET;
}
}
public long getAESKey(byte[] pb_bytes, Method parseFrom) {
try {
Object respObj = parseFrom.invoke((Object) null, new Object[]{pb_bytes});
return ((Long) respObj.getClass().getMethod("getKey", new Class[0]).invoke(respObj, new Object[0])).longValue();
} catch (Exception e) {
e.printStackTrace();
return -1;
}
}
public byte[] getPBResp() {
byte[] pb_res = new byte[0];
try {
return new OkHttpClient().newCall(new Request.Builder().url("http://192.168.2.247:5000/api").build()).execute().body().bytes();
} catch (IOException e) {
e.printStackTrace();
return pb_res;
}
}
}
主要是这里,其中getPBResp()中的api的结果可以从流量包中得到
然后难点就在于密钥生成的时候调用了getPBClass(),进而调用了PBClass.dex,这里的dex不能直接获取,但是要是换个思路,安卓下的PB,很容易想到ProtoBuf,想不到的话百度也不难搜到
而且,不难发现反编译得到的com.google包内就存在protobuf
那么就拿通过api得到的17字节的数据去解一下
显然1对应的就是符合long长度的key,去解一下
Lost Excel
excel文件,要溯源是谁泄露的,不难想到水印,先单独导出excel的水印图片后,发现0通道存在色点,但中间是有污染的
给了一个hint,提示说Block size = 8. Notice repeating patterns.
既然已经存在空域隐写了,再来频域转换可能性不大,故只考虑像素的规律
结合块大小为8,这里暂且先考虑是8*8的像素块,用ps分割一下看看
我们可以看到,刚巧每个8*8的像素块中最多有1个黑色块,且图中被红色框出的两块可以很明显的看出是重复的,所以其实我们只需使用上半部分即可,这样就可以避免掉原本中间被污染案的部分,再结合黑色像素块出现的位置(四种情况)可以对应转四进制,脚本如下
import cv2
img = cv2.imread(r'1.png')[:, :, 0]
h, w = img.shape
for j in range(0, h, 8):
for i in range(0, w, 8):
block = img[j:j + 8, i:i + 8]
if block[0, 0] == 0:
print(0, end='')
elif block[0, 4] == 0:
print(1, end='')
elif block[4, 0] == 0:
print(2, end='')
elif block[4, 4] == 0:
print(3, end='')
print('')
得到四进制,这里再转一下ascii
得到flag ByteCTF{ExcelHiddenWM}
frequently
拿到流量包,大致看看首先是dns的流量很奇怪
这个神似base64的域名不难想到有dns tunnel数据啊
还有一个io的也可以试试转01
这里先看看dns tunnel
值得注意的是流量中存在红色框中重复的,也有蓝色框中重复但id不同不能剔除的,故分别导出域名和id然后比对过滤
.\tshark.exe -r frequently.pcap -T fields -e dns.qry.name -Y “dns and ip.src==8.8.8.8 and frame.len!=91” > 0.txt
.\tshark.exe -r frequently.pcap -T fields -e dns.id -Y “dns and ip.src==8.8.8.8 and frame.len!=91” > 1.txt
然后结合起来剔除重复
dns = open(r'0.txt').read().splitlines()
id = open(r'1.txt').read().splitlines()
idli=[]
for i in range(len(dns)):
if id[i] not in idli:
idli.append(id[i])
print(dns[i].split('.')[0],end='')
print('')
得到base64了,decode一下
很明显的png图片啊,导出看看
恶俗出题壬….搁这放诱捕器呢
再有io对应转10
这里可看到长度都是91且源ip都是8.8.8.8,以此过滤
.\tshark.exe -r frequently.pcap -T fields -e dns.qry.name -Y “dns and ip.src==8.8.8.8 and frame.len==91” > res.txt
oi对应转01
s = open(r'res.txt').read().splitlines()
for i in s:
print(1, end='') if 'i' in i else print(0, end='')
print('')
010101000110100001100101001000000110011001101001011100100111001101110100001000000111000001100001011100100111010000100000011011110110011000100000011001100110110001100001011001110011101000100000010000100111100101110100011001010100001101010100010001100111101101011110010111110101111001100101011011100100101000110000011110010010011001111001001100000111010101110010
ok得到前半部分
然后继续,再去看看有什么流比较可疑
这里可以看到udp流有足足900+,且这里蓝色框中有一个内网ip向10.2.173.238发送过udp流,而10.2.173.238正是刚才发送dns解析的,故再来看看10.2.160.1
可以看到他发送了一堆dhcp报文
且报文的option字段还存在dns服务器是bytedance.net,十分可疑
进一步查看,他的租约期太短了吧,才几分钟
而且每次的租约时间都不同
故我们考虑获得dhcp.option.ip_address_lease_time字段(即租约时间)来看看
.\tshark.exe -r frequently.pcap -T fields -e dhcp.option.ip_address_lease_time -Y “ip.src==10.2.160.1” > res.txt
得到
115
115
101
49
102
95
119
73
116
104
95
109
49
115
99
94
95
94
125
简单转一下ascii
结合一下就行了