zoukankan      html  css  js  c++  java
  • ByteCTF 2021 bytecert Writeup

    证书校验

    @Override  // javax.net.ssl.X509TrustManager
    public void checkServerTrusted(X509Certificate[] arg4, String arg5) {
        int v1 = 0;
        try {
            while (v1 < arg4.length) {
                arg4[v1].checkValidity();
                ++v1;
            }
    
            String v4 = arg4[0].getSubjectDN().getName();
            Log.d("BYTECERT", v4);
            if ((v4.contains("bytedance.com")) && (v4.contains("字节跳动"))) {
                return;
            }
        } catch (Exception unused_ex) {
        }
    
        throw new CertificateException("Bad Cert");
    }
    

    checkServerTrusted 检查证书时间是否有效,以及证书 subject 是否包含 bytedance.com字节跳动,但是后续没有 CA 校验。

    域名校验

    @Override  // javax.net.ssl.HostnameVerifier
    public boolean verify(String arg2, SSLSession arg3) {
        return b.a.verify("bytedance.com", arg3);
    }
    

    HostnameVerifier 检查证书 dNSName 是否包含 bytedance.com

    伪造证书

    使用 OpenSSL 构造满足上述要求的 X.509 证书即可。

    openssl req -x509 -nodes -days 730 -newkey rsa:2048 -keyout server-key.pem -out server-cert.pem -utf8 -config <(
        cat <<-EOF
        [req]
        default_bits  = 2048
        distinguished_name = dn
        req_extensions = req_ext
        x509_extensions = v3_req
        prompt = no
        [ dn ]
        C=CN
        ST=bytedance.com
        L=bytedance.com
        O=字节跳动
        OU=bytedance.com
        CN = bytecert.gwyn.me
        [ req_ext ]
        subjectAltName = @alt_names
        [v3_req]
        subjectAltName = @alt_names
        [ alt_names ]
        DNS.1 = bytedance.com
        DNS.2 = gwyn.me
        DNS.3 = *.gwyn.me
        DNS.4 = bytecert.gwyn.me
        EOF
    )
    

    客户端交互

    使用伪造的 X.509 证书启动 HTTPS 服务,截获请求参数后得到 flag 的第一部分。

    import http.server, ssl
    
    class HookHandler(http.server.BaseHTTPRequestHandler):
        def _set_response(self):
            self.send_response(200)
            self.send_header('Content-type', 'text/html')
            self.end_headers()
    
        def do_GET(self):
            print("GET request,
    Path: %s
    Headers:
    %s
    ", str(self.path), str(self.headers))
            self._set_response()
            self.wfile.write("GET request for {}".format(self.path).encode('utf-8'))
    
        def do_POST(self):
            content_length = int(self.headers['Content-Length'])
            post_data = self.rfile.read(content_length)
            print("POST request,
    Path: %s
    Headers:
    %s
    
    Body:
    %s
    ",
                    str(self.path), str(self.headers), post_data.decode('utf-8'))
    
    server_address = ('0.0.0.0', 8080)
    httpd = http.server.HTTPServer(server_address, HookHandler)
    
    httpd.socket = ssl.wrap_socket(httpd.socket,
                                   server_side=True,
                                   keyfile='server-key.pem',
                                   certfile='server-cert.pem',
                                   ssl_version=ssl.PROTOCOL_TLS)
    
    httpd.serve_forever()
    

    服务端交互

    服务端要求请求数据中的 packnamecom.ss.android.ugc.aweme,且 packsign 的内容与之相对应。

    这里 com.ss.android.ugc.aweme 对应的是抖音客户端的包名,使用 JEB 导出抖音客户端的签名即可。

    得到返回消息后使用 AES 进行解密得到 flag 的第二部分。

    package com.company;
    import javax.crypto.Cipher;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    import javax.net.ssl.HttpsURLConnection;
    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.net.URI;
    import java.net.URL;
    import java.net.URLEncoder;
    import java.net.http.HttpClient;
    import java.nio.charset.StandardCharsets;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    import java.util.*;
    import java.security.spec.X509EncodedKeySpec;
    import java.security.KeyFactory;
    import java.security.MessageDigest;
    import java.security.PublicKey;
    import org.json.JSONObject;
    
    public class Main {
    
        public static String MD5_HEX(byte b[]) throws Exception {
            byte[] v7_1 = MessageDigest.getInstance("MD5").digest(b);
            char[] v1_1 = new char[v7_1.length * 2];
            int v2;
            for(v2 = 0; v2 < v7_1.length; ++v2) {
                int v3_1 = v7_1[v2] & 15;
                int v4_1 = (v7_1[v2] & 0xF0) >> 4;
                int v5_1 = v2 * 2;
                char[] v6 = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
                v1_1[v5_1] = v6[v4_1];
                v1_1[v5_1 + 1] = v6[v3_1];
            }
            return new String(v1_1);
        }
    
        public static void main(String[] args) throws Exception {
            String fileName = "Certificate.txt";
            byte[] bytes = Files.readAllBytes(Paths.get(fileName));
            String PAKGSIG = MD5_HEX(bytes).substring(0, 0x20);
            String PACKNAME = "com.ss.android.ugc.aweme";
            String RAWKEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAi3o9aMYew7zmjoFCBz3RJl/IFQOHxEUZlargUog+TPLtsQAvNwJ/X4ypkEL9c6T40jQp3qVNcJkjJn5WNcXX5YEt6qpF+18TVqlNLIBoQBag/pCtwDNPi+8dYeEDkusougKlBIvu44M8v3B/VFedAAMuYbG8d+6L8S/ZitMMaVOn9/UlVcPUOi/PL6N/fRrjgwM2A/FePquReq86pO2ZtUDDJ4GK9H+hwSxgEKYJ0i68oSxAaqHg3pAy8BjyWZ5zBQg60JtVMfmjYqFdTqitrJzbj41k3bO6+7VBiV/saIVMrQHaUyqIEKcYFlihRfExE67PZ79n7F7FvrLtF9NsVQIDAQAB";
            String FLAG = "ByteCTF{b6b31f89-a3ee-";
            String flag = FLAG;
            String packname = PACKNAME;
            Cipher v4 = Cipher.getInstance("AES/CBC/PKCS5Padding");
            IvParameterSpec v5 = new IvParameterSpec(new byte[v4.getBlockSize()]);
            v4.init(1, new SecretKeySpec(PAKGSIG.getBytes(), "AES"), v5);
            String packsign = Base64.getEncoder().encodeToString(v4.doFinal(packname.getBytes("UTF-8")));
            String v3 = UUID.randomUUID().toString().replace("-", "").substring(0, 16);
            X509EncodedKeySpec v0 = new X509EncodedKeySpec(Base64.getDecoder().decode(RAWKEY));
            PublicKey v3_0 = KeyFactory.getInstance("RSA").generatePublic(v0);
            Cipher v0_1 = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            v0_1.init(1, v3_0);
            String key = URLEncoder.encode(Base64.getEncoder().encodeToString(v0_1.doFinal(v3.getBytes())), "UTF-8");
            long timeStamp = System.currentTimeMillis();
            String sign = MD5_HEX((packname + packsign + key + "json" + flag + "1.0" + Long.toString(timeStamp).toString()).getBytes());
            JSONObject v1 = new JSONObject();
            v1.put("key", key);
            v1.put("packname", packname);
            v1.put("packsign", packsign);
            v1.put("sign", sign);
            v1.put("flag", flag);
            v1.put("timeStamp", timeStamp);
            v1.put("clientType", "android");
            v1.put("format", "json");
            v1.put("version", "1.0");
            String urljson = URLEncoder.encode(v1.toString(),"UTF-8");
            URL url = new URL("https://bytecert.gwyn.me:8080/decrypt?json="+urljson);
            HttpsURLConnection con = (HttpsURLConnection)url.openConnection();
            BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream()));
            String input = br.readLine();
            br.close();
            String rawinput = new JSONObject(input).get("message").toString();
            Cipher v0_3 = Cipher.getInstance("AES/CBC/PKCS5Padding");
            IvParameterSpec v1_3 = new IvParameterSpec(new byte[v0_3.getBlockSize()]);
            v0_3.init(2, new SecretKeySpec(v3.getBytes(), "AES"), v1_3);
            String v6 = new String(v0_3.doFinal(Base64.getDecoder().decode(rawinput)));
            System.out.print(v6);
        }
    }
    
  • 相关阅读:
    mssql分页原理及效率分析
    [ActiveX]使用VS2010创建MFC ActiveX工程项目
    Study notes for Discrete Probability Distribution
    oracle存储过程异常捕获
    (Python学习9)Python虚拟机中的一般表达式
    用Arduino做一个可视化网络威胁级别指示器!
    iOS上线项目源码分享
    android实习程序6——拨号通话
    评价等级使用的五星选择,包含半星的选择
    AJAX实现无刷新验证用户名
  • 原文地址:https://www.cnblogs.com/algonote/p/15422823.html
Copyright © 2011-2022 走看看