diff --git a/.gitignore b/.gitignore index 37466a32..ace6d18a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ local.properties .DS_Store build/ captures/ -pcap/ +/pcap/ .externalNativeBuild .cxx app/release/ diff --git a/app/src/main/java/com/emanuelef/remote_capture/HTTPReassembly.java b/app/src/main/java/com/emanuelef/remote_capture/HTTPReassembly.java index e2eb8889..6e140b32 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/HTTPReassembly.java +++ b/app/src/main/java/com/emanuelef/remote_capture/HTTPReassembly.java @@ -108,8 +108,15 @@ public class HTTPReassembly { /* The request/response tab shows reassembled HTTP chunks. * Reassembling chunks is requires when using a content-encoding like gzip since we can only * decode the data when we have the full chunk and we cannot determine data bounds. + * * When reading data via the MitmReceiver, mitmproxy already performs chunks reassembly and - * also handles HTTP/2 so that we only get the payload. */ + * also handles HTTP/2 so that we only get the payload. + * + * Similarly, also ushark performs HTTP/2 request/reply reassembly, so we always get the whole + * HTTP headers and body in a single chunk. With ushark, though, HTTP/2 replies may arrive in + * a different order (due to HTTP/2 multiplexing). chunk.stream_id is used by ConnectionDescriptor + * to associate the reply to the correct request. + */ public void handleChunk(PayloadChunk chunk) { int body_start = 0; byte[] payload = chunk.payload; @@ -158,6 +165,8 @@ public class HTTPReassembly { } mFirstChunk.httpPath = path; + + //log_d(mFirstChunk.httpMethod + " " + mFirstChunk.httpPath); } } else if (line.startsWith("HTTP/")) { int first_space = line.indexOf(' '); @@ -255,6 +264,12 @@ public class HTTPReassembly { if(!mReassembleChunks) mReadingHeaders = false; + boolean httpRst = false; + if (mReadingHeaders && chunk.isHttp2Rst()) { + mReadingHeaders = false; + httpRst = true; + } + if(!mReadingHeaders) { // Reading HTTP body int body_size = payload.length - body_start; @@ -349,6 +364,11 @@ public class HTTPReassembly { to_add.httpQuery = mFirstChunk.httpQuery; to_add.httpBodyLength = mBodySize; + if (httpRst) + // this is necessary when mDumpPayload=false, to ensure that + // the chunk is marked as HTTP RST + to_add.setHttpRst(); + // Fix the chunk type after upgrade when read from ushark if (mSwitchedProtocols && (to_add.type == PayloadChunk.ChunkType.HTTP)) { to_add.type = mWebsocketUpgrade ? PayloadChunk.ChunkType.WEBSOCKET : PayloadChunk.ChunkType.RAW; @@ -359,6 +379,12 @@ public class HTTPReassembly { } mBodySize = 0; + + if ((to_add.type == PayloadChunk.ChunkType.HTTP)) { + Log.d(TAG, "Reassembled HTTP " + + (to_add.isHttp2Rst() ? "RST" : (to_add.is_sent ? "request" : "response"))); + } + mListener.onChunkReassembled(to_add); reset(); // mReadingHeaders = true diff --git a/app/src/main/java/com/emanuelef/remote_capture/HttpLog.java b/app/src/main/java/com/emanuelef/remote_capture/HttpLog.java index cb9c8b81..5c0c4929 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/HttpLog.java +++ b/app/src/main/java/com/emanuelef/remote_capture/HttpLog.java @@ -43,8 +43,10 @@ public class HttpLog { public String path = ""; public String query = ""; public HttpReply reply; - public int bodyLength; - public long timestamp; + public int bodyLength = 0; + public int streamId = 0; + public long timestamp = 0; + public boolean httpRst = false; private int idx = -1; public HttpRequest(ConnectionDescriptor conn, int firstChunkPos) { diff --git a/app/src/main/java/com/emanuelef/remote_capture/MitmReceiver.java b/app/src/main/java/com/emanuelef/remote_capture/MitmReceiver.java index ea227658..06883f80 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/MitmReceiver.java +++ b/app/src/main/java/com/emanuelef/remote_capture/MitmReceiver.java @@ -327,7 +327,7 @@ public class MitmReceiver implements Runnable, ConnectionsListener, MitmListener } else if(type == MsgType.JS_INJECTED) { conn.js_injected_scripts = new String(message, StandardCharsets.US_ASCII); } else - conn.addPayloadChunkMitm(new PayloadChunk(message, getChunkType(type), isSent(type), tstamp)); + conn.addPayloadChunkMitm(new PayloadChunk(message, getChunkType(type), isSent(type), tstamp, 0)); } private synchronized void addPendingMessage(PendingMessage pending) { diff --git a/app/src/main/java/com/emanuelef/remote_capture/adapters/PayloadAdapter.java b/app/src/main/java/com/emanuelef/remote_capture/adapters/PayloadAdapter.java index 08ce4542..e3631468 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/adapters/PayloadAdapter.java +++ b/app/src/main/java/com/emanuelef/remote_capture/adapters/PayloadAdapter.java @@ -126,7 +126,7 @@ public class PayloadAdapter extends RecyclerView.Adapter. * - * Copyright 2021-25 - Emanuele Faranda + * Copyright 2021-26 - Emanuele Faranda */ #include @@ -406,34 +406,78 @@ static int get_ip_offset(int linktype) { /* ******************************************************* */ static plain_data_t g_plain_data = {}; +static pkt_context_t *g_cur_ctx = NULL; -static void handle_tls_data(const unsigned char *plain_data, unsigned int data_len) { - if (data_len == 0) - return; +static void handle_http_data(bool is_tx, uint64_t ms, uint32_t stream_id, const unsigned char *plain_data, unsigned int data_len) { + // NOTE: (plain_data == NULL) / (data_len == 0) signals HTTP/2 resets - //log_w(" %s", plain_data); - g_plain_data.data = (unsigned char *) pd_realloc(g_plain_data.data, g_plain_data.tot_length + data_len); - g_plain_data.lengths = (unsigned int *) pd_realloc(g_plain_data.lengths, (g_plain_data.n_items + 1) * sizeof(unsigned int)); - if (!g_plain_data.data || !g_plain_data.lengths) { + // allocate the data first, as a failure in this allocation is easier to handle now rather + // than after a plain_data_item_t allocation + unsigned char *new_item_data_buf = NULL; + if (plain_data) { + new_item_data_buf = pd_malloc(data_len); + if (!new_item_data_buf) { + log_e("alloc(http_data) failed[%d]: %s", errno, strerror(errno)); + return; + } + } + + plain_data_item_t* new_items = (plain_data_item_t*) pd_realloc(g_plain_data.items, (g_plain_data.n_items + 1) * sizeof(plain_data_item_t)); + if (!new_items) { log_e("realloc(tls_data) failed[%d]: %s", errno, strerror(errno)); - if (g_plain_data.data) - pd_free(g_plain_data.data); - if (g_plain_data.lengths) - pd_free(g_plain_data.lengths); + if (new_item_data_buf) + pd_free(new_item_data_buf); + return; + } - g_plain_data.data = NULL; - g_plain_data.lengths = NULL; - g_plain_data.tot_length = 0; - g_plain_data.n_items = 0; - } else { - memcpy(g_plain_data.data + g_plain_data.tot_length, plain_data, data_len); - g_plain_data.lengths[g_plain_data.n_items] = data_len; - g_plain_data.tot_length += data_len; - g_plain_data.n_items++; + g_plain_data.items = new_items; + plain_data_item_t *item = &g_plain_data.items[g_plain_data.n_items++]; + + memset(item, 0, sizeof(plain_data_item_t)); + item->ms = ms; + item->is_tx = is_tx; + item->stream_id = stream_id; + + if (new_item_data_buf) { + memcpy(new_item_data_buf, plain_data, data_len); + + item->data = new_item_data_buf; + item->data_length = data_len; } } +static void handle_ushark_http1_data(uint32_t conv_id, const unsigned char *plain_data, size_t data_len) { + if (!g_cur_ctx) + return; + + // HTTP/1 is sequential, use 0 as the stream ID + handle_http_data(g_cur_ctx->is_tx, g_cur_ctx->ms, 0, plain_data, data_len); +} + +static void handle_ushark_http2_request(uint32_t conv_id, uint32_t stream_id, const unsigned char *plain_data, size_t data_len) { + if (!g_cur_ctx) + return; + + handle_http_data(g_cur_ctx->is_tx, g_cur_ctx->ms, stream_id, plain_data, data_len); +} + +static void handle_ushark_http2_response(uint32_t conv_id, uint32_t stream_id, const unsigned char *plain_data, size_t data_len) { + if (!g_cur_ctx) + return; + + handle_http_data(g_cur_ctx->is_tx, g_cur_ctx->ms, stream_id, plain_data, data_len); +} + +static void handle_ushark_http2_reset(uint32_t conv_id, uint32_t stream_id) { + if (!g_cur_ctx) + return; + + handle_http_data(g_cur_ctx->is_tx, g_cur_ctx->ms, stream_id, NULL, 0); +} + +/* ******************************************************* */ + /* Returns true if packet is valid. If false is returned, the pkt must still be dumped, so a call to * pd_dump_packet is required. */ static bool handle_packet(pcapdroid_t *pd, pcapd_hdr_t *hdr, const char *buffer, int ipoffset) { @@ -534,17 +578,21 @@ static bool handle_packet(pcapdroid_t *pd, pcapd_hdr_t *hdr, const char *buffer, struct timeval tv = hdr->ts; pkt_context_t pinfo; pd_init_pkt_context(&pinfo, &pkt, is_tx, &conn_tuple, conn->data, &tv); + g_cur_ctx = &pinfo; if (pd->pcap.usk && (pkt.len > 0)) { struct pcap_pkthdr pcap_hdr; pcap_hdr.len = pcap_hdr.caplen = pkt.len; pcap_hdr.ts = hdr->ts; - ushark_dissect_tls(pd->pcap.usk, + // This disables the reporting of leaks inside libushark. + // The reporting is enabled again in the individual callbacks (e.g. handle_ushark_http2_request) + // to detect leaks in pcapdroid + ushark_dissect(pd->pcap.usk, (const unsigned char*) pkt.l3, - &pcap_hdr, handle_tls_data); + &pcap_hdr); - if (g_plain_data.data) + if (g_plain_data.n_items > 0) pinfo.plain_data = &g_plain_data; } @@ -555,11 +603,14 @@ static bool handle_packet(pcapdroid_t *pd, pcapd_hdr_t *hdr, const char *buffer, pd_account_stats(pd, &pinfo); - if (g_plain_data.data) { - pd_free(g_plain_data.data); - pd_free(g_plain_data.lengths); + if (g_plain_data.items) { + for (size_t i = 0; i < g_plain_data.n_items; i++) + pd_free(g_plain_data.items[i].data); + + pd_free(g_plain_data.items); memset(&g_plain_data, 0, sizeof(g_plain_data)); } + g_cur_ctx = NULL; return true; } @@ -714,6 +765,23 @@ static reader_rv read_file(pcapdroid_t *pd, pd_reader_t *reader, pcapd_hdr_t* hd /* ******************************************************* */ +// Wrapper function for ushark initialization to allow LSAN suppression +static void init_ushark_for_pcap(pcapdroid_t *pd, const char *keylog_path) { + if (ushark_init(pd)) { + pd->pcap.usk = ushark_new(PCAPD_DLT_RAW, ""); + + ushark_data_callbacks_t cbs = { + .on_http1_data = handle_ushark_http1_data, + .on_http2_request = handle_ushark_http2_request, + .on_http2_response = handle_ushark_http2_response, + .on_http2_reset = handle_ushark_http2_reset, + }; + ushark_set_callbacks(pd->pcap.usk, &cbs); + + ushark_set_pref("tls.keylog_file", keylog_path); + } +} + int run_pcap(pcapdroid_t *pd) { int sock = -1; pd_reader_t *reader = NULL; @@ -726,15 +794,14 @@ int run_pcap(pcapdroid_t *pd) { if (pd->pcap_file_capture) { // check if the SSL keylog exists - const char *keylog_path = get_cache_path(pd, "sslkeylog.txt"); + // Use override path if provided (for tests), otherwise use default location + const char *keylog_path = pd->keylog_path_override + ? pd->keylog_path_override + : get_cache_path(pd, "sslkeylog.txt"); if (access(keylog_path, F_OK) == 0) { log_i("Use ushark for TLS decryption"); - - if (ushark_init(pd)) { - pd->pcap.usk = ushark_new(PCAPD_DLT_RAW, ""); - ushark_set_pref("tls.keylog_file", keylog_path); - } + init_ushark_for_pcap(pd, keylog_path); } } diff --git a/app/src/main/jni/core/jni_impl.c b/app/src/main/jni/core/jni_impl.c index 39ab62b3..d42dd9dd 100644 --- a/app/src/main/jni/core/jni_impl.c +++ b/app/src/main/jni/core/jni_impl.c @@ -432,16 +432,16 @@ static void notifyBlacklistsLoaded(pcapdroid_t *pd, bl_status_arr_t *status_arr) /* ******************************************************* */ -static bool dumpPayloadChunk(struct pcapdroid *pd, const pkt_context_t *pctx, const char *dump_data, int dump_size) { +static bool dumpPayloadChunk(struct pcapdroid *pd, pd_conn_t *conn, bool is_tx, uint64_t ms, uint32_t stream_id, const char *dump_data, int dump_size) { JNIEnv *env = pd->env; bool rv = false; - if(pctx->data->payload_chunks == NULL) { + if(conn->payload_chunks == NULL) { // Directly allocating an ArrayList rather than creating it afterwards saves us from a data copy. // However, this creates a local reference, which is retained until sendConnectionsDump is called. // NOTE: Android only allows up to 512 local references. - pctx->data->payload_chunks = (*env)->NewObject(env, cls.arraylist, mids.arraylistNew); - if((pctx->data->payload_chunks == NULL) || jniCheckException(env)) + conn->payload_chunks = (*env)->NewObject(env, cls.arraylist, mids.arraylistNew); + if((conn->payload_chunks == NULL) || jniCheckException(env)) return false; } @@ -449,12 +449,13 @@ static bool dumpPayloadChunk(struct pcapdroid *pd, const pkt_context_t *pctx, co if(jniCheckException(env)) return false; - jobject chunk_type = (pctx->data->l7proto == NDPI_PROTOCOL_HTTP) ? enums.chunktype_http : enums.chunktype_raw; + jobject chunk_type = (conn->l7proto == NDPI_PROTOCOL_HTTP) ? enums.chunktype_http : enums.chunktype_raw; - jobject chunk = (*env)->NewObject(env, cls.payload_chunk, mids.payloadChunkInit, barray, chunk_type, pctx->is_tx, pctx->ms); + jobject chunk = (*env)->NewObject(env, cls.payload_chunk, mids.payloadChunkInit, barray, chunk_type, is_tx, ms, stream_id); if(chunk && !jniCheckException(env)) { - (*env)->SetByteArrayRegion(env, barray, 0, dump_size, (jbyte*) dump_data); - rv = (*env)->CallBooleanMethod(env, pctx->data->payload_chunks, mids.arraylistAdd, chunk); + if (dump_data) // can be NULL for RST reporting in HTTP/2 + (*env)->SetByteArrayRegion(env, barray, 0, dump_size, (jbyte*) dump_data); + rv = (*env)->CallBooleanMethod(env, conn->payload_chunks, mids.arraylistAdd, chunk); } //log_d("Dump chunk [size=%d]: %d", rv, dump_size); @@ -567,7 +568,7 @@ static void init_jni(JNIEnv *env) { mids.listGet = jniGetMethodID(env, cls.list, "get", "(I)Ljava/lang/Object;"); mids.arraylistNew = jniGetMethodID(env, cls.arraylist, "", "()V"); mids.arraylistAdd = jniGetMethodID(env, cls.arraylist, "add", "(Ljava/lang/Object;)Z"); - mids.payloadChunkInit = jniGetMethodID(env, cls.payload_chunk, "", "([BLcom/emanuelef/remote_capture/model/PayloadChunk$ChunkType;ZJ)V"); + mids.payloadChunkInit = jniGetMethodID(env, cls.payload_chunk, "", "([BLcom/emanuelef/remote_capture/model/PayloadChunk$ChunkType;ZJI)V"); /* Fields */ fields.bldescr_fname = jniFieldID(env, cls.blacklist_descriptor, "fname", "Ljava/lang/String;"); diff --git a/app/src/main/jni/core/pcapdroid.c b/app/src/main/jni/core/pcapdroid.c index d202035b..added875 100644 --- a/app/src/main/jni/core/pcapdroid.c +++ b/app/src/main/jni/core/pcapdroid.c @@ -588,8 +588,9 @@ void pd_giveup_dpi(pcapdroid_t *pd, pd_conn_t *data, const zdtun_5tuple_t *tuple /* ******************************************************* */ // dumps the payload and returns true if fully dumped, false if failed or truncated -static bool dump_payload(pcapdroid_t *pd, pkt_context_t *pctx, const char *to_dump, int dump_size) { - pd_conn_t *data = pctx->data; +static bool dump_payload(pcapdroid_t *pd, pd_conn_t *conn, bool is_tx, uint64_t ms, uint32_t stream_id, + const char *to_dump, int dump_size) +{ bool truncated = false; if((pd->payload_mode == PAYLOAD_MODE_MINIMAL) && (dump_size > MINIMAL_PAYLOAD_MAX_DIRECTION_SIZE)) { @@ -597,8 +598,8 @@ static bool dump_payload(pcapdroid_t *pd, pkt_context_t *pctx, const char *to_du truncated = true; } - if(pd->cb.dump_payload_chunk(pd, pctx, to_dump, dump_size)) - data->has_payload[pctx->is_tx] = true; + if(pd->cb.dump_payload_chunk(pd, conn, is_tx, ms, stream_id, to_dump, dump_size)) + conn->has_payload[is_tx] = true; else truncated = true; @@ -630,17 +631,18 @@ static void process_payload(pcapdroid_t *pd, pkt_context_t *pctx) { data->has_decrypted_data = true; } - unsigned int data_offset = 0; truncated = false; for (unsigned int i = 0; i < pctx->plain_data->n_items; i++) { - truncated |= !dump_payload(pd, pctx, (const char*) pctx->plain_data->data + data_offset, - (int) pctx->plain_data->lengths[i]); + const plain_data_item_t *item = &pctx->plain_data->items[i]; - data_offset += pctx->plain_data->lengths[i]; + // use the item is_tx and ms timestamp data, rather than the ones from pctx because + // http2.c may buffer http responses/resets so they may be processed with a different pctx + truncated |= !dump_payload(pd, pctx->data, item->is_tx, item->ms, item->stream_id, + (const char*) item->data, (int) item->data_length); } } else - truncated = !dump_payload(pd, pctx, pkt->l7, pkt->l7_len); + truncated = !dump_payload(pd, pctx->data, pctx->is_tx, pctx->ms, 0, pkt->l7, pkt->l7_len); updated = true; } else diff --git a/app/src/main/jni/core/pcapdroid.h b/app/src/main/jni/core/pcapdroid.h index 1bd5c8ab..3f25bfda 100644 --- a/app/src/main/jni/core/pcapdroid.h +++ b/app/src/main/jni/core/pcapdroid.h @@ -153,8 +153,14 @@ typedef struct { typedef struct { unsigned char *data; - unsigned int *lengths; - unsigned int tot_length; + unsigned int data_length; + uint64_t ms; + uint32_t stream_id; + bool is_tx; +} plain_data_item_t; + +typedef struct { + plain_data_item_t *items; unsigned int n_items; } plain_data_t; @@ -184,7 +190,7 @@ typedef struct { void (*stop_pcap_dump)(struct pcapdroid *pd); void (*notify_service_status)(struct pcapdroid *pd, const char *status); void (*notify_blacklists_loaded)(struct pcapdroid *pd, bl_status_arr_t *status_arr); - bool (*dump_payload_chunk)(struct pcapdroid *pd, const pkt_context_t *pctx, const char *dump_data, int dump_size); + bool (*dump_payload_chunk)(struct pcapdroid *pd, pd_conn_t *conn, bool is_tx, uint64_t ms, uint32_t stream_id, const char *dump_data, int dump_size); void (*clear_payload_chunks)(struct pcapdroid *pd, const pkt_context_t *pctx); } pd_callbacks_t; @@ -214,6 +220,7 @@ typedef struct pcapdroid { jint mitm_addon_uid; bool vpn_capture; bool pcap_file_capture; + const char *keylog_path_override; // For tests: override sslkeylog.txt location payload_mode_t payload_mode; // stats diff --git a/app/src/main/jni/core/ushark_dll.c b/app/src/main/jni/core/ushark_dll.c index a4f7da15..8c541189 100644 --- a/app/src/main/jni/core/ushark_dll.c +++ b/app/src/main/jni/core/ushark_dll.c @@ -11,9 +11,9 @@ static void (*sk_init)(); static void (*sk_cleanup)(); static ushark_t* (*sk_new)(int, const char *); static void (*sk_set_pref)(const char *, const char *); +static void (*sk_set_callbacks)(ushark_t *, const ushark_data_callbacks_t *); static void (*sk_destroy)(ushark_t*); -static void (*sk_dissect_tls)(ushark_t*, const unsigned char *, const struct pcap_pkthdr *, - ushark_tls_data_callback); +static const char* (*sk_dissect)(ushark_t*, const unsigned char *, const struct pcap_pkthdr *); bool ushark_init(pcapdroid_t *pd) { assert(!sk_dll); @@ -44,10 +44,11 @@ bool ushark_init(pcapdroid_t *pd) { sk_cleanup = dlsym(sk_dll, "ushark_cleanup"); sk_new = dlsym(sk_dll, "ushark_new"); sk_set_pref = dlsym(sk_dll, "ushark_set_pref"); + sk_set_callbacks = dlsym(sk_dll, "ushark_set_callbacks"); sk_destroy = dlsym(sk_dll, "ushark_destroy"); - sk_dissect_tls = dlsym(sk_dll, "ushark_dissect_tls"); + sk_dissect = dlsym(sk_dll, "ushark_dissect"); - if (!sk_init || !sk_cleanup || !sk_new || !sk_set_pref || !sk_destroy || !sk_dissect_tls) { + if (!sk_init || !sk_cleanup || !sk_new || !sk_set_pref || !sk_set_callbacks || !sk_destroy || !sk_dissect) { dlclose(sk_dll); sk_dll = NULL; log_e("libushark.so misses some required symbols"); @@ -60,14 +61,16 @@ bool ushark_init(pcapdroid_t *pd) { void ushark_cleanup() { assert(sk_dll); + sk_cleanup(); sk_init = NULL; sk_cleanup = NULL; sk_new = NULL; sk_set_pref = NULL; + sk_set_callbacks = NULL; sk_destroy = NULL; - sk_dissect_tls = NULL; + sk_dissect = NULL; // deallocates the static variables in wireshark, necessary to run cleanly again dlclose(sk_dll); @@ -76,20 +79,30 @@ void ushark_cleanup() { ushark_t* ushark_new(int pcap_encap, const char *dfilter) { assert(sk_new); + return sk_new(pcap_encap, dfilter); } void ushark_destroy(ushark_t *sk) { assert(sk_destroy); - return sk_destroy(sk); + + sk_destroy(sk); } void ushark_set_pref(const char *name, const char *val) { assert(sk_set_pref); - return sk_set_pref(name, val); + + sk_set_pref(name, val); } -void ushark_dissect_tls(ushark_t *sk, const unsigned char *buf, const struct pcap_pkthdr *hdr, ushark_tls_data_callback cb) { - assert(sk_dissect_tls); - return sk_dissect_tls(sk, buf, hdr, cb); -} \ No newline at end of file +void ushark_set_callbacks(ushark_t *sk, const ushark_data_callbacks_t *cbs) { + assert(sk_set_callbacks); + + sk_set_callbacks(sk, cbs); +} + +const char* ushark_dissect(ushark_t *sk, const unsigned char *buf, const struct pcap_pkthdr *hdr) { + assert(sk_dissect); + + return sk_dissect(sk, buf, hdr); +} diff --git a/app/src/main/jni/core/ushark_dll.h b/app/src/main/jni/core/ushark_dll.h index 5d2f2521..d625a9fe 100644 --- a/app/src/main/jni/core/ushark_dll.h +++ b/app/src/main/jni/core/ushark_dll.h @@ -13,7 +13,14 @@ ushark_t* ushark_new(int pcap_encap, const char *dfilter); void ushark_destroy(ushark_t *sk); void ushark_set_pref(const char *name, const char *val); -typedef void (*ushark_tls_data_callback)(const unsigned char *plain_data, unsigned int data_len); -void ushark_dissect_tls(ushark_t *sk, const unsigned char *buf, const struct pcap_pkthdr *hdr, ushark_tls_data_callback cb); +typedef struct { + void (*on_http1_data)(uint32_t conversation_id, const unsigned char *plain_data, size_t data_len); + void (*on_http2_request)(uint32_t conversation_id, uint32_t stream_id, const unsigned char *plain_data, size_t data_len); + void (*on_http2_response)(uint32_t conversation_id, uint32_t stream_id, const unsigned char *plain_data, size_t data_len); + void (*on_http2_reset)(uint32_t conversation_id, uint32_t stream_id); +} ushark_data_callbacks_t; +void ushark_set_callbacks(ushark_t *sk, const ushark_data_callbacks_t *cbs); + +const char* ushark_dissect(ushark_t *sk, const unsigned char *buf, const struct pcap_pkthdr *hdr); #endif diff --git a/app/src/main/jni/tests/Makefile b/app/src/main/jni/tests/Makefile index d03849b3..368f8905 100644 --- a/app/src/main/jni/tests/Makefile +++ b/app/src/main/jni/tests/Makefile @@ -6,7 +6,7 @@ clean: run_tests: mkdir -p build/test cd build/test && \ - cmake -DCMAKE_C_COMPILER=clang -DCMAKE_C_FLAGS="-fsanitize=address,leak -DHAVE_GNU_STRERROR_R -fno-omit-frame-pointer -O1" ../.. && \ + cmake -DCMAKE_C_COMPILER=clang -DCMAKE_C_FLAGS="-fsanitize=address,leak -DHAVE_GNU_STRERROR_R -fno-omit-frame-pointer -g -O0" ../.. && \ $(MAKE) run_tests fuzz: diff --git a/app/src/main/jni/tests/test_utils.c b/app/src/main/jni/tests/test_utils.c index 02940fe9..aeb6011d 100644 --- a/app/src/main/jni/tests/test_utils.c +++ b/app/src/main/jni/tests/test_utils.c @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with PCAPdroid. If not, see . * - * Copyright 2022 - Emanuele Faranda + * Copyright 2022-26 - Emanuele Faranda */ #include "test_utils.h" @@ -37,8 +37,15 @@ static void free_payload_chunks(pcapdroid_t *pd); /* ******************************************************* */ -static void getPcapdPath(struct pcapdroid *pd, const char *prog_name, char *buf, int bufsize) { - snprintf(buf, bufsize, "../main/pcapd/libpcapd.so"); +static void getNativeLibPath(struct pcapdroid *pd, const char *prog_name, char *buf, int bufsize) { + if (strcmp(prog_name, "pcapd") == 0) + snprintf(buf, bufsize, "../main/pcapd/libpcapd.so"); + else { + fprintf(stderr, "Unknown native library: %s", prog_name); + + if (bufsize > 0) + buf[0] = '\0'; + } } /* ******************************************************* */ @@ -89,7 +96,7 @@ pcapdroid_t* pd_init_test(const char *ifname) { pd->pcap_file_capture = true; pd->pcap.capture_interface = (char*) ifname; pd->pcap.as_root = false; // don't run as root - pd->cb.get_libprog_path = getPcapdPath; + pd->cb.get_libprog_path = getNativeLibPath; pd->payload_mode = PAYLOAD_MODE_FULL; strcpy(pd->cachedir, "."); @@ -215,8 +222,8 @@ u_char* next_pcap_record(pcap_rec_t *rec) { /* ******************************************************* */ /* Dumps all the payload chunks into a linked list. The linked list is accessible via - * (payload_chunk_t*)data->payload_chunks */ -bool dump_cb_payload_chunk(pcapdroid_t *pd, const pkt_context_t *pctx, const char *dump_data, int dump_size) { + * (payload_chunk_t*)conn->payload_chunks */ +bool dump_cb_payload_chunk(pcapdroid_t *pd, pd_conn_t *conn, bool is_tx, uint64_t ms, uint32_t stream_id, const char *dump_data, int dump_size) { payload_chunk_t *chunk = calloc(1, sizeof(payload_chunk_t)); assert(chunk != NULL); chunk->payload = (u_char*)malloc(dump_size); @@ -224,10 +231,10 @@ bool dump_cb_payload_chunk(pcapdroid_t *pd, const pkt_context_t *pctx, const cha memcpy(chunk->payload, dump_data, dump_size); chunk->size = dump_size; - chunk->is_tx = pctx->is_tx; + chunk->is_tx = is_tx; // append to the linked list - payload_chunk_t *last = (payload_chunk_t*)pctx->data->payload_chunks; + payload_chunk_t *last = (payload_chunk_t*) conn->payload_chunks; if(last) { while(last->next) last = last->next; @@ -237,7 +244,7 @@ bool dump_cb_payload_chunk(pcapdroid_t *pd, const pkt_context_t *pctx, const cha num_chunks_lists++; chunks_lists_heads = realloc(chunks_lists_heads, num_chunks_lists * sizeof(void*)); chunks_lists_heads[num_chunks_lists - 1] = chunk; - pctx->data->payload_chunks = chunk; + conn->payload_chunks = chunk; } return true; diff --git a/app/src/main/jni/tests/test_utils.h b/app/src/main/jni/tests/test_utils.h index 57b682b3..50ac5d31 100644 --- a/app/src/main/jni/tests/test_utils.h +++ b/app/src/main/jni/tests/test_utils.h @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with PCAPdroid. If not, see . * - * Copyright 2022 - Emanuele Faranda + * Copyright 2022-26 - Emanuele Faranda */ #ifndef __TEST_UTILS_H__ @@ -52,7 +52,7 @@ void assert_pcap_header(pcap_hdr_t *hdr); u_char* next_pcap_record(pcap_rec_t *rec); // Callbacks -bool dump_cb_payload_chunk(pcapdroid_t *pd, const pkt_context_t *pctx, const char *dump_data, int dump_size); +bool dump_cb_payload_chunk(pcapdroid_t *pd, pd_conn_t *conn, bool is_tx, uint64_t ms, uint32_t stream_id, const char *dump_data, int dump_size); conn_and_tuple_t* assert_conn(pcapdroid_t *pd, int ipproto, const char *dst_ip, uint16_t dst_port, const char *info); diff --git a/submodules/PCAPdroid-ushark-bin b/submodules/PCAPdroid-ushark-bin index 905499ae..996a45cd 160000 --- a/submodules/PCAPdroid-ushark-bin +++ b/submodules/PCAPdroid-ushark-bin @@ -1 +1 @@ -Subproject commit 905499ae27d8bc6e2759a1d4c558a815804d5891 +Subproject commit 996a45cdc91ea8d5c6d15cef9a964118d0cc2fd1 diff --git a/submodules/nDPI b/submodules/nDPI index ce606bf2..20832156 160000 --- a/submodules/nDPI +++ b/submodules/nDPI @@ -1 +1 @@ -Subproject commit ce606bf28af1d6a4cb70707a4790e93aed231275 +Subproject commit 20832156595ed1420f574c17b24d713f8ce925bf