This commit is contained in:
End 2020-08-13 05:35:22 +02:00
parent f91da7a47c
commit d8b52dcb44
12 changed files with 278 additions and 119 deletions

View file

@ -34,11 +34,13 @@ add_executable(littlesnitch
mainwindow.cpp mainwindow.cpp
networkthread.cpp networkthread.cpp
session.cpp session.cpp
mainwindow.ui httpflow.cpp
mainwindow.h mainwindow.h
httpflow.h
networkthread.h networkthread.h
session.h session.h
includes.h includes.h
mainwindow.ui
) )
target_link_libraries(littlesnitch PRIVATE Qt5::Widgets cppzmq sqlite3) target_link_libraries(littlesnitch PRIVATE Qt5::Widgets cppzmq sqlite3)

2
httpflow.cpp Normal file
View file

@ -0,0 +1,2 @@
#include "httpflow.h"

153
httpflow.h Normal file
View file

@ -0,0 +1,153 @@
#pragma once
#include <includes.h>
/*
{
"flow": {
"client_conn": {
"address": [
"::1",
37570,
0,
0
],
"alpn_proto_negotiated": "http/1.1",
"cipher_name": "TLS_AES_256_GCM_SHA384",
"clientcert": null,
"id": "a1e82917-2d58-4b99-be9e-2b962bc499b2",
"mitmcert": "mitmcertstring",
"sni": "yolo.jetzt",
"timestamp_end": null,
"timestamp_start": 1597284668.6260498,
"timestamp_tls_setup": 1597284669.8449724,
"tls_established": true,
"tls_extensions": [...],
"tls_version": "TLSv1.3"
},
"error": null,
"id": "a6aa4e6e-ca31-4f58-bf47-2da7bfcf0000",
"intercepted": false,
"marked": false,
"metadata": {},
"mode": "transparent",
"request": {
"content": "",
"first_line_format": "relative",
"headers": [
[
"Host",
"yolo.jetzt"
],
[
"User-Agent",
"curl/7.68.0"
],
[
"Accept",
]
],
"host": "yolo.jetzt",
"http_version": "HTTP/1.1",
"is_replay": false,
"method": "GET",
"path": "/",
"port": 443,
"scheme": "https",
"timestamp_end": 1597284669.92817,
"timestamp_start": 1597284669.8761458
},
"response": null,
"server_conn": {
"address": [
"yolo.jetzt",
443
],
"alpn_proto_negotiated": "http/1.1",
"cert": "certstring",
"id": "50a3b79d-2912-45f3-991b-c03406a1018f",
"ip_address": [
"95.156.226.69",
443
],
"sni": "yolo.jetzt",
"source_address": [
"192.168.42.102",
44949
],
"timestamp_end": null,
"timestamp_start": 1597284669.2133315,
"timestamp_tcp_setup": 1597284669.2892282,
"timestamp_tls_setup": 1597284669.584602,
"tls_established": true,
"tls_version": "TLSv1.2",
"via": null
},
"type": "http",
"version": 7
},
"msg": "request"
}
*/
namespace http {
struct Request
{
std::string server_conn_id;
std::string server_ip_address;
bool tls;
std::string content;
std::string scheme;
std::string method;
std::string host;
std::string address;
unsigned short port;
std::string http_version;
std::string path;
double timestamp_start;
double timestamp_end;
std::vector<std::tuple<std::string,std::string>> headers;
std::string error;
};
struct Response {};
struct Flow {
Request request;
Response response;
};
inline void to_json(json& j, const Flow& flow) {}
inline void from_json(const json& j, Flow& flow) {
std::cout << std::setw(4) << j << "\n\n";
if(!j.contains("flow")) {
return;
}
if(j.at("flow").contains("server_conn")) {
j.at("flow").at("server_conn").at("id").get_to(flow.request.server_conn_id);
j.at("flow").at("server_conn").at("tls_established").get_to(flow.request.tls);
}
if(j.at("flow").contains("request")) {
j.at("flow").at("request").at("port").get_to(flow.request.port);
j.at("flow").at("request").at("host").get_to(flow.request.host);
j.at("flow").at("request").at("scheme").get_to(flow.request.scheme);
j.at("flow").at("request").at("path").get_to(flow.request.path);
j.at("flow").at("request").at("content").get_to(flow.request.content);
j.at("flow").at("request").at("method").get_to(flow.request.method);
j.at("flow").at("request").at("http_version").get_to(flow.request.http_version);
//j.at("server_conn").at("address").get_to(flow.server_conn_address);
}
}
}
Q_DECLARE_METATYPE(http::Flow)

