Research: Kill the Router with one request

13 minute read


I discoverd a bug while playing around with the ZXHN H168N V3.5 home router device which result in killing the router and make it go down with one request. So, If you want to get the device alive again, You have to restart it or wait for hours. Let’s go and explain it.

Spotting the Bug

I was doing the test as a blackbox without looking into hte firmware (We will look at the firmware later). First go to your device dashboard and login. Then, go to Local network tab. After that move to WLAN. Finally for sure don’t forget your Burp Suite tool, Since we doing the test on the web Application interface level. So, we be able to intercept the requests and play around with it. As the following step showed in the bellow picture: open any of the WLAN SSID and click on save.


But, before this open burp suite to intercept the request.


Now, i will send the request to the repeater to explain the vulnerability, by press right click and choose send to repeater :


Let’s go to the repeater tab and play with the request, You will see the request then click go:


So, Let’s explain what actual happened and what the request doing and what is the response for it. The request is about modifying the WLAN settings and You can see in the response tab, In the body you can see the XML response from server and has many objects, as example the <INSTIDENTITY> you will see that this object has a value “DEV.WIFI.AP1.PSK1”, it looks like a value refering to WIFI Access point 1, Casue as you saw before we had 2 Access points in the router.

Now, we have XML response in this web application, We will try XXE (iNJECTING SOME xml body to check for it). I even take the response and send it back as a request.

When, I did that it’s actually back to me with more information about the Parameters and each value for each Paramenter as you can see in the below screenshot.


I tried other payloads related to XXE ,. But, non of payloads worked. So, This bug is limited. Now, this bug is authenticated, When we are not authenticated it will not show any information. But, it will parse our request without any problems.

Under The Hood

Now, We will take a static look at the backend code to understand more. So, what we need now is the Firmware for the device and we have many ways to do it:

  • You can search for the firmware on the official website for the vendor.
  • Download it from any other source (after someone already dump it from the device and published it).
  • Dump the firmware through URAT, You could read a detailed blog from Here.
  • Finally dumping the firmware using CH341A Mini programmer USB, You could read a detailed blog from Here.

In my case, I found that someone dump it before, Therefor, i download it. Now, Let’s extract the firmware using Binwalk tool and discover the firmware files. Specially the vulnerable end-point file.

  • Extracting the firmware: binwalk -e firmware. The -e is for extracting.


In the above screenshot it’s done successfully and we extracted the firmware as we can see in the following picature, we moved to the firmware extracted directory:


Now, if we go to the /squashfs-root directory, We can see the directory to all the Linux system directories which the firmware router system built-on. Under the /home directory we will find the /httpd directory which home directory for the web service user that hosting the web-interface for the router. (You can see on the following picature)


Now, backing to the URL of the end-point we were testing, /common_page/Localnet_WlanBasicAd_WLANSSIDConf_EncryOption_lua.lua. It’s clearly the interface written in Lua programming language. Let’s move on to our vulnerable page under /common_page directory. If we list the files in the directory and grep the first words from the page name we can see the following:


We can see that the page full name is not here (It may be a one old step realse from the actual one we do have). So, I will pick up the Localnet_WlanBasicAd_WLANSSIDConf_lua.lua which is the closer one to our page and if we checked the file using file command, We will be able to see that it’s a Lua, ByteCode, Version 5.1:


If we tried to read the file using cat command, the output will be non-understandable representation:


Lua ByteCode

As we saw before the file is Lua ByteCode. After a couple hours of search, I was able to find this website to decompile Lua ByteCode. Therefor, we will be able to analysis and understand what happen in the background.


We can see that we have a lot of code here and if we scroll down you gonna see some instructions looks like Assembly, After searching for the Lua Bytecode. I found the follwoing reference with the instructions and description for each instruction:


So, Now we can start to read and understand. If we take a look at the first few lines it’s giving us information about the language version, etc. But, the starting part where we can see the main function and it’s information:

************ Main Function
Source: @/home/xialei/Builds/H168NV31_Develop_BaseCSP3005P01/chip_mtk7510/product/H168NV31_BELT/scripts/../basefilesystem/luapages/html//common_page/Localnet_WlanBasicAd_WLANSSIDConf_lua.lua
Parameters: 0
Is vararg: yes
Local variable 'arg': absent
VM registers needed: 36
  • Source: The source code file directory (Which also seems from the firmware team developers under /home).
  • Parameters: Number of functions parameters.
  • Is Vrarg: Meaning it can accept a variable number of arguments.
  • Local variable 'arg': absent:is a special variable in Lua that refers to a list of arguments passed to a function.
  • VM registers needed: 36: Requirted VM Registers to execute.]

