欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

基于Android的http&https中间人攻击

程序员文章站 2022-05-01 09:53:22
...

本文所说的中间人攻击是在同一台Android手机上,通过中间人方式获取其他App的网络访问,支持http&https.


原理:首先通过本地VPN拿到其他APP的网络请求,再做进一步处理。https解密则需要导入自己的CA,绕过证书验证。


遇到的几个技术点

1.通过VPN,拿到的是一个个的IP层数据包,比如sync包,fin包,psh包等,而http/https等是数据流,需要做好状态维护

2.https数据解密需要为每个访问动态生成证书

3.动态生成证书需要处理SNI(Server Name Identification,同一个ip对应多个域名)


针对第1点,首先看Vpn的工作原理:

基于Android的http&https中间人攻击


参考:https://www.ibm.com/developerworks/cn/linux/l-tuntap/

简单的说就是会创建一个工作在网络层的虚拟网卡叫做TUN,然后其他APP的网卡id(Android中socket的网卡变量叫netId)都会设置为tun.

所以当其他APP往外发送数据或者读取数据时,都是通过TUN。

这样我们的VPN APP只需要从TUN这里读取数据便可以获取到其他APP往外发送的数据。

当然我们的VPN APP读到了这些数据后,还要做的事情就是代替他们去和他们的目的服务器交互,发送数据,读取回复等....

读到回复后,再将这些回复数据写入TUN,这样那些APP才能通过读取TUN接收到自己的回复。

这点有很多开源的参考,不是重点。


关于第二点,解密https数据时,首先需要生成一个CA,利用OPEN SSL命令:

参考这个:http://www.cnblogs.com/syuee/p/6482687.html

开发过程中还会用到一些其他命令,比如查看证书内容:

openssl x509 -in demoCA/cacert.pem -noout -text

查看某个网站的证书:

openssl s_client -connect 111.161.121.28:443

PEM格式转换为DER格式:

openssl x509 -outform der -in CARoot1024.crt -out ca.der

DER格式转换为PEM格式:

openssl x509 -inform der -in certificate.cer -out certificate.pem


证书导入后,接下来就是数据解密了。针对HTTP,用了一个socket队列处理,HTTPS的则需要创建一个Server,前面的socket接收到https数据后,和这个server进行tcp连接,tcp握手完成后进行https握手。在https握手时,我们就需要根据请求的地址去拿到真正的证书,再将这个证书的CA设置为我们导入的CA。

动态生成证书代码:

X509 *create_fake_certificate(struct mg_connection *conn, SSL *ssl_to_server, EVP_PKEY *key) {
    X509 * server_x509 = SSL_get_peer_certificate(ssl_to_server);
    if(server_x509 == NULL){
        return NULL;
    }
    X509 * ca = SSL_CTX_get0_certificate(conn->ctx->ssl_ctx);
    if(ca == NULL){
        return NULL;
    }


    X509 * mn = X509_new();
    if(mn == NULL){
        return NULL;
    }

    X509_set_notBefore(mn, X509_get_notBefore(server_x509));
    X509_set_notAfter(mn, X509_get_notAfter(server_x509));
    X509_set_issuer_name(mn, X509_get_subject_name(ca));

    X509_set_subject_name(mn, X509_get_subject_name(server_x509));


    X509_set_serialNumber(mn, X509_get_serialNumber(server_x509));
    X509_set_version(mn, 2);
    int extcount = X509_get_ext_count(server_x509);
    int i;
    int addIndex = 0;
    for (i = 0; i < extcount; ++i) {
        const char *extstr;
        X509_EXTENSION * ext;
        ext = X509_get_ext(server_x509, i);
        extstr = OBJ_nid2sn(OBJ_obj2nid(X509_EXTENSION_get_object(ext)));

        if (strstr(extstr, "authorityKeyIdentifier")) {
            continue;
        }

        X509_add_ext(mn, ext, addIndex);
        addIndex++;
    }

    X509_set_pubkey(mn, key);
    X509_sign(mn, key, EVP_sha256());

    return mn;
}


证书生成好后,接下来需要设置ssl参数以及证书链:

if (SSL_CTX_use_PrivateKey(sslServerCtxUsed, evpKey) != 1) {
        return NULL;
    }

    if (SSL_CTX_use_certificate(sslServerCtxUsed, fake_x509) != 1) {
        return NULL;
    }

    if (SSL_CTX_check_private_key(sslServerCtxUsed) != 1) {
        return NULL;
    }


    X509 * x1 = SSL_CTX_get0_certificate(conn->ctx->ssl_ctx);
    if(x1 == NULL){
        log_android_yassl("get_ssl_to_app2...x1 is null...");
    }

    if (!SSL_CTX_add1_chain_cert(sslServerCtxUsed, x1)) {
        return NULL;
    }