View file

@ -5,5 +5,6 @@
#include <sqlite3.h> #include <sqlite3.h>
#include <filesystem> #include <filesystem>
#include <QDebug> #include <QDebug>
#include <iostream>
using json = nlohmann::json; using json = nlohmann::json;

View file

@ -8,6 +8,7 @@ MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), : QMainWindow(parent),
ui(new Ui::MainWindow) ui(new Ui::MainWindow)
{ {
int metatype_id = qRegisterMetaType<http::Flow>("httpflow");
current_session = new Session(); current_session = new Session();
current_session->load("/tmp/littlesnitch.session"); current_session->load("/tmp/littlesnitch.session");
@ -16,8 +17,8 @@ MainWindow::MainWindow(QWidget *parent)
worker->moveToThread(thread); worker->moveToThread(thread);
connect(thread, SIGNAL (started()), worker, SLOT (process())); connect(thread, SIGNAL (started()), worker, SLOT (process()));
//connect(worker, SIGNAL (error(QString)), this, SLOT (errorString(QString))); //connect(worker, SIGNAL (error(QString)), this, SLOT (errorString(QString)));
connect(worker, SIGNAL (httpMessage(json)), current_session, SLOT (saveRequest(json))); connect(worker, SIGNAL (httpMessage(http::Flow)), current_session, SLOT (saveRequest(http::Flow)), Qt::QueuedConnection);
connect(worker, SIGNAL (httpMessage(json)), this, SLOT (updateHistory(json))); connect(worker, SIGNAL (httpMessage(http::Flow)), this, SLOT (updateHistory(http::Flow)), Qt::QueuedConnection);
thread->start(); thread->start();
ui->setupUi(this); ui->setupUi(this);
@ -40,10 +41,10 @@ MainWindow::~MainWindow()
delete ui; delete ui;
} }
void MainWindow::updateHistory(json data) { void MainWindow::updateHistory(http::Flow flow) {
ui->historyHTTPTable->setItem(0,0,new QTableWidgetItem(QString::fromStdString(data["id"]))); ui->historyHTTPTable->setItem(0,0,new QTableWidgetItem(QString::fromStdString(flow.request.server_conn_id)));
// ui->historyHTTPTable->setItem(0,1,new QTableWidgetItem(QString::fromStdString(data.method))); ui->historyHTTPTable->setItem(0,1,new QTableWidgetItem(QString::fromStdString(flow.request.method)));
// ui->historyHTTPTable->setItem(0,2,new QTableWidgetItem(QString::fromStdString(data.url))); ui->historyHTTPTable->setItem(0,2,new QTableWidgetItem(QString::fromStdString(flow.request.host)));
// ui->historyHTTPTable->setItem(0,3,new QTableWidgetItem(QString::number(data.ttl))); //ui->historyHTTPTable->setItem(0,3,new QTableWidgetItem(QString::number(data.ttl)));
ui->historyHTTPTable->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); ui->historyHTTPTable->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents);
} }

View file

@ -4,6 +4,7 @@
#include <string> #include <string>
#include <session.h> #include <session.h>
#include <includes.h> #include <includes.h>
#include <httpflow.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; } namespace Ui { class MainWindow; }
@ -21,5 +22,5 @@ public:
MainWindow(QWidget *parent = nullptr); MainWindow(QWidget *parent = nullptr);
~MainWindow(); ~MainWindow();
public slots: public slots:
void updateHistory(json data); void updateHistory(http::Flow flow);
}; };

View file