Under the main functions we can find 2 things Constants which are variables and can not be changed, Also Locals which are defined local variables.

  • Constants
    Constants: 61
    +0658:Size=7: 72 65 71 75 69 72 65            Const#1   string    "require"
    +0665:Size=E: 6C 69 62 2F 63 6F 6D 6D 6F...   Const#2   string    "lib/common_lua"
    +0679:Size=0:                                 Const#3   string    ""
    +067F:Size=A: 49 46 5F 45 52 52 4F 52 49 44   Const#4   string    "IF_ERRORID"
    +068B:Size=8: 00 00 00 00 00 00 00 00         Const#5   float     0.0
    +0694:Size=8: 3F F0 00 00 00 00 00 00         Const#6   float     1.0
    +06A1:Size=6: 63 67 69 6C 75 61               Const#7   string    "cgilua"
    +06AD:Size=B: 72 65 6D 6F 74 65 5F 61 64...   Const#8   string    "remote_addr"
    +06BE:Size=B: 73 65 73 73 69 6F 6E 5F 67...   Const#9   string    "session_get"
    +06CF:Size=5: 52 69 67 68 74                  Const#10  string    "Right"
    +06DA:Size=11: 4F 42 4A 5F 57 4C 41 4E 4...   Const#11  string    "OBJ_WLANCONFIG_ID"
    +06F1:Size=1: 33                              Const#12  string    "3"
    +06F8:Size=5: 45 53 53 49 44                  Const#13  string    "ESSID"
    +0703:Size=A: 42 65 61 63 6F 6E 54 79 70 65   Const#14  string    "BeaconType"
    +0713:Size=B: 57 45 50 41 75 74 68 4D 6F...   Const#15  string    "WEPAuthMode"
    +0724:Size=B: 57 45 50 4B 65 79 49 6E 64...   Const#16  string    "WEPKeyIndex"
    +0735:Size=B: 57 50 41 41 75 74 68 4D 6F...   Const#17  string    "WPAAuthMode"
    +0746:Size=E: 57 50 41 45 6E 63 72 79 70...   Const#18  string    "WPAEncryptType"
    +075A:Size=B: 31 31 69 41 75 74 68 4D 6F...   Const#19  string    "11iAuthMode"
    +076B:Size=E: 31 31 69 45 6E 63 72 79 70...   Const#20  string    "11iEncryptType"
    +077F:Size=6: 45 6E 61 62 6C 65               Const#21  string    "Enable"
    +078B:Size=D: 57 50 41 47 72 6F 75 70 52...   Const#22  string    "WPAGroupRekey"
    +079E:Size=F: 45 53 53 49 44 48 69 64 65...   Const#23  string    "ESSIDHideEnable"
    +07B3:Size=12: 56 61 70 49 73 6F 6C 61 7...   Const#24  string    "VapIsolationEnable"
    +07CB:Size=8: 50 72 69 6F 72 69 74 79         Const#25  string    "Priority"
    +07D9:Size=A: 4D 61 78 55 73 65 72 4E 75 6D   Const#26  string    "MaxUserNum"
    +07E9:Size=11: 4F 42 4A 5F 57 4C 41 4E 5...   Const#27  string    "OBJ_WLANWEPKEY_ID"
    +0800:Size=6: 57 45 50 4B 65 79               Const#28  string    "WEPKey"
    +080C:Size=E: 4F 42 4A 5F 57 4C 41 4E 50...   Const#29  string    "OBJ_WLANPSK_ID"
    +0820:Size=D: 4B 65 79 50 61 73 73 70 68...   Const#30  string    "KeyPassphrase"
    +0833:Size=19: 51 75 65 72 79 57 43 61 7...   Const#31  string    "QueryWCardAndSSIDIdentity"
    +0852:Size=4: 50 4F 53 54                     Const#32  string    "POST"
    +085C:Size=9: 49 46 5F 41 43 54 49 4F 4E      Const#33  string    "IF_ACTION"
    +086B:Size=9: 5F 57 45 50 43 4F 4E 49 47      Const#34  string    "_WEPCONIG"
    +087A:Size=9: 5F 50 53 4B 43 4F 4E 49 47      Const#35  string    "_PSKCONIG"
    +0889:Size=7: 5F 49 6E 73 74 49 44            Const#36  string    "_InstID"
    +0896:Size=C: 5F 49 6E 73 74 49 44 5F 57...   Const#37  string    "_InstID_WEP0"
    +08A8:Size=C: 5F 49 6E 73 74 49 44 5F 57...   Const#38  string    "_InstID_WEP1"
    +08BA:Size=C: 5F 49 6E 73 74 49 44 5F 57...   Const#39  string    "_InstID_WEP2"
    +08CC:Size=C: 5F 49 6E 73 74 49 44 5F 57...   Const#40  string    "_InstID_WEP3"
    +08DE:Size=B: 5F 49 6E 73 74 49 44 5F 50...   Const#41  string    "_InstID_PSK"
    +08EF:Size=E: 74 72 61 6E 73 54 6F 50 6F...   Const#42  string    "transToPostTab"
    +0903:Size=10: 74 72 61 6E 73 54 6F 46 6...   Const#43  string    "transToFilterTab"
    +0919:Size=5: 41 70 70 6C 79                  Const#44  string    "Apply"
    +0924:Size=1: 59                              Const#45  string    "Y"
    +0927:Size=0:                                 Const#46  nil       nil
    +092C:Size=13: 61 70 70 6C 79 4F 72 4E 6...   Const#47  string    "applyOrNewOrDelInst"
    +0945:Size=8: 57 45 50 4B 65 79 30 30         Const#48  string    "WEPKey00"
    +0953:Size=8: 57 45 50 4B 65 79 30 31         Const#49  string    "WEPKey01"
    +0961:Size=8: 57 45 50 4B 65 79 30 32         Const#50  string    "WEPKey02"
    +096F:Size=8: 57 45 50 4B 65 79 30 33         Const#51  string    "WEPKey03"
    +097D:Size=6: 43 61 6E 63 65 6C               Const#52  string    "Cancel"
    +0989:Size=12: 67 65 74 53 70 65 63 69 6...   Const#53  string    "getSpecificInstXML"
    +09A1:Size=5: 74 61 62 6C 65                  Const#54  string    "table"
    +09AC:Size=6: 69 6E 73 65 72 74               Const#55  string    "insert"
    +09B8:Size=F: 53 53 49 44 46 69 6C 74 65...   Const#56  string    "SSIDFilterByNIC"
    +09CD:Size=D: 67 65 74 41 6C 6C 49 6E 73...   Const#57  string    "getAllInstXML"
    +09E0:Size=3: 49 47 44                        Const#58  string    "IGD"
    +09E9:Size=6: 63 6F 6E 63 61 74               Const#59  string    "concat"
    +09F5:Size=E: 6F 75 74 70 75 74 45 72 72...   Const#60  string    "outputErrorXML"
    +0A09:Size=9: 6F 75 74 70 75 74 58 4D 4C      Const#61  string    "outputXML"

