本文首发于安全客 [https://www.anquanke.com/post/id/259842

来写写ByteCTF2021的misc部分题目,有些是比赛的时候出的,有些带点脑洞是赛后复现的,总体来说水印部分的题跟去年感觉完全是两个风格,今年流量包题多了起来,音频题感觉就是体力活= =

HearingNotBelieving

音频题,看频谱图前半部分是二维码

image-20211016103040306

image-20211016112022155

手动补全

image-20211016103012269

image-20211016103002020

m4yB3_

后面音频听着像慢扫电视

image-20211016133026773

继续手撸

image-20211016133045855

image-20211016133056470

故flag为:ByteCTF{m4yB3_U_kn0W_S57V}

BabyShark

看到baby,试图全局搜索bytectf从而获得flag

image-20211016154255088

结果找到一个这个,追踪进去发现是一个Kotlin的apk

image-20211016154336592

导出后反编译失败,说没找到classes.dex

重新检查流量包的数据,发现多了数据WRTE.....

image-20211018151201582

image-20211018151228406

从而导致压缩包解析错误

image-20211018151340563

从这,压缩包内的文件路径被WRTE块截断,从而不难看出wrte是24字节的冗余数据,手动剔除

image-20211018154136686

手动删除完毕,再反编译

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的结果可以从流量包中得到

image-20211016160808130

然后难点就在于密钥生成的时候调用了getPBClass(),进而调用了PBClass.dex,这里的dex不能直接获取,但是要是换个思路,安卓下的PB,很容易想到ProtoBuf,想不到请百度

image-20211018200255874

而且,不难发现反编译得到的com.google包内就存在protobuf

image-20211018221647847

那么就拿通过api得到的17字节的数据去解一下

image-20211018195251128

显然1对应的就是符合long长度的key,去解一下

image-20211018220352267

image-20211018220405372

Lost Excel

excel文件,要溯源是谁泄露的,不难想到水印,先单独导出excel的水印图片后,发现0通道存在色点,但中间是有污染的

image-20211016141527488

给了一个hint,提示说Block size = 8. Notice repeating patterns.

既然已经存在空域隐写了,再来频域转换可能性不大,故只考虑像素的规律

结合块大小为8,这里暂且先考虑是8*8的像素块,用ps分割一下看看

image-20211018132759442

我们可以看到,刚巧每个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

image-20211018133557810

得到flag ByteCTF{ExcelHiddenWM}

frequently

拿到流量包,大致看看首先是dns的流量很奇怪

image-20211018134626152

这个神似base64的域名不难想到有dns tunnel数据啊

还有一个io的也可以试试转01

这里先看看dns tunnel

image-20211018134916986

值得注意的是流量中存在红色框中重复的,也有蓝色框中重复但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一下

image-20211016235022145

很明显的png图片啊,导出看看

image-20211016235027619

恶俗出题壬....搁这放诱捕器呢

再有io对应转10

image-20211018135204078

这里可看到长度都是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

image-20211018140007050

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

image-20211018140352941

ok得到前半部分

然后继续,再去看看有什么流比较可疑

image-20211018141528810

这里可以看到udp流有足足900+,且这里蓝色框中有一个内网ip向10.2.173.238发送过udp流,而10.2.173.238正是刚才发送dns解析的,故再来看看10.2.160.1

image-20211018141740588

可以看到他发送了一堆dhcp报文

且报文的option字段还存在dns服务器是bytedance.net,十分可疑

image-20211018142516767

进一步查看,他的租约期太短了吧,才几分钟

image-20211018142650282

而且每次的租约时间都不同

image-20211018142719139

故我们考虑获得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

image-20211018143736810

结合一下就行了

最后修改:2021 年 11 月 29 日 12 : 14 PM
如果觉得我的文章对你有用,请随意赞赏