@ -8,18 +8,6 @@ import zmq
import json import json
from enum import Enum from enum import Enum
NO_MSG = {"msg": None}
INIT_MSG = {"msg": "init"}
ACK_MSG = {"msg": "ack"}
PING_MSG = {"msg": "ping"}
PONG_MSG = {"msg": "pong"}
class NetworkState(Enum):
DISCONNECTED = auto()
CONNECTED = auto()
PING = auto()
SENDING = auto()
def convert_to_strings(obj): def convert_to_strings(obj):
if isinstance(obj, dict): if isinstance(obj, dict):
return {convert_to_strings(key): convert_to_strings(value) return {convert_to_strings(key): convert_to_strings(value)
@ -38,87 +26,57 @@ class NetworkThread(threading.Thread):
self.context = zmq.Context() self.context = zmq.Context()
def run(self): def run(self):
print("thread started")
self.connect() self.connect()
msg = self.send_msg_and_expect() while True:
timer = time.monotonic()
a = None
if not a:
try:
a = self.q.get(block=False)
except Empty:
pass
if a:
self.send_msg_and_ack(a)
timer = time.monotonic()
if timer - time.monotonic() < -5:
self.send_msg_and_ack({"msg": "ping"})
def disconnect(self): def disconnect(self):
self.socket.setsockopt(zmq.LINGER,0) self.socket.setsockopt(zmq.LINGER,0)
self.socket.close() self.socket.close()
print("disconnected")
def reconnect(self): def reconnect(self):
self.disconnect() self.disconnect()
time.sleep(1)
self.connect() self.connect()
def connect(self): def connect(self):
self.socket = self.context.socket(zmq.PAIR) self.socket = self.context.socket(zmq.PAIR)
self.socket.connect("tcp://127.0.0.1:12345") self.socket.connect("tcp://127.0.0.1:12345")
self.send_msg_and_ack({"msg": "ping"})
print("successfully connected")
def send_msg_and_expect(this, msg, expect, timeout=5, retries=3): def send_msg_and_ack(self, msg):
while retries: while True:
a = convert_to_strings(msg) a = convert_to_strings(msg)
self.socket.send(str.encode(json.dumps(a))) self.socket.send(str.encode(json.dumps(a)))
if (client.poll(REQUEST_TIMEOUT) & zmq.POLLIN) != 0: if (self.socket.poll(5) & zmq.POLLIN) != 0:
msg = self.socket.recv() msg = self.socket.recv()
try: try:
if msg: if msg:
result = json.loads(msg) result = json.loads(msg)
if result["msg"] in expect: if result["msg"] == "ack":
return result return result
else: else:
print("got unexpected message {result}") print("got unexpected message {result}")
except json.JSONDecodeError: except json.JSONDecodeError:
print(f"malformed message received {msg}") print(f"malformed message received {msg}")
retries -= 1 print("no ack received, reconnecting...")
self.reconnect() self.reconnect()
return NO_MSG
"""
def networking(q):
print("starting thread")
state = NetworkState.DISCONNECTED
a = None
while state == NetworkState.DISCONNECTED:
socket = context.socket(zmq.PAIR)
socket.connect("tcp://127.0.0.1:12345")
msg = get_msg(socket)
if msg["msg"] == "init":
send_msg(ACK_MSG, socket)
state = NetworkState.CONNECTED
timer = time.monotonic()
while state != NetworkState.DISCONNECTED:
if state == NetworkState.CONNECTED and timer - time.monotonic() >= 5:
timer = time.monotonic()
send_msg(PING_MSG,socket)
msg = get_msg(socket)
if msg["msg"] != "pong":
state = NetworkState.
msg = get_msg(socket)
if msg['msg'] == "ping":
send_msg(PONG_MSG, socket)
timer = time.monotonic()
if not a:
try:
a = q.get(block=False)
except Empty:
pass
if a:
send_msg(a, socket)
msg = get_msg(socket)
if msg["msg"] == "ack":
timer = time.monotonic()
a = None
self.q.task_done()
else:
connected = False
"""
class LittleSnitchBridge: class LittleSnitchBridge:
def __init__(self): def __init__(self):
self.q = Queue() self.q = Queue()
@ -127,11 +85,9 @@ class LittleSnitchBridge:
def request(self, flow): def request(self, flow):
self.q.put({'msg': 'request', 'flow': flow.get_state()}) self.q.put({'msg': 'request', 'flow': flow.get_state()})
self.q.join()
def response(self, flow): def response(self, flow):
self.q.put({'msg': 'response', 'flow': flow.get_state()}) self.q.put({'msg': 'response', 'flow': flow.get_state()})
self.q.join()
addons = [ addons = [
LittleSnitchBridge() LittleSnitchBridge()

View file

@ -1,40 +1,70 @@
#include "networkthread.h" #include "networkthread.h"
#include <iostream>
NetworkThread::NetworkThread(QObject *parent) : QObject(parent) NetworkThread::NetworkThread(QObject *parent) :
QObject(parent),
context(1)
{ {
} }
void NetworkThread::process() { void NetworkThread::connect() {
zmq::context_t ctx(1); socket = new zmq::socket_t(context, zmq::socket_type::pair);
zmq::socket_t sock(ctx, zmq::socket_type::pair); int linger = 0;
socket->setsockopt(ZMQ_LINGER, &linger, sizeof (linger));
try { try {
sock.bind("tcp://127.0.0.1:12345"); socket->bind("tcp://127.0.0.1:12345");
} catch (zmq::error_t err) { } catch (zmq::error_t err) {
qDebug() << "failed binding socket" << err.what(); qDebug() << "failed binding socket" << err.what();
emit error(err.what()); emit error(err.what());
return; throw;
}; };
qDebug() << "connected";
}
while(true){ void NetworkThread::disconnect() {
bool connected = false; delete socket;
while(!connected) { qDebug() << "disconnected";
sock.send(zmq::buffer(INIT_MSG_STR.c_str(), INIT_MSG_STR.length()), zmq::send_flags::dontwait); }
zmq::message_t msg;
const auto ret = sock.recv(msg, zmq::recv_flags::dontwait); void NetworkThread::reconnect() {
this->disconnect();
this->connect();
}
void NetworkThread::process() {
connect();
while(true) {
zmq::message_t response;
const auto ret = socket->recv(response, zmq::recv_flags::dontwait);
if(ret) { if(ret) {
if(msg.to_string() == ACK_MSG_STR) { auto j = json::parse(response.to_string());
connected = true; //std::cout << std::setw(4) << j << "\n\n";
if(j.contains("msg") && j["msg"].is_string()) {
std::string msg_type;
try {
j["msg"].get_to(msg_type);
} catch (nlohmann::detail::type_error& err) {
qDebug() << "json type error, message type not a string: " << response.to_string().c_str();
emit error("no message type");
} }
if(msg_type == "request") {
} else if (msg_type == "response") {
try {
emit httpMessage(j);
} catch (nlohmann::detail::type_error& err) {
qDebug() << "error reading HTTP Flow";
emit error("error converting to flow");
} }
} else if(msg_type == "ping") {
} else {
qDebug() << "unknown or broken message type received: " << msg_type.c_str();
emit error("unknown message");
} }
while(connected) { std::string m = "{\"msg\": \"ack\"}";
zmq::message_t msg; socket->send(zmq::buffer(m.c_str(), m.length()), zmq::send_flags::dontwait);
const auto ret = sock.recv(msg, zmq::recv_flags::dontwait); } else {
if(ret) { qDebug() << "broken message, but correct json: " << response.to_string().c_str();
qDebug() << msg.to_string().c_str();
emit httpMessage(json(msg.to_string()));
} }
} }
} }

View file

@ -2,15 +2,17 @@
#include <QObject> #include <QObject>
#include <includes.h> #include <includes.h>
#include <httpflow.h>
const std::string INIT_MSG_STR = R"({"msg": "init"})";
const std::string ACK_MSG_STR = R"({"msg": "ack"})";
class NetworkThread : public QObject class NetworkThread : public QObject
{ {
Q_OBJECT Q_OBJECT
private: private:
// add your variables here zmq::context_t context;
zmq::socket_t *socket;
void connect();
void disconnect();
void reconnect();
public: public:
explicit NetworkThread(QObject *parent = nullptr); explicit NetworkThread(QObject *parent = nullptr);
public slots: public slots:
@ -18,5 +20,5 @@ public slots:
signals: signals:
void finished(); void finished();
void error(QString err); void error(QString err);
void httpMessage(json data); void httpMessage(http::Flow flow);
}; };

View file

@ -32,6 +32,6 @@ void Session::unload() {
} }
} }
void Session::saveRequest(json data) { void Session::saveRequest(http::Flow flow) {
// todo // todo
} }

View file

@ -2,6 +2,7 @@
#include <QObject> #include <QObject>
#include <includes.h> #include <includes.h>
#include <httpflow.h>
// static int callback(void *NotUsed, int argc, char **argv, char **azColName){ // static int callback(void *NotUsed, int argc, char **argv, char **azColName){
// int i; // int i;
@ -51,7 +52,7 @@ public:
void unload(); void unload();
bool isLoaded(); bool isLoaded();
public slots: public slots:
void saveRequest(json data); void saveRequest(http::Flow flow);
signals: signals:
}; };

12
test.sh
View file

@ -4,5 +4,15 @@ mitmdump -k -p 8080 -s mitmaddon/littlesnitch.py &
./build/littlesnitch & ./build/littlesnitch &
sleep 1 sleep 1
curl -x http://localhost:8080 -k https://yolo.jetzt curl -x http://localhost:8080 -k https://get.yolo.jetzt
sleep 1
curl -x http://localhost:8080 -k https://get.yolo.jetzt
killall mitmdump
sleep 1
killall mitmdump
sleep 2
killall mitmdump
sleep 3
killall mitmdump
killall mitmdump