As you can see we have 3 fields:

  • Const#<number>: the Constant variable number.
  • <Type>: The variable data type (e.x:Float,stringm etc).
  • <Name>: The variable name.

Now, to the Locals variables:

  • Locals
    Locals: 29
    Local#1   R0   def:<4>    scope:<5..349>      name: InstXML
    Local#2   R1   def:<5>    scope:<6..349>      name: ErrorXML
    Local#3   R2   def:<6>    scope:<7..349>      name: OutXML
    Local#4   R3   def:<7>    scope:<9..349>      name: tError
    Local#5   R4   def:<9>    scope:<10..349>     name: need2Get
    Local#6   R5   def:<10>   scope:<11..349>     name: xmlStr
    Local#7   R6   def:<12>   scope:<13..349>     name: sess_id
    Local#8   R7   def:<16>   scope:<17..349>     name: uRight
    Local#9   R8   def:<17>   scope:<18..349>     name: PARA
    Local#10  R9   def:<18>   scope:<19..349>     name: FP_OBJNAME
    Local#11  R10  def:<50>   scope:<51..349>     name: WEP_OBJNAME
    Local#12  R11  def:<51>   scope:<54..349>     name: WEP_PARA
    Local#13  R12  def:<54>   scope:<55..349>     name: PSK_OBJNAME
    Local#14  R13  def:<55>   scope:<58..349>     name: PSK_PARA
    Local#15  R14  def:<58>   scope:<59..349>     name: WLANNICNum
    Local#16  R15  def:<59>   scope:<60..349>     name: WNICIdentity
    Local#17  R16  def:<60>   scope:<61..349>     name: WLANBasicIDTable
    Local#18  R17  def:<68>   scope:<69..349>     name: FP_ACTION
    Local#19  R18  def:<71>   scope:<72..349>     name: _WEPCONIG
    Local#20  R19  def:<74>   scope:<75..349>     name: _PSKCONIG
    Local#21  R20  def:<77>   scope:<78..349>     name: FP_IDENTITY
    Local#22  R21  def:<80>   scope:<81..349>     name: WEP_IDENTITY0
    Local#23  R22  def:<83>   scope:<84..349>     name: WEP_IDENTITY1
    Local#24  R23  def:<86>   scope:<87..349>     name: WEP_IDENTITY2
    Local#25  R24  def:<89>   scope:<90..349>     name: WEP_IDENTITY3
    Local#26  R25  def:<92>   scope:<93..349>     name: PSK_IDENTITY
    Local#27  R26  def:<93>   scope:<94..349>     name: xmlTable
    Local#28  R27  def:<96>   scope:<97..349>     name: WEP_PARA_POST
    Local#29  R28  def:<99>   scope:<100..349>    name: WEP_PARA_FILTER

