Newbie HvHer
- User ID
- 138768
- Messages
- 2
- Reactions
- 4
- Level
- 2
I was notified about an "exploit" regarding colored text. (I pardon for the first image, the community is filled with weird people)
Figure 1. Initial sighting of the "exploit".
It intrigued me. Ideated with a friend and started off trying to replicate it. First idea was to use colored character codes that I was familiar with due to dealing with colored messages on SourceMod plugins (0x01-0x07 (or 0x08 i don't remember)). After a bit of testing, I realised what I did wasn't working (but I got some interesting results)
Figure 2. "ab:", aka messing around with DispatchUserMessage and messagetypes.
Now this was all in February.
*and then...*
My friend brought it up again (today)
He had figured out a way to break chat. With colors. A variation of the same "exploit" we saw before (no images, sorry!).
So, he got colors working. How? Honestly I still don't know, but he found out an issue...
Figure 3. Apparently color characters didn't work with that variation.
Also, all of those colors were visible to people who got the broken message
Figure 4. Explanation.
Oh and then, his friend found a video on how to do the bug.
Figure 5. Friend of a friend found a video.
Figure 6. How the "exploit" works.
Notice what they said: "This will work in all chat if you are dead, it only works in team chat when alive"
...
I swear I tried that, right? Back in February? I must've tried it???
I hadn't
I completely ignored all of the signs on every colored message report I got. In ALL OF THE IMAGES the sender was either dead (ingame) or sent it to team chat.
Figure 7. I felt very very dumb.
That's it! The server must be not removing the colored characters from the message (we'll come back to this in a second). So I started off again.
after a while...
Figure 8. Crashout.
And then he said something to me
Figure 8. Server doesn't clean the color characters.
And then I started experimenting, different chats, what changes. And after a while, my screen was filled with analysis.
Figure 9. Color byte from server.
The server did send the color byte (0x07)! He was correct, I completely missed it. I was so sure before, that the server would clean out the colors.
(but what can you expect from a 20+ year old game)
Now, for a long time I though this line was somehow messing up the colors, didn't make a lot of sense to me (because it's after chat printing), but it was one of my thinking steps (this was actually to log chat history iirc)
Figure 10. RemoveColorMarkup.
So I started reversing, instruction by instruction. This was the "bf_read" object passed to "CHudBaseChat::SayText2" (name might be wrong, something `SayText2`).
(image is bad but the custom color byte was there (`0x07`))
Figure 11. bf_read passed in one of the registers on CHudChat::MsgFunc_SayText2.
Now, remember "ChatPrintF" from before? The one that had "RemoveColorMarkup" after it? This is the string buffer that was passed to it. The color byte. 0x07.
Figure 12. String buffer passed to ChatPrintF.
Figure 13. String buffer is stored in rsp+100.
Figure 14. Going to rsp+100 in Cheat Engine (color byte 0x07 is still there).
So something inside that had to clean it, but what?
Figure 15a. ChatPrintF from the source free.
"// Strip leading \n characters ( or notify/color signifiers ) for empty string check"
Hm could be but probably not. Everything else I doubted, but the last line...
Figure 15b. ChatPrintF from the source free.
Now, the last function called sparked my interest. InsertAndColorizeText. Sounds about right with what we're investigating.
Figure 16. Interesting function.
It has to be this! Finding that was also easier than expected!
Figure 17. Decompiled ChatPrintF in Cheat Engine.
Stepping into the function and walking through some instructions with cheat engine, our message buffer (rdx) will be (on an AllChat type message)
Figure 18. rdx in Cheat Engine.
We see the chat type byte 0x02 (row 1, col 1) and our color byte 0x07 (row 2, col 2).
Now if we bring up the Source SDK, we can ideate a bit.
Figure 19. InsertAndColorizeText from the SDK.
At first I was confused
This function is not removing colors.
Hm.
But what if...
What if the colors aren't added there at all? That if statement looks very weird...
So it's checking the first byte in the message buffer passed to it. We know that the first byte shows what type of chat message we're dealing with. So this if statement accepts all (aka colors all) chat messages with those types.
...simplified...
3 || 4 || 1 || 5 || 6 || 7 || 8
At first I chuckled at this, why couldn't it be an range check, another Valve code moment? But...
What's missing? 2. Number 2. Now if we step a few images back, what was the first byte on AllChat messages?
Figure 20. First byte of AllChat type message.
It's 2. Just to double check my sanity, what if it isn't 2? Try sending a message when we're dead/sending a team message.
Figure 21a. Team message.
Figure 21b. AllChat message.
Figure 21c. DeadAllChat message.
Figure 21d. Output of Team and DeadAllChat in chat.
Figure 22. What does InsertAndColorizeText do exactly?
The function only adds colors for specific color types, and ignores what AllChat uses (0x02).
Mystery solved. For some reason AllChat sends messages with TextColor::COLOR_USEOLDCOLORS. Maybe it was forgotten?
Figure 23. TextColor enum.
Okay so what was the actual cause?
So the culprit was with IVguiLocalize's Find method. This function takes in our chat mode string (TF_Chat_Team, TF_Chat_AllDead etc) and spits out the localized string. Issue is, some of these formats have a prefix infront of them (the funny color character `0x01` and `0x02`), so they allow ChatPrintF to print their specified start segment in a given color!
Figure 24. Culprit.
(These translations are in `Team Fortress 2\tf\resource\tf_[language].txt`)
Figure 25. Hidden color bytes for all message types.
As we can see, the first byte is `0x02` on AllChat messages and `0x01` everywhere else. Same what we saw with PrintChatF's "to print" buffer. Also here are all the messages (minus name and class change) that custom colors could work with.
Funnily enough, the widely known "updated localizations" update would actually fix this... who could've thought.
And thanks for reading - Franz F., out.
Figure 1. Initial sighting of the "exploit".
It intrigued me. Ideated with a friend and started off trying to replicate it. First idea was to use colored character codes that I was familiar with due to dealing with colored messages on SourceMod plugins (0x01-0x07 (or 0x08 i don't remember)). After a bit of testing, I realised what I did wasn't working (but I got some interesting results)
Figure 2. "ab:", aka messing around with DispatchUserMessage and messagetypes.
Now this was all in February.
*and then...*
My friend brought it up again (today)
He had figured out a way to break chat. With colors. A variation of the same "exploit" we saw before (no images, sorry!).
So, he got colors working. How? Honestly I still don't know, but he found out an issue...
Figure 3. Apparently color characters didn't work with that variation.
Also, all of those colors were visible to people who got the broken message
Figure 4. Explanation.
Oh and then, his friend found a video on how to do the bug.
Figure 5. Friend of a friend found a video.
Figure 6. How the "exploit" works.
Notice what they said: "This will work in all chat if you are dead, it only works in team chat when alive"
...
I swear I tried that, right? Back in February? I must've tried it???
I hadn't
I completely ignored all of the signs on every colored message report I got. In ALL OF THE IMAGES the sender was either dead (ingame) or sent it to team chat.
Figure 7. I felt very very dumb.
That's it! The server must be not removing the colored characters from the message (we'll come back to this in a second). So I started off again.
after a while...
Figure 8. Crashout.
And then he said something to me
Figure 8. Server doesn't clean the color characters.
And then I started experimenting, different chats, what changes. And after a while, my screen was filled with analysis.
Figure 9. Color byte from server.
The server did send the color byte (0x07)! He was correct, I completely missed it. I was so sure before, that the server would clean out the colors.
(but what can you expect from a 20+ year old game)
Now, for a long time I though this line was somehow messing up the colors, didn't make a lot of sense to me (because it's after chat printing), but it was one of my thinking steps (this was actually to log chat history iirc)
Figure 10. RemoveColorMarkup.
So I started reversing, instruction by instruction. This was the "bf_read" object passed to "CHudBaseChat::SayText2" (name might be wrong, something `SayText2`).
(image is bad but the custom color byte was there (`0x07`))
Figure 11. bf_read passed in one of the registers on CHudChat::MsgFunc_SayText2.
Now, remember "ChatPrintF" from before? The one that had "RemoveColorMarkup" after it? This is the string buffer that was passed to it. The color byte. 0x07.
Figure 12. String buffer passed to ChatPrintF.
Figure 13. String buffer is stored in rsp+100.
Figure 14. Going to rsp+100 in Cheat Engine (color byte 0x07 is still there).
So something inside that had to clean it, but what?
Figure 15a. ChatPrintF from the source free.
"// Strip leading \n characters ( or notify/color signifiers ) for empty string check"
Hm could be but probably not. Everything else I doubted, but the last line...
Figure 15b. ChatPrintF from the source free.
Now, the last function called sparked my interest. InsertAndColorizeText. Sounds about right with what we're investigating.
Figure 16. Interesting function.
It has to be this! Finding that was also easier than expected!
Figure 17. Decompiled ChatPrintF in Cheat Engine.
Stepping into the function and walking through some instructions with cheat engine, our message buffer (rdx) will be (on an AllChat type message)
Figure 18. rdx in Cheat Engine.
We see the chat type byte 0x02 (row 1, col 1) and our color byte 0x07 (row 2, col 2).
Now if we bring up the Source SDK, we can ideate a bit.
Figure 19. InsertAndColorizeText from the SDK.
At first I was confused
This function is not removing colors.
Hm.
But what if...
What if the colors aren't added there at all? That if statement looks very weird...
if-statement:
if (
m_text[0] == COLOR_PLAYERNAME ||
m_text[0] == COLOR_LOCATION ||
m_text[0] == COLOR_NORMAL ||
m_text[0] == COLOR_ACHIEVEMENT ||
m_text[0] == COLOR_CUSTOM ||
m_text[0] == COLOR_HEXCODE ||
m_text[0] == COLOR_HEXCODE_ALPHA
)
// ...
So it's checking the first byte in the message buffer passed to it. We know that the first byte shows what type of chat message we're dealing with. So this if statement accepts all (aka colors all) chat messages with those types.
...simplified...
3 || 4 || 1 || 5 || 6 || 7 || 8
At first I chuckled at this, why couldn't it be an range check, another Valve code moment? But...
What's missing? 2. Number 2. Now if we step a few images back, what was the first byte on AllChat messages?
Figure 20. First byte of AllChat type message.
It's 2. Just to double check my sanity, what if it isn't 2? Try sending a message when we're dead/sending a team message.
Figure 21a. Team message.
Figure 21b. AllChat message.
Figure 21c. DeadAllChat message.
Figure 21d. Output of Team and DeadAllChat in chat.
Figure 22. What does InsertAndColorizeText do exactly?
The function only adds colors for specific color types, and ignores what AllChat uses (0x02).
Mystery solved. For some reason AllChat sends messages with TextColor::COLOR_USEOLDCOLORS. Maybe it was forgotten?
Figure 23. TextColor enum.
Okay so what was the actual cause?
So the culprit was with IVguiLocalize's Find method. This function takes in our chat mode string (TF_Chat_Team, TF_Chat_AllDead etc) and spits out the localized string. Issue is, some of these formats have a prefix infront of them (the funny color character `0x01` and `0x02`), so they allow ChatPrintF to print their specified start segment in a given color!
Figure 24. Culprit.
(These translations are in `Team Fortress 2\tf\resource\tf_[language].txt`)
Figure 25. Hidden color bytes for all message types.
As we can see, the first byte is `0x02` on AllChat messages and `0x01` everywhere else. Same what we saw with PrintChatF's "to print" buffer. Also here are all the messages (minus name and class change) that custom colors could work with.
Funnily enough, the widely known "updated localizations" update would actually fix this... who could've thought.
And thanks for reading - Franz F., out.