Skip to content

Commit f195a54

Browse files
hyperbxSajidur78
andauthored
[Sonic Frontiers] Converse: fix OOB reads, use-after-free and memory leak (#264)
Co-authored-by: Sajid <sajidur78@gmail.com>
1 parent b6e83e6 commit f195a54

1 file changed

Lines changed: 110 additions & 104 deletions

File tree

Source/Sonic Frontiers/Libraries/Converse.hmm

Lines changed: 110 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -81,130 +81,128 @@ Library "Converse" by "Hyper & RagdollClash"
8181

8282
UNMANAGED_FUNCTION(long, RedirectString, long in_pCnvrsKey, long in_pCnvrsText, int in_cnvrsTextLength)
8383
{
84-
if (in_pCnvrsKey == 0)
85-
return in_pCnvrsText;
84+
var result = string.Empty;
85+
var cnvrsKey = string.Empty;
86+
var cnvrsText = string.Empty;
8687

87-
string cnvrsKey = Marshal.PtrToStringAnsi((IntPtr)in_pCnvrsKey);
88-
string cnvrsText = in_pCnvrsText == 0 ? string.Empty : Marshal.PtrToStringUni((IntPtr)in_pCnvrsText, in_cnvrsTextLength);
89-
nint pData = 0;
88+
if (in_pCnvrsKey != 0)
89+
cnvrsKey = Marshal.PtrToStringAnsi((nint)in_pCnvrsKey);
9090

91-
void MarshalString(string in_str)
91+
if (in_pCnvrsText != 0 && in_cnvrsTextLength > 0)
92+
result = cnvrsText = Marshal.PtrToStringUni((nint)in_pCnvrsText, in_cnvrsTextLength);
93+
94+
foreach (var entry in _redirectedStrings)
9295
{
93-
int length = sizeof(int) + ((in_str.Length + 1) * sizeof(short));
94-
95-
pData = Marshal.AllocHGlobal(length);
96-
97-
var wideTextStart = pData + sizeof(int);
98-
var wideText = Encoding.Unicode.GetBytes(in_str);
99-
var wideTextEnd = wideTextStart + wideText.Length;
100-
101-
Marshal.Copy(wideText, 0, wideTextStart, wideText.Length);
96+
if (entry.Key != cnvrsKey)
97+
continue;
10298

103-
// Null terminator.
104-
Marshal.WriteInt16(wideTextEnd, 0);
105-
106-
in_pCnvrsText = (long)pData;
107-
in_cnvrsTextLength = length;
99+
result = entry.IsRawAttributes
100+
? entry.Value
101+
: WriteAttributes(entry.Value);
108102
}
109103

110-
string redirectResult = cnvrsText;
104+
foreach (var entry in _converseNotifyActions)
111105
{
112-
foreach (var entry in _redirectedStrings)
113-
{
114-
if (entry.Key == cnvrsKey)
115-
{
116-
redirectResult = entry.IsRawAttributes
117-
? entry.Value
118-
: WriteAttributes(entry.Value);
119-
}
120-
}
106+
result = entry.Key(cnvrsKey, result, ReadAttributes(result));
121107

122-
if (redirectResult != cnvrsText)
123-
MarshalString(redirectResult);
108+
if (!entry.Value)
109+
result = WriteAttributes(result);
124110
}
125111

126-
string notifyResult = redirectResult;
112+
foreach (var entry in _replacementStrings)
127113
{
128-
foreach (var entry in _converseNotifyActions)
129-
{
130-
notifyResult = entry.Key(cnvrsKey, notifyResult, ReadAttributes(notifyResult));
131-
132-
if (!entry.Value)
133-
notifyResult = WriteAttributes(notifyResult);
134-
}
114+
var text = entry.IsRawAttributes
115+
? result
116+
: ReadAttributes(result);
117+
118+
if (string.IsNullOrEmpty(text))
119+
break;
120+
121+
text = entry.IsRegex
122+
? new Regex(entry.Pattern).Replace(text, entry.Replacement)
123+
: text.Replace(entry.Pattern, entry.Replacement);
135124

136-
if (notifyResult != cnvrsText)
137-
MarshalString(notifyResult);
125+
result = entry.IsRawAttributes
126+
? text
127+
: WriteAttributes(text);
138128
}
139129

140-
foreach (var entry in _replacementStrings)
141-
{
142-
string replacementResult = entry.IsRawAttributes
143-
? notifyResult
144-
: ReadAttributes(notifyResult);
130+
// Fallback for null strings.
131+
if (string.IsNullOrEmpty(result))
132+
result = " ";
145133

146-
// Perform replacement operation.
147-
replacementResult = entry.IsRegex
148-
? new Regex(entry.Pattern).Replace(replacementResult, entry.Replacement)
149-
: replacementResult.Replace(entry.Pattern, entry.Replacement);
134+
if (result != cnvrsText)
135+
{
136+
var length = result.Length;
137+
var buffer = Marshal.AllocHGlobal(sizeof(int) + (length * sizeof(char)));
150138

151-
replacementResult = entry.IsRawAttributes
152-
? replacementResult
153-
: WriteAttributes(replacementResult);
139+
Marshal.WriteInt32(buffer, length);
140+
Marshal.Copy(result.ToCharArray(), 0, buffer + sizeof(int), length);
154141

155-
if (replacementResult != cnvrsText)
156-
MarshalString(replacementResult);
142+
return (long)buffer;
157143
}
158144

159-
if (pData != 0)
160-
{
161-
// Wide string length.
162-
Marshal.WriteInt32(pData, in_cnvrsTextLength);
163-
Marshal.FreeHGlobal(pData);
164-
}
165-
else if (in_pCnvrsText != 0)
166-
{
167-
/* No need to reallocate all strings,
168-
return -1 and reload original pointer. */
169-
in_pCnvrsText = -1;
170-
}
145+
return -1L;
146+
}
171147

172-
return in_pCnvrsText;
148+
UNMANAGED_FUNCTION(void, FreeString, long in_pCnvrsText)
149+
{
150+
Marshal.FreeHGlobal((nint)in_pCnvrsText);
173151
}
174152

175153
[LibraryInitializer]
176154
public void Init()
177155
{
178156
if (_isInitialised)
179157
return;
180-
158+
181159
void ConverseHook(long in_addr)
182160
{
183161
WriteAsmHook
184162
(
185163
$@"
186-
mov r8d, dword ptr [rsp + 0x1C8 + 0xFFFFFFFFFFFFFE60]
187-
mov rcx, rsi
164+
mov r8d, dword ptr [rsp + 0x28] ; Converse Text Length
165+
mov rdx, qword ptr [rsp + 0x20] ; Converse Text
166+
mov rcx, rsi ; Converse Key
188167
mov rax, {GET_UNMANAGED_FUNCTION_PTR(RedirectString)}
189168
call rax
190-
cmp rax, 0
191-
jne notNull
192-
mov rdx, {TO_WSTRING_PTR(" ")}
193-
mov r8d, 1
194-
jmp exit
195-
notNull:
196-
cmp rax, -1
197-
je notCustom
198-
lea rdx, qword ptr [rax + 4]
199-
mov r8d, dword ptr [rax]
200-
jmp exit
201-
notCustom:
202-
mov rdx, qword ptr [rsp + 0x1C8 + 0xFFFFFFFFFFFFFE58]
203-
mov r8d, dword ptr [rsp + 0x1C8 + 0xFFFFFFFFFFFFFE60]
204-
exit:
205-
mov rcx, qword ptr [rdi + 8]
169+
mov rbx, rax ; Preserve result in RBX.
170+
171+
; Check if result is -1 or null, use original string if so.
172+
cmp rbx, -1
173+
je original
174+
test rbx, rbx
175+
jz original
176+
177+
mov r8d, dword ptr [rbx] ; Redirected Text Length
178+
lea rdx, qword ptr [rbx + 0x04] ; Redirected Text
179+
jmp cnvrs
180+
181+
original:
182+
mov r8d, dword ptr [rsp + 0x28] ; Converse Text Length
183+
mov rdx, qword ptr [rsp + 0x20] ; Converse Text
184+
185+
cnvrs:
186+
mov rcx, qword ptr [rdi + 0x08] ; Allocator
187+
push rbx
206188
mov rax, {Memory.ReadCall(in_addr + 0x09)}
207189
call rax
190+
pop rbx
191+
192+
; Check if redirect result is -1, skip free if so.
193+
cmp rbx, -1
194+
je exit
195+
196+
; Free redirected string.
197+
mov rcx, rbx
198+
mov rbx, rax
199+
sub rsp, 32
200+
mov rax, {GET_UNMANAGED_FUNCTION_PTR(FreeString)}
201+
call rax
202+
add rsp, 32
203+
mov rax, rbx ; Restore original result.
204+
205+
exit:
208206
",
209207

210208
in_addr,
@@ -215,10 +213,10 @@ Library "Converse" by "Hyper & RagdollClash"
215213

216214
WriteNop
217215
(
218-
/* v1.41: 0x14DA978CD */
216+
/* v1.42: 0x14D89049D */
219217
ScanSignature
220218
(
221-
"\x0F\x84\xE8\x00\x00\x00\x48\x8D\x8F\xC8\x00\x00\x00",
219+
"\x0F\x84\xF7\x00\x00\x00\x48\x8D\x8F\xC8\x00\x00\x00",
222220
"xxxxxxxxxxxxx"
223221
),
224222

@@ -227,7 +225,7 @@ Library "Converse" by "Hyper & RagdollClash"
227225

228226
ConverseHook
229227
(
230-
/* 0x14DC858D3 */
228+
/* v1.42: 0x14D8904D1 */
231229
ScanSignature
232230
(
233231
"\x44\x8B\x44\x24\x28\x48\x8B\x4F\x08\xE8\xCC\xCC\xCC\xCC\x48\x89\xC2\x48\x8B\x8F\xF0\x00\x00\x00",
@@ -249,7 +247,7 @@ Library "Converse" by "Hyper & RagdollClash"
249247

250248
ConverseHook
251249
(
252-
/* 0x14DC8593A */
250+
/* v1.42: 0x14D89053C */
253251
ScanSignature
254252
(
255253
"\x44\x8B\x44\x24\x28\x48\x8B\x4F\x08\xE8\xCC\xCC\xCC\xCC\x48\x89\xC2\x48\x89\xF9",
@@ -259,7 +257,7 @@ Library "Converse" by "Hyper & RagdollClash"
259257

260258
fixed (sbyte* p_language = &_language)
261259
{
262-
/* 0x140AFA200 */
260+
/* v1.42: 0x146E3A1CC */
263261
var sig = Memory.ReadCall
264262
(
265263
ScanSignature
@@ -368,12 +366,14 @@ Library "Converse" by "Hyper & RagdollClash"
368366
/// <returns>The input string where known binary Converse attributes are replaced with XML keys.</returns>
369367
public string ReadAttributes(string in_text)
370368
{
369+
if (string.IsNullOrEmpty(in_text))
370+
return in_text;
371+
371372
var result = new StringBuilder();
372-
var chars = Encoding.Unicode.GetChars(Encoding.Unicode.GetBytes(in_text));
373373

374-
for (int i = 0; i < chars.Length; i++)
374+
for (int i = 0; i < in_text.Length; i++)
375375
{
376-
var c = chars[i];
376+
var c = in_text[i];
377377

378378
switch ((AttributeType)(c & 0xF00F))
379379
{
@@ -385,43 +385,43 @@ Library "Converse" by "Hyper & RagdollClash"
385385
result.Append("</color>");
386386
break;
387387
}
388-
388+
389389
i++;
390390

391391
var nameLength = GetNameLength(c) - 2;
392-
var argb = (uint)(chars[i] << 16 | (chars[i + 1]));
392+
var argb = (uint)(in_text[i] << 16 | (in_text[i + 1]));
393393

394394
i += 2;
395395

396-
var name = new string(chars, i, nameLength);
396+
var name = in_text.Substring(i, nameLength);
397397

398398
result.Append($"<color name=\"{name}\" value=\"{argb.ToString("X8")}\">");
399399

400400
i += nameLength;
401401

402402
break;
403403
}
404-
404+
405405
case AttributeType.Variable:
406406
{
407407
i++;
408408

409409
var nameLength = GetNameLength(c);
410-
var name = new string(chars, i, nameLength);
410+
var name = in_text.Substring(i, nameLength);
411411

412412
result.Append($"<var name=\"{name}\" />");
413413

414414
i += nameLength;
415415

416416
break;
417417
}
418-
418+
419419
case AttributeType.Image:
420420
{
421421
i++;
422422

423423
var nameLength = GetNameLength(c);
424-
var name = new string(chars, i, nameLength);
424+
var name = in_text.Substring(i, nameLength);
425425

426426
result.Append($"<image name=\"{name}\" />");
427427

@@ -447,6 +447,9 @@ Library "Converse" by "Hyper & RagdollClash"
447447
/// <returns>The input string where XML Converse attributes are replaced with known binary attributes.</returns>
448448
public string WriteAttributes(string in_text, XElement in_element = null)
449449
{
450+
if (string.IsNullOrEmpty(in_text))
451+
return in_text;
452+
450453
var result = new StringBuilder();
451454
var root = in_element ?? XElement.Parse($"<text>{in_text}</text>");
452455

@@ -531,7 +534,7 @@ Library "Converse" by "Hyper & RagdollClash"
531534
public string OmitAttributes(string in_text)
532535
{
533536
var result = new StringBuilder();
534-
var chars = Encoding.Unicode.GetChars(Encoding.Unicode.GetBytes(in_text));
537+
var chars = in_text.ToCharArray();
535538

536539
for (int i = 0; i < chars.Length; i++)
537540
{
@@ -601,6 +604,7 @@ Library "Converse" by "Hyper & RagdollClash"
601604
if (first != null)
602605
{
603606
first.Value = in_text;
607+
first.IsRawAttributes = in_isRawAttributes;
604608
return;
605609
}
606610

@@ -624,6 +628,8 @@ Library "Converse" by "Hyper & RagdollClash"
624628
if (first != null)
625629
{
626630
first.Replacement = in_replacement;
631+
first.IsRegex = in_isRegex;
632+
first.IsRawAttributes = in_isRawAttributes;
627633
return;
628634
}
629635

0 commit comments

Comments
 (0)