这里需要注意的是SSL_read & SSL_write 需要读满一个record才能返回,可能需要重试几次,通过如下设置让它和普通IO没有区别:

    int mode = SSL_CTX_get_mode(sslServerCtxUsed);
    mode |= SSL_MODE_AUTO_RETRY;
    SSL_CTX_set_mode(sslServerCtxUsed, mode);


到这一步,就可以对HTTPS内容解密了,但是还有个问题,就是对同一个IP对应多个域名的情况还不能处理。

也就是第三点所说的SNI。那如何解决呢? 分为2步。


首先在我们的Server端,创建SSL Ctx时,调用如下函数设置SNI:

SSL_set_tlsext_host_name(ssl, sni);

那Server端的sni从哪里获得?  这个值其实可以在ssl握手的第一步,也就是client hello中获取,不过open ssl似乎没有提供相关的方法,只有自己解析获取。

ssl握手协议格式参考:http://blog.fourthbit.com/2014/12/23/traffic-analysis-of-an-ssl-slash-tls-session

                    |
                           |
                           |
         Record Layer      |  Handshake Layer
                           |                                  |
                           |                                  |  ...more messages
  +----+----+----+----+----+----+----+----+----+------ - - - -+--
  | 22 |    |    |    |    |    |    |    |    |              |
  |0x16|    |    |    |    |    |    |    |    |message       |
  +----+----+----+----+----+----+----+----+----+------ - - - -+--
    /               /      | \    \----\-----\                |
   /               /       |  \         \
  type: 22        /        |   \         handshake message length
                 /              type
                /
           length: arbitrary (up to 16k)


   Handshake Type Values    dec      hex
   -------------------------------------
   HELLO_REQUEST              0     0x00
   CLIENT_HELLO               1     0x01
   SERVER_HELLO               2     0x02
   CERTIFICATE               11     0x0b
   SERVER_KEY_EXCHANGE       12     0x0c
   CERTIFICATE_REQUEST       13     0x0d
   SERVER_DONE               14     0x0e
   CERTIFICATE_VERIFY        15     0x0f
   CLIENT_KEY_EXCHANGE       16     0x10
   FINISHED                  20     0x14


解析sni代码如下:

#include <stdio.h>
#include <stdlib.h> /* malloc() */
#include <string.h> /* strncpy() */
#include <sys/socket.h>
#include "clienthello.h"
#include "common.h"

#define SERVER_NAME_LEN 256
#define TLS_HEADER_LEN 5
#define TLS_HANDSHAKE_CONTENT_TYPE 0x16
#define TLS_HANDSHAKE_TYPE_CLIENT_HELLO 0x01

#ifndef MIN
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
#endif

static const char tls_alert[] = {
        0x15, /* TLS Alert */
        0x03, 0x01, /* TLS version  */
        0x00, 0x02, /* Payload length */
        0x02, 0x28, /* Fatal, handshake failure */
};

static int parse_extensions(const char *, size_t, char *);
static int parse_server_name_extension(const char *, size_t, char *);

static const struct Protocol tls_protocol_st = {
        .name = "tls",
        .default_port = 443,
        .parse_packet = &parse_tls_header,
        .abort_message = tls_alert,
        .abort_message_len = sizeof(tls_alert)
};
const struct Protocol *const tls_protocol = &tls_protocol_st;


