Closed martijnpoppen closed 1 year ago
same
Hi martijnpoppen,
this seem to be implemented by the firmware version 3.2.5.1. My HomeBase E gives another link to the image (T8002REDACTED~/media/mmcblk0p1/video/20221210235159_c00.jpg). My cams on the HomeBase 2 have not detect any motion since the firmware update.
I think, we have two options: We can use the stationDownload
function to download the image (i will try that tonight). Or, if this does not work, we have to implement function like bropat did it in his ioBroker plugin (onStationDownloadStart function in bropat/ioBroker.eusec). There he is downloading the video and create a thumbnail from the downloaded video. But this will add new dependencies (mainly ffmpeg).
Can we download the image as if we download the video?
Can we download the image as if we download the video?
I am not sure. I will try it later this day.
@PhilippEngler yeah that's the Issue for my app. (https://homey.app/en-us/app/com.eufylife.security/Eufy-Security/)
The Homey hardware is not powerful enough to run ffmpeg + no way to install it :/
Can we download the image as if we download the video?
I am not familiar with downloading video, can you explain me how to do it with Home Assistant or Node-red?
Downloading the image does not work. Eventually there is a new CommandType for downloading images. I could not find anything regarding this in the android app (v4.5.1), maybe someone else finds something.
For all recordings done with older firmware, the lifetime of the CDN-Links was reduced from 24 to 1 hour in my case.
I will check this soon...
I checked my Eufy Cam 2 and the notification image is still coming from the cloud, except that the image file is encrypted. The first 41 bytes are a custom header from Eufy that contains info to decrypt the beginning of the image data. Only the first 256 bytes of the image data are encrypted with AES.
Thanks for your checking @bropat. May I ask, what firmware version your homebase is running on? The problem here occurred after the homebase was updated to 3.2.5.1(h) and only for events after the firmware update. Very strange...
DEBUG: Normalized Properties { deviceSN: 'T8112N2X', properties: { motionDetected: false, personDetected: false, personName: '', name: 'Wäscheplatz Tor', model: 'T8112', serialNumber: 'T8112N2X', type: 1, hardwareVersion: 'HAIYI-IMX323', softwareVersion: '1.9.3', stationSerialNumber: 'T8002N2X', pictureUrl: 'T8002N2X~/media/mmcblk0p1/video/20221210235159_c00.jpg', recordingClipLength: 0, ...
@PhilippEngler thats the cover_path from the device response The one @bropat talks about is the cloud image from the notification
@bropat is the decryption something you want to put in this library? I was thinking that it might be nice to provide a decryption method which can be used by the clients using the library ?
I wish you all success. I hope you all can find a way to retrieve the last motion image (thumbnail). I always used it for sending to my telegram with (home assistant / node-red) because I did this for all my cams. It's fast and much better than opening all kinds of apps.
Thank you for clarification, @martijnpoppen, I have misunderstood that point, sorry.
I think the android app (apk) will show the way to decode the thumbnail ...
@bropat i tried several decryption methods based on the info you gave (first 41 bytes etc) but unfortunately i end up with corrupted images. Hopefully you can come up with a nice solution :D
@martijnpoppen A few days ago I successfully decrypted the image with the correct keys ;) But I still need to explore the generation of the correct key. Unfortunately I had a very stressful month at work and could not spend much time...
Thats moe than i achieved :p
No worries! Take your time and have nice holidays! 🍾🎄
Thank you all for the effort. Have a nice Christmas and lucky new year!
Thank you, you too
I am struggling with the 2fa again. I have to reconfigure it, to see if i get it working again.,
I will ask it again because I never got an answer. Is it possible to retrieve any video from the base station? What can we do with the eufy-security-ws library at this moment other than streaming start/stop and motion and ring detection? Can we retrieve any kind of video from the past?
@Beer17HWAM did you check commands and events of the repository and couldn't find your answer?
@Beer17HWAM did you check commands and events of the repository and couldn't find your answer?
I'am only able to start video streaming. I don't know if I can do more. I like to hear it from someone who knows. Maybe with examples in HA or Node-red. I can't find it myself.
You didn't answer my question? Did you go through the getting started documentation?
You didn't answer my question? Did you go through the getting started documentation?
Yes here: https://bropat.github.io/eufy-security-ws/#/api_cmds
But I don't know how to do use the commands and I hope someone can show me how to retrieve old videos.
So, your question had changed a lot. So you know now it is capable of doing it but you don't know how to do it.
You can more info below or different previously created issues.
https://github.com/bropat/eufy-security-ws/issues/31
Get video events Get cipher and path from events Send download video command with cipher and path You will receive bytes of video and audio with video data and audio data events
Thanks for the info. I was hoping for a step by step instruction manual and an example. I am familiar with scripting but this is a bit to complex for me.
it is not an easy thing, agree. Let me put more detailed example here, I am planning to add this feature into Home Assistant integration soon (get the latest recorded video)
send_message - {'messageId': '1', 'command': 'driver.get_video_events', 'maxResults': 1}
_on_message - {'type': 'result', 'success': True, 'messageId': '1', 'result': {'events': [{'monitor_id': 114484245, 'transfer_monitor_id': 0, 'station_sn': 'T8010N2320460480', 'device_sn': 'T8113N63205014E2', 'storage_type': 1, 'storage_path': '/media/mmcblk0p1/Camera01/20221230205117.dat', 'hevc_storage_path': '', 'cloud_path': '', 'frame_num': 149, 'thumb_path': 'T8010N2320460480~/media/mmcblk0p1/video/20221230205117_c01.jpg', 'thumb_data': '', 'start_time': 1672429877877, 'end_time': 1672429887784, 'cipher_id': 191, 'cipher_user_id': 'd0da85d5ba183f277c3d7eca940c4cb880c43651', 'has_human': 0, 'volume': 'Anker_B_JICiGmd', 'vision': 1, 'device_name': 'Entrance', 'device_type': 8, 'video_type': 1002, 'extra': 'eyJhdXRvbWF0aW9uX2lkIjowLCJoYXNfbWRldGVjdCI6MSwicHVzaF9tb2RlIjoyLCJtaWNfc3RhdHVzIjoxLCJyZWNvcmRfZm9ybWF0IjoxLCJwaWNrX3RpbWUiOjAsImRlbGl2ZXJfdGltZSI6MH0=', 'user_range_id': 387702, 'viewed': False, 'create_time': 1672429888, 'update_time': 1672429888, 'status': 1, 'station_name': '', 'p2p_did': 'HXEUCAM-135914-HKXPL', 'push_did': 'HXEUCAM-135914-HKXPL', 'p2p_license': 'RSEUZH', 'push_license': '', 'ndt_did': 'HXEUCAM-135914-HKXPL', 'ndt_license': '', 'wakeup_flag': 1, 'p2p_conn': '', 'app_conn': '', 'query_server_did': '', 'prefix': 'ZXCAMAA', 'wakeup_key': '', 'ai_faces': None, 'is_favorite': False, 'storage_alias': 1}]}}
send_message - call.data: {'message': {'messageId': '1', 'command': 'device.start_download', 'serialNumber': 'T8113N63205014E2', 'cipherId': 191, 'path': '/media/mmcblk0p1/Camera01/20221230205117.dat'}}
_on_message - {'type': 'result', 'success': True, 'messageId': '1', 'result': {'async': True}}
_on_message - {'type': 'event', 'event': {'source': 'device', 'event': 'command result', 'serialNumber': 'T8113N63205014E2', 'command': 'start_download', 'returnCode': 0, 'returnCodeName': 'ERROR_PPCS_SUCCESSFUL', 'customData': {'command': {'name': 'deviceStartDownload', 'value': {'path': '/media/mmcblk0p1/Camera01/20221230205117.dat', 'cipher_id': 191}}}}}
_on_message - {'type': 'event', 'event': {'source': 'device', 'event': 'download started', 'serialNumber': 'T8113N63205014E2'}}
_on_message - {'type': 'event', 'event': {'source': 'device', 'event': 'download video data', 'serialNumber': 'T8113N63205014E2', 'buffer': {'type': 'Buffer', 'data': [VIDEO BYTES ARE HERE]}, 'metadata': {'videoCodec': 'H264', 'videoFPS': 15, 'videoHeight': 1080, 'videoWidth': 1920}}}
_on_message - {'type': 'event', 'event': {'source': 'device', 'event': 'download video data', 'serialNumber': 'T8113N63205014E2', 'buffer': {'type': 'Buffer', 'data': [AUDIO BYTES ARE HERE]}, 'metadata': {'videoCodec': 'H264', 'videoFPS': 15, 'videoHeight': 1080, 'videoWidth': 1920}}}
Now, you need to merge video and audio bytes into one video file, save it somewhere so you can watch it later on. Or you can live stream these.
If I am right, the image path in the notification event is only contained when the extended push notification is enabled.
I have taken a deeper look at the app and the p2p communication: The app use p2p to fetch the preview image from the HomeBase. Therefor the "cover_path" is used. The "~" is to split the path in station serial and the path to the image.
For now, I was able to build the p2p request, sending it to the station and receiving data from the station. What is missing is parsing the data and store the image (and last but not least the confirmation, that the data contains the image).
You can take a look here: Link to PhilippEngler/eufy-security-client/commit/4bed085f413df9fb04ee35e8a71cac59d45011f1
Nice @PhilippEngler I'll have a look at that too! 👀
I guess if you can download the image this way it would also work for the notification images. As every notification has a thumb_path
. Then there's no need to decrypt the cloud image :)
fyi: The image data you receive via the p2p command is encrypted in the same way as the images of the cloud push notification.
I think I am close to the result.
The reverse engineering of .so files is much more complicated (libenc.so)... ;)
@bropat can we support in any way to help in the reverse engineering? Missing the pictures of the camera a lot :)
See my post fuatakgun/eufy_security#685
I start the Eufy Doorbell Stream and grab a part of the stream. I grab directly from the rtsp Eufy stream, not the go2rtc stream. It's working fine but indeed ffmpeg is slow to start, you miss the first seconds. But it works, you can send a few seconds to Telegram. I have it up and running now:
Start streaming
Grab piece of stream: ffmpeg -y -i rtsp://192.168.1.8:8554/T8210P0020330C10 -c copy -map 0 -t 6 ffmpeg-snapshot.mp4
Send mp4 to Telegram
Repeat this as long there is motion and keep it running for a half a minute or so when motion stops.
It works great. I tried to Grab one jpg frame but that is not working because the first part is indeed not "full". It will be a distorted jpg because of the delay of a keyframe. The mp4 is perfect. I grab 6 seconds.
I hope you can do something with this now. Good luck.
Sorry guys had a lot to do and so couldn't invest much time here. Anyone who wants to help is of course welcome. :)
As you know eufy has encrypted the images you receive through the push notification. The same encryption is also used when you try to download the images via P2P (see commit above from @PhilippEngler).
Example Image data:
eufysecurity:T8010P2XXXXXXXX8:0110972768:<256 bytes encrypted image header><unencrypted image data>
The first 41 bytes contain part of the required decryption information.
3 pieces of information are supplied (separated by :
):
eufysecurity
, the image data is encrypted, otherwise not.A fourth piece of information is needed and this is fetched directly from the cloud from the properties of the station, namely p2p_did
.
The following implementation is found in the code used for image decoding:
public byte[] decryptImage(byte[] image_data) {
int length = image_data.length - 41;
byte[] bImageData = new byte[length];
byte[] bSerialnumber= new byte[16];
byte[] bSecret = new byte[10];
// Station serialnumber
System.arraycopy(image_data, 13, bSerialnumber, 0, 16);
// Secret
System.arraycopy(image_data, 30, bSecret, 0, 10);
// Effective image data
System.arraycopy(image_data, 41, bImageData, 0, length);
String serialnumber = new String(bSerialnumber, StandardCharsets.UTF_8);
String secret = new String(bSecret, StandardCharsets.UTF_8);
QueryStationData queryStationData = StationDataManager.a().e(serialnumber);
if (e == null) {
return null;
}
// Get AES key passing station serialnumber, p2p_did and secret to the jndi function in libenc.so
String aeskey = Encoder.genCheckCode(serialnumber, queryStationData.p2p_did, secret).substring(0, 16);
byte[] bEncryptedImageHeader = new byte[256];
System.arraycopy(bImageData, 0, bEncryptedImageHeader, 0, 256);
// Decrypt first 256 image bytes using the derivated AES key
System.arraycopy(decrypt(bEncryptedImageHeader, aeskey), 0, bImageData, 0, 256);
return bImageData;
}
public byte[] decrypt(byte[] bEncryptedData, String aeskey) {
try {
SecretKeySpec secretKeySpec = new SecretKeySpec(aeskey.getBytes(StandardCharsets.UTF_8), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(2, secretKeySpec);
return cipher.doFinal(bEncryptedData);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
...
The following is the class that loads the native external library libenc.so
:
public class Encoder {
public static native String genCheckCode(String serialnumber, String p2p_did, String secret);
static {
System.loadLibrary("enc");
}
}
This library contains the knowledge of how to derive the AES key to decrypt the image header. For this reason, we need to understand how this works through reverse engineering.
I use ghidra for that.
So I load the library libenc.so
into ghidra and have it analysed. From this I get pseudo C code. Here you still have to correct the data types and sizes. Of course, you can also give the variables meaningful names, etc.
Behind the function genCheckCode
you will find the following procedures in the native library:
gen_pic_base_code(p_serialnumber,p_len_serialnumber,p_p2p_did,res_basecode);
gen_rand_seed(p_p2p_did,p_numericstr,res_seed);
gen_check_code_v1(res_basecode,res_seed,out_result);
I have tried to understand these procedures and translated them into the following functions in typescript:
import md5 from "crypto-js/md5";
import enc_hex from "crypto-js/enc-hex";
import sha256 from "crypto-js/sha256";
export const getIdSuffix = function(p2pDid: string): number {
let result = 0;
const match = p2pDid.match(/^[A-Z]+-(\d+)-[A-Z]+$/);
if (match?.length == 2) {
/*const num1 = Number.parseInt(match[1]);
const num2 = Number.parseInt(match[1].substring(1));
const num3 = Number.parseInt(match[1].substring(3));
const num4 = Number.parseInt(match[1].substring(5));*/
const num1 = Number.parseInt(match[1][0]);
const num2 = Number.parseInt(match[1][1]);
const num3 = Number.parseInt(match[1][3]);
const num4 = Number.parseInt(match[1][5]);
result = num1 + num2 + num3;
if (num3 < 5) {
result = result + num3;
}
result = result + num4;
}
return result;
};
export const getImageBaseCode = function(serialnumber: string, p2pDid: string): string {
let nr = 0;
try {
nr = Number.parseInt(`0x${serialnumber[serialnumber.length - 1]}`);
} catch (error) {
}
nr = (nr + 10) % 10;
const base = serialnumber.substring(nr);
return `${base}${getIdSuffix(p2pDid)}`;
};
export const getImageSeed = function(p2pDid: string, secret: string): string {
try {
const nsecret = Number.parseInt(secret.substring(2));
const prefix = 1000 - getIdSuffix(p2pDid);
return md5(`${prefix}${nsecret}`).toString(enc_hex).toUpperCase();
} catch(error) {
//TODO: raise custom exception
}
return ``;
};
export const getImageKey = function(serialnummer: string, p2pDid: string, secret: string): string {
const basecode = getImageBaseCode(serialnummer, p2pDid);
const seed = getImageSeed(p2pDid, secret);
const data = `10${basecode}${seed}`;
const hash = sha256(data);
const hashBytes = [...Buffer.from(hash.toString(enc_hex), "hex")];
for(let i = 0; i < 32; i++) {
const byte = hashBytes[i];
let fixed_byte = hashBytes[10];
if (i < 31) {
fixed_byte = hashBytes[i + 1];
}
if ((i == 31) || ((i & 1) != 0)) {
if (i != 31) {
hashBytes[10] = fixed_byte;
}
if ((126 < byte) || (126 < hashBytes[10])) {
if (byte < hashBytes[10] || (byte - hashBytes[10]) == 0) {
hashBytes[i] = hashBytes[10] - byte;
} else {
hashBytes[i] = byte - hashBytes[10];
}
}
} else if ((byte < 125) || (fixed_byte < 125)) {
hashBytes[i] = fixed_byte + byte;
}
}
return `${Buffer.from(hashBytes.slice(16)).toString("hex").toUpperCase()}`;
};
Unfortunately, I do not get the expected result. I think the error is in the second half of the getImageKey
function...
To understand where this effectively is, you have to debug the native library at runtime (e.g. with gdb).
So I wrote a simple Java console program that implements and calls the native class Encoder
so that I can test the native library better.
Encoder.java
package com.example.enc;
public class Encoder {
public static native String genCheckCode(String str, String str2, String str3);
static {
System.loadLibrary("enc");
}
}
HelloWorld.java
package com.example.enc;
import com.example.enc.Encoder;
import java.util.Scanner;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Start...");
String serialnumber = "0123456789000003";
String p2p_did = "ZXCAMAA-000000-HCUHU";
String numericstr = "0000000000";
if (args.length > 0) {
serialnumber = args[0];
}
if (args.length > 1) {
p2p_did = args[1];
}
if (args.length > 2) {
numericstr = args[2];
}
System.out.println("Serialnumber: " + serialnumber);
System.out.println("p2p_did: " + p2p_did);
System.out.println("numericstr: " + numericstr);
String result = Encoder.genCheckCode(serialnumber, p2p_did, numericstr);
System.out.println("1 Result length: " + result.length());
System.out.println("1 Result: " + result);
Scanner sc = new Scanner(System.in);
sc.nextLine();
result = Encoder.genCheckCode(serialnumber, p2p_did, numericstr);
System.out.println("2 Result length: " + result.length());
System.out.println("2 Result: " + result);
}
}
The line with sc.nextLine();
was implemented so that I can easily debug the process with gdb and be sure that the native library has already been loaded by the JVM (dalvikVM).
I compiled the Java code and created a jar file from the class files. I then converted this into a dex file using the Android SDK Build Tools (v. 33.0.0) with d8
:
.../Android/sdk/build-tools/33.0.0/d8 --output dex helloworld.jar
I then loaded the dex file by adb push
onto a rooted device or emulator including the libenc.so file (in the correct architecture of the device or emulator):
adb push classes.dex /data/local/tmp
adb push libenc.so /data/local/tmp
Then I started the program on the device or emulator as follows:
adb shell
su -
cd /data/local/tmp
export LD_LIBRARY_PATH=/data/local/tmp
dalvikvm64 -cp classes.dex com.example.HelloWorld 0123456789000003 ZXCAMAA-000000-HCUHU 0000000000
Output of the above command:
Start...
Serialnumber: 0123456789000003
p2p_did: ZXCAMAA-000000-HCUHU
numericstr: 0000000000
output:: ts:0000000000, check code: D174D8169229AD39F55E6C3D801E4169
1 Result length: 32
1 Result: D174D8169229AD39F55E6C3D801E4169
<ENTER>
output:: ts:0000000000, check code: D174D8169229AD39F55E6C3D801E4169
2 Result length: 32
2 Result: D174D8169229AD39F55E6C3D801E4169
If you pass the correct input values here, you will get the AES key to decrypt the image header.
Attention: Only the first 16 bytes are used as AES key (see Java code at the beginning).
I have now fetched the gdbserver
for the correct architecture of the device or emulator from the Android NDK (older version still supplied with gdb), loaded it with adb push
and then started it.
adb push gdbserver /data/local/tmp
adb shell
su -
ps -edf | grep dalvikvm
adb push gdbserver /data/local/tmp
adb shell
su -
cd /data/local/tmp
./gdbserver --attach :<port> <pid>
# Example: ./gdbserver --attach :5055 1234
Now you can forward the previously selected port with adb so that you can debug from the PC:
adb forward tcp:5055 tcp:5055
localhost:5055
and start debugging.So far, I have not been able to read the correct registers or memory areas to understand what was misinterpreted in the typescript code above. I assume that there is an error in the generated pseudo C code of ghidra. Here you could also check the assembler code using the official documentation from ARM to understand what ghidra has interpreted wrong... Another challenge... ;)
The challenge is now to get the correct result with the following input values and thus solve the mystery ;)
serialnumber = "0123456789000003";
p2pDid = "ZXCAMAA-000000-HCUHU";
code = "0000000000";
Expected outcome: D174D8169229AD39F55E6C3D801E4169
If someone can be helpful here would be great :)
Annex:
I have compiled the Java code with the following commands:
javac -source 1.7 -target 1.7 -d bin src/com/example/enc/Encoder.java
javac -source 1.7 -target 1.7 -d bin -cp bin/com/example/enc src/com/example/enc/HelloWorld.java
I'll have a look soon :)
So small update from my side
I spend some long evenings on this. Unluckily I'm not really helpful...
Java and reverse engineering APK's are not my expertise. Certainly not reading Peudo Code C files 😅
I did take another approach at getting the genCheckCode
result.
I hooked up frida
to my emulator and ofcourse I also got D174D8169229AD39F55E6C3D801E4169
back.
Next to that I tried to dissasemble the libhome_security.so
(same as libenc.so
?) with RetDec
and IDA Pro
and Hopper
.
The IDA Pro
file is better readable than the RetDec variant, but still I couldn't manage to get anything useful out of it.
If it helps I can share you the IDA Pro functions.
Also trying another approach:
I downloaded the ROM
of my indoorcam. Hoping there would be a encrypt function somewhere which would help us decrypting it. No luck yet, but will dig further. Some info on the ROM here
Might be interesting...: it seems like the Homebase 3 isn't encrypting it's images..
I have also spend some time on this. Yes, @martijnpoppen, the functions from libenc.so
were moved to libhome_security.so
in v4.5.4 (and the libhome_security.so
should also includes the new p2p messages types).
I have also tried to understand the code. From my side, I have a slightly different implementation of the for
-statement of @bropat's getImageKey
-function. But the result is the same, so that I believe, that the problem is in one of the three functions before. I will have a look on this.
One question I have regarding @bropat's implementation:
Does ${Buffer.from(hashBytes.slice(16)).toString("hex").toUpperCase()}
will return us the really the first 16 bytes? I have understand the slice-function so, that in this case we will get the last 16 bytes. ${Buffer.from(hashBytes.slice(0,16)).toString("hex").toUpperCase()}
will return the first 16 bytes? But maybe (surely) I am completely wrong.
The non-encrypting thing with the HB3 is interesting... All in all I have the impression, that they are being under time pressure. The app versions v4.5.4 and v4.5.5 for example does not show the mode changing events from HomeBase 2 or E while they are on firmware version below 3.2.6.7. And the app v4.5.3 does not show the mode changing events from HomeBase firmware 3.2.6.7.
Ah thanks @PhilippEngler that makes sense. Saw that GenCheckCode is there since app V2.
I do think you're right about the 3 first functions, also tried some different implementations but got the same result everytime.
Next to that I did just find something in the firmware 😄
There's home_security file there too with functions save_the_enc_pic
, gen_rand_seed
, gen_check_code_v1
, gen_base_code
Might be useful ?
Thank you @martijnpoppen for the information about the GenCheckCode occurrence in the app.
I do think you're right about the 3 first functions, also tried some different implementations but got the same result everytime.
That sounds quite good. @bropat has done great work with the implementation.
Next to that I did just find something in the firmware 😄 There's home_security file there too with functions
save_the_enc_pic
,gen_rand_seed
,gen_check_code_v1
,gen_base_code
Might be useful ?
Yes, this could maybe help. The results should be the same. Is there a possibility to send me the IDA Pro
result of the three functions, I want to check if there are differences to Ghidra
(I think we should not share the disassembled code here in the comments).
I hope that you have success on the firmware-way.
Yes i can send you the file. Anywhere we can reach each other? 😄 And also the firmware .so file if you're interested in that. Decompiling that one is a bit harder
Yes i can send you the file. Anywhere we can reach each other? 😄 And also the firmware .so file if you're interested in that. Decompiling that one is a bit harder
From my side, the best way is by email: I would ask you, if you could take a look here, there you can find the address (it's the package.json of my project): PhilippEngler/eufy-security-hm/package.json in the author
key. It lead to my private mail, so no other person will receive this email. Many thanks in advance.
I will take what I can get and try my best :smile:.
One question I have regarding @bropat's implementation: Does
${Buffer.from(hashBytes.slice(16)).toString("hex").toUpperCase()}
will return us the really the first 16 bytes? I have understand the slice-function so, that in this case we will get the last 16 bytes.${Buffer.from(hashBytes.slice(0,16)).toString("hex").toUpperCase()}
will return the first 16 bytes? But maybe (surely) I am completely wrong.
The gen_check_code_v1
function produces a SHA256 hash at the end, whereby only the last 16 bytes (of the total 32 bytes) are converted into uppercase hex string (32 characters long) and returned as the result.
From this result, the first 16 characters of the string are then used as the AES key.
@martijnpoppen can you send me the IDA functions too? :)
@bropat @PhilippEngler Sent! :D
The IDA files only contain the relevant functions. Someone else decompiled it for me, as I don't have the pro version
Thank you very much.
I quickly skimmed the code and there is still 1 relevant function j_cal_ppcs_id_suffix
missing. Can you ask for these as well? :)
It's in your mail @bropat
@martijnpoppen
I need the following file <IDA installation folder>\plugins\defs.h
from the used IDA version.
Thanks again :)
Ask your question
Hey @bropat , Seems like Eufy patched more in their API and now the cover_path is moved from a CDN url to
T8410REDACTED~/media/mmcblk0p1/Camera00/20221210230725n.jpg
Any idea if it's possible to fetch an image from the station itself?