Here we can see that there are 5 fields:

  • Local#<number>: The Local variable number.
  • R<number>: The register that the variable assigned to.
  • def:<number>: Line that the variable defined on, In the code.
  • scope:<ramge>: indicates that the variable is in scope (can be accessed and modified) from a range of lines in the code.
  • <Name>: Finally the variable name.

If we take a look at the Constants & Local variable names including the same parameters in our request.:

IF_ACTION | Const#33  string    "IF_ACTION"
Enable | Const#21  string    "Enable"
_InstID | Const#36  string    "_InstID"
_PSKCONIG | Local#20  R19  def:<74>   scope:<75..349>     name: _PSKCONIG
BeaconType | Const#14  string    "BeaconType"
WPAAuthMode | Const#17  string    "WPAAuthMode"
11iAuthMode | Const#19  string    "11iAuthMode"
WPAEncryptType | Const#18  string    "WPAEncryptType"
11iEncryptType | Const#20  string    "11iEncryptType"
_InstID_PSK | Const#41  string    "_InstID_PSK"
ESSID | Const#13  string    "ESSID"
ESSIDHideEnable | Const#23  string    "ESSIDHideEnable"
KeyPassphrase | Const#30  string    "KeyPassphrase"

and many more parameters......
  • Note: you could find differents in files becuase of the firmware version & realse

Also there are some cariables has the XML name in as the following which explains the XML format type:

  Local#1   R0   def:<4>    scope:<5..349>      name: InstXML
  Local#2   R1   def:<5>    scope:<6..349>      name: ErrorXML
  Local#3   R2   def:<6>    scope:<7..349>      name: OutXML
  Local#6   R5   def:<10>   scope:<11..349>     name: xmlStr
  Local#27  R26  def:<93>   scope:<94..349>     name: xmlTable

Now, backing to the request again as the _InstID is a Const variable. It has a value in the request as we can see, But when we try to change the value. It’s giving us error:

  • Before changing the value c4304cc09afb3069fb431e6daf75fd09.png

  • After Changing the value, It’s giving an error. fdb8d7b7742fdb501237c80eca7df7a9.png

  • Here Trying to flow it with huge value to get any crash, andIt didn’t work. 96aa2d3ac3c904ce11eef47317e294ad.png

Final Thoughts

As we didn’t do any dynamic analysis. Cause it will require us to emulate the firmware and debug it,To make everything clear. But, there are a logical posability that the bug in the following line:

GETTABLE 20 20 -36     <77>   R20@FP_IDENTITY = R20["_InstID"]

Let’s explain the line first the GETTABLE operation is used to retrieve a value from a table which in this case is the XMLtable and it will retrive the value stored in the key _InstID and store it into FP_IDENTITY that holded at R20. In this case, As we pass a huge value it will crash the Lua VM which running the app.


We can not fully admit that the Final Thoughts we discuss is the right or final one as we didn’t dig deep into and perform dynamic analysis. But, we would be analyzing it dynamically to fully verify the Final Thoughts. Also, it could be the same way but in another place or line of code.