/* Parse a TLS packet for the Server Name Indication extension in the client
 * hello handshake, returning the first servername found (pointer to static
 * array)
 *
 * Returns:
 *  >=0  - length of the hostname and updates *hostname
 *         caller is responsible for freeing *hostname
 *  -1   - Incomplete request
 *  -2   - No Host header included in this request
 *  -3   - Invalid hostname pointer
 *  -4   - malloc failure
 *  < -4 - Invalid TLS client hello
 */
 int parse_tls_header(const char *data, size_t data_len, char *hostname) {
    char tls_content_type;
    char tls_version_major;
    char tls_version_minor;
    size_t pos = TLS_HEADER_LEN;
    size_t len;

    if (hostname == NULL)
        return -3;

    /* Check that our TCP payload is at least large enough for a TLS header */
    if (data_len < TLS_HEADER_LEN)
        return -1;

    /* SSL 2.0 compatible Client Hello
     *
     * High bit of first byte (length) and content type is Client Hello
     *
     * See RFC5246 Appendix E.2
     */
    if (data[0] & 0x80 && data[2] == 1) {
        log_android_ssl("client hello Received SSL 2.0 Client Hello which can not support SNI.");
        return -2;
    }

    tls_content_type = data[0];
    if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) {
        log_android_ssl("client hello Request did not begin with TLS handshake.");
        return -5;
    }

    tls_version_major = data[1];
    tls_version_minor = data[2];
    if (tls_version_major < 3) {
        log_android_ssl("client hello Received SSL %d.%d handshake which can not support SNI.",
              tls_version_major, tls_version_minor);

        return -2;
    }

    /* TLS record length */
    len = ((unsigned char)data[3] << 8) +
          (unsigned char)data[4] + TLS_HEADER_LEN;
    data_len = MIN(data_len, len);

    /* Check we received entire TLS record length */
    if (data_len < len)
        return -1;

    /*
     * Handshake
     */
    if (pos + 1 > data_len) {
        return -5;
    }
    if (data[pos] != TLS_HANDSHAKE_TYPE_CLIENT_HELLO) {
        //log_android_ssl("client hello Not a client hello");

        return -5;
    }

    /* Skip past fixed length records:
       1	Handshake Type
       3	Length
       2	Version (again)
       32	Random
       to	Session ID Length
     */
    pos += 38;

    /* Session ID */
    if (pos + 1 > data_len)
        return -5;
    len = (unsigned char)data[pos];
    pos += 1 + len;

    /* Cipher Suites */
    if (pos + 2 > data_len)
        return -5;
    len = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1];
    pos += 2 + len;

    /* Compression Methods */
    if (pos + 1 > data_len)
        return -5;
    len = (unsigned char)data[pos];
    pos += 1 + len;

    if (pos == data_len && tls_version_major == 3 && tls_version_minor == 0) {
      //  log_android_ssl("client hello Received SSL 3.0 handshake without extensions");
        return -2;
    }

    /* Extensions */
    if (pos + 2 > data_len)
        return -5;
    len = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1];
    pos += 2;

    if (pos + len > data_len)
        return -5;

    return parse_extensions(data + pos, len, hostname);
}

static int
parse_extensions(const char *data, size_t data_len, char *hostname) {
   // log_android_ssl("client hello parse_extensions...");
    size_t pos = 0;
    size_t len;

    /* Parse each 4 bytes for the extension header */
    while (pos + 4 <= data_len) {
        /* Extension Length */
        len = ((unsigned char)data[pos + 2] << 8) +
              (unsigned char)data[pos + 3];

        /* Check if it's a server name extension */
        if (data[pos] == 0x00 && data[pos + 1] == 0x00) {
           // log_android_ssl("client hello found server name:%s",data + pos + 4);

            /* There can be only one extension of each type, so we break
               our state and move p to beinnging of the extension here */
            if (pos + 4 + len > data_len)
                return -5;
            return parse_server_name_extension(data + pos + 4, len, hostname);
        }
        pos += 4 + len; /* Advance to the next extension header */
    }
    /* Check we ended where we expected to */
    if (pos != data_len)
        return -5;

    return -2;
}

static int
parse_server_name_extension(const char *data, size_t data_len,
                            char *hostname) {
    size_t pos = 2; /* skip server name list length */
    size_t len;

    while (pos + 3 < data_len) {
        len = ((unsigned char)data[pos + 1] << 8) +
              (unsigned char)data[pos + 2];

        if (pos + 3 + len > data_len)
            return -5;

        switch (data[pos]) { /* name type */
            case 0x00: /* host_name */
               // log_android_ssl("client hello found host name:%s",data + pos + 3);

                strncpy(hostname, data + pos + 3, len);
                hostname[len] = '\0';

                return len;
            default:
                log_android_ssl("client hello Unknown server name extension name type: %d",
                      data[pos]);
        }
        pos += 3 + len;
    }
    /* Check we ended where we expected to */
    if (pos != data_len)
        return -5;

    return -2;
}

头文件:

//
// Created by yanchen on 17-9-24.
//

#ifndef NQMDM_CLIENTHELLO_H
#define NQMDM_CLIENTHELLO_H
#include <inttypes.h>

struct Protocol {
    const char *const name;
    const uint16_t default_port;
    int (*const parse_packet)(const char*, size_t, char **);
    const char *const abort_message;
    const size_t abort_message_len;
};

const struct Protocol *const tls_protocol;

int parse_tls_header(const char *data, size_t data_len, char *hostname);


#endif //NQMDM_CLIENTHELLO_H


调用方式:

if(dataP[0] == 0x16 && dataP[1] == 0x03 && dataP[5] == 0x01){
    char hostName[1024] = {0};
    struct Handshake *hs = (struct Handshake *) dataP;
    int result = parse_tls_header(dataP,len,hostName);



到此基本上就解决完开发中遇到的几个主要问题,可以对https进行审计等操作。