This article is a highlight from the Kanxue Forum.
Author ID on Kanxue Forum: Shaobanjia
After reading the STM32 firmware reverse engineering thread on the forum (https://bbs.pediy.com/thread-272811.htm), I found a help request in the comments about the STM32 chip program that uses the XTEA encryption algorithm, but I couldn’t understand the data sorting issue (https://bbs.pediy.com/thread-272872.htm). So I thought I would analyze it and practice a bit.
Based on the help request, I summarized the useful information.
Plaintext 110 BE 62 F8 E8 DC 34 46 Ciphertext 18C 79 F5 D1 5E A9 46 2D Plaintext 20E 77 50 C8 C6 27 E1 BF Ciphertext 236 0A 1A 6E 6E FE F0 84
Next, the goal is to find the encryption function and restore the encryption process based on the above information.
Start
First, open the stm32f103RCT6.bin file with IDA, select ARM little-endian, and enter.
Opening directly as a Binary file shows that IDA did not recognize any functions.
Habitually, I check the first few bytes for “D” three times, as shown in the figure, and can boldly guess that the loading base address is 0x8000000. In fact, it is indeed so, of course, you can check the datasheet according to the MCU model stm32f103RCT6. For more methods to determine the base address, please refer to this forum post on firmware security and loading address analysis (https://bbs.pediy.com/thread-267719.htm), which details various methods to determine the base address, so I won’t elaborate here.
Edit > Segments > Rebase program… Reset the base address to 0x8000000. After setting the base address, we have made a big step towards success.
Next, press Alt + L to start selecting from the first few addresses to the end, and right-click to select “Analyze selected area”.
A prompt box appears; select Analyze, and wait for the IDA analysis process to finish.
At this point, you can see that IDA has recognized many functions.
Next, use the Findcrypt plugin to search for encryption constants to see if there are any findings. Search > Find crypto constants, as shown in the figure. You can see a TEA delta encryption constant, which corresponds to the XTEA encryption used during MCU communication.
Track and view the sub_800E288 function that references this constant; the F5 result is shown in the figure below:
Comparing with the XTEA encryption C code online, we can see that the parameters of the sub_800E288 function from left to right are the encryption key, encryption input, and encryption output.
Referring to the XTEA encryption C code above, we can rename some variables as shown in the figure. You can see that the key input during encryption is not directly used as the XTEA encryption key but has undergone some operations for transformation.
Next, we analyze the encryption process in detail. First, in the front part, there are many left shifts of 24, 16, and 8. Familiar friends may quickly realize that this is for endianness conversion. Big-endian refers to “high order first, low order last,” while little-endian refers to “low order first, high order last.” For example, for the same 4-byte byte array “12 34 56 78,” in big-endian, it is treated as 0x12345678, while in little-endian, it is treated as 0x78563412. The operations “<<24 <<16 <<8” are used to convert the 4-byte array into a big-endian integer (the MCU is little-endian, so directly using *(int *) type cast would yield a little-endian int).
Ignoring the left shifts for endianness conversion, we can see that there is no special processing for the input “in” before the actual XTEA encryption, while each byte of the key undergoes XOR processing with some constants. Similarly, seeing the values “0x66, 0x6F…”, these values, if familiar, can easily be recognized as ASCII codes.
Press the TAB key to switch to view the disassembled code, as shown in the figure. You can see that these constants are based on the offset starting at address 0x8030A30.
Viewing address 0x8030A30, pressing “A” gives a string as shown in the figure, which is clearly a string with a story.
Returning to the decompiled code window, pressing F5 again shows that the decompiled code is clearer. The key is XORed with the corresponding byte of the above string before being used for XTEA encryption.
Now looking at the latter part, as shown in the figure, the latter part is actually standard XTEA encryption, but there is also endianness conversion for the output of the encryption result. “HIBYTE(x) BYTE2(x) BYTE1(x)” is similar to the operations “<<24 <<16 <<8” seen earlier, which are commonly used in data endianness conversion.
Is this the end? – The answer is no. The help request mentioned that the MCU registration key is “BA2F96A9”, which is only 4 bytes in size, but the encryption function’s key input has 16 bytes, so there is still some process to analyze from the MCU registration key “BA2F96A9” to the encryption key userKey.
Press “X” on sub_800E288 to view its cross-references, as shown in the figure. There is only one call.
View that call, as shown in the figure. The first parameter, the encryption key, is passed in at address 0x20000104, so the encryption key is stored at address 0x20000104. Next, we just need to check which functions write data to address 0x20000104 to find the key transformation function.
Next, use Text Search to search for 0x20000104 to see which functions use address 0x20000104.
As shown in the figure, the search results are not many. After checking them one by one, we can see that only the sub_800F3C8 function has code that writes data to address 0x20000104, and the parameter of sub_800F3C8 is of unsigned int type (exactly 4 bytes), so we can basically confirm that this function is the one that transforms the MCU registration key into the encryption key.
Next, we extract the decompiled pseudocode from the sub_800F3C8 and sub_800E288 functions and write a simple program in VS Code to verify it. The code is as follows:
#include <stdio.h>
#include "defs.h"
void keyExpand(unsigned int key, _BYTE *outKey);
void XTEA(_BYTE *userkey, _BYTE *in, _BYTE *out);
void keyExpand(unsigned int key, _BYTE *outKey) {
int i; // r0
for (i = 0; i < 16; i = (i + 1))
*(i + outKey) = key >> (8 * (3 - i % 4));
}
void XTEA(_BYTE *userkey, _BYTE *in, _BYTE *out) {
unsigned int v3; // r3
unsigned int v4; // r4
unsigned int v5; // r5
unsigned int i; // r6
int v7[4]; // [sp+0h] [bp-34h]
unsigned int v8; // [sp+10h] [bp-24h]
unsigned int v9; // [sp+14h] [bp-20h]
char aStefanlovesmay[] = "StefanLovesMaya!";
if (userkey != 0 && in != 0 && out != 0) {
v8 = (*in << 24) + (in[1] << 16) + (in[2] << 8) + in[3];
v9 = (in[4] << 24) + (in[5] << 16) + (in[6] << 8) + in[7];
v7[0] = (userkey[3] ^ aStefanlovesmay[3])
+ ((*userkey ^ aStefanlovesmay[0]) << 24)
+ ((userkey[1] ^ aStefanlovesmay[1]) << 16)
+ ((userkey[2] ^ aStefanlovesmay[2]) << 8);
v7[1] = (userkey[7] ^ aStefanlovesmay[7])
+ ((userkey[4] ^ aStefanlovesmay[4]) << 24)
+ ((userkey[5] ^ aStefanlovesmay[5]) << 16)
+ ((userkey[6] ^ aStefanlovesmay[6]) << 8);
v7[2] = (userkey[11] ^ aStefanlovesmay[11])
+ ((userkey[8] ^ aStefanlovesmay[8]) << 24)
+ ((userkey[9] ^ aStefanlovesmay[9]) << 16)
+ ((userkey[10] ^ aStefanlovesmay[10]) << 8);
v7[3] = (userkey[15] ^ aStefanlovesmay[15])
+ ((userkey[12] ^ aStefanlovesmay[12]) << 24)
+ ((userkey[13] ^ aStefanlovesmay[13]) << 16)
+ ((userkey[14] ^ aStefanlovesmay[14]) << 8);
v3 = v8;
v4 = v9;
v5 = 0;
for (i = 0; i < 0x20; ++i) {
v3 += (((16 * v4) ^ (v4 >>> 5)) + v4) ^ (v7[v5 && 3] + v5);
v5 -= -0x9E3779B9;
v4 += (((16 * v3) ^ (v3 >>> 5)) + v3) ^ (v7[(v5 >>> 11) && 3] + v5);
}
*out = HIBYTE(v3);
out[1] = BYTE2(v3);
out[2] = BYTE1(v3);
out[3] = v3;
out[4] = HIBYTE(v4);
out[5] = BYTE2(v4);
out[6] = BYTE1(v4);
out[7] = v4;
}
}
int main() {
unsigned int key = 0xBA2F96A9;
_BYTE outKey[16];
keyExpand(key, outKey);
printf("outKey: ");
for (int i = 0; i < 16; i++) {
printf("%02x ", outKey[i]);
}
printf("\n");
// _BYTE in[] = {0x10, 0xBE, 0x62, 0xF8, 0xE8, 0xDC, 0x34, 0x46};
_BYTE in[] = {0x0E, 0x77, 0x50, 0xC8, 0xC6, 0x27, 0xE1, 0xBF};
_BYTE out[8];
printf("in: ");
for (int i = 0; i < 8; i++) {
printf("%02x ", in[i]);
}
printf("\n");
XTEA(outKey, in, out);
printf("out: ");
for (int i = 0; i < 8; i++) {
printf("%02x ", out[i]);
}
return 0;
}
The output result is shown in the figure below. The output result uses the second set of data from the above example (the first set has the same effect). You can see that the encryption result matches the expectation, thus successfully restoring the entire XTEA encryption process.
Kanxue ID: Shaobanjia
https://bbs.pediy.com/user-home-855195.htm
Summit Review: https://mp.weixin.qq.com/s/eEbc8k8H9Pc2K0d_AHG3BA
# Previous Recommendations
1. CVE-2022-21882 Privilege Escalation Vulnerability Study Notes
2. Wibu Certificate – Initial Exploration
3. Windows 10 1909 Reverse Engineering of APIC Interrupts and Experiments
4. Analysis and Simulation of EAF Mechanism Under EMET
5. SQL Injection Learning Sharing
6. Issues and POCs of V8 Array.prototype.concat Function


Share the Ball

Like the Ball

Watching the Ball