pharos
pharos copied to clipboard
ooprolog fails initial reasoning with 'Contradictory information about merging classes'
Hi there, while trying to analyze an old binary from 2001 I've come across an error during Prolog analysis. This error persists across multiple fresh retries and I have no idea as to why or how:
reasonForwardAsManyTimesAsPossible complete.
Entering stage 'Initial reasoning complete'.
failed.
Consistency checks failed.
Contradictory information about merging classes: Method1=0x7f7ee4 Method2=0x7f7f58
Initial sanity check failed, indicating the OO rules are incorrect.
Please report this failure to the Pharos developers!
[29] prolog_stack:get_prolog_backtrace(100,[frame(29,clause(<clause>(0x55b39b63aec0),6),_9553224)|_9553212],[goal_term_depth(100)]) at /usr/local/lib/swipl/library/prolog_stack.pl:134
[28] throw_with_backtrace(error(system_error(initialSanityChecks))) at /usr/local/share/pharos/prolog/oorules/util.pl:185
[26] solve_internal at /usr/local/share/pharos/prolog/oorules/setup.pl:659
[25] catch(user:solve_internal,_9553448,user:((_9553516=error(resource_error(private_table_space),_9553530)->complain_table_space(ooscript);_9553580=error(resource_error(stack),_9553594)->complain_stack_size(ooscript);true),throw(_9553626))) at /usr/local/lib/swipl/boot/init.pl:537
[24] solve(ooscript) at /usr/local/share/pharos/prolog/oorules/setup.pl:604
[23] psolve_no_halt('<garbage_collected>') at /usr/local/share/pharos/prolog/oorules/report.pl:11
[22] catch(user:psolve_no_halt(stream(<stream>(0x55b39b67f620))),_9553800,user:(print_message(error,_9553866),(globalHalt->halt(1);true))) at /usr/local/lib/swipl/boot/init.pl:537
[21] catch_with_backtrace('<garbage_collected>','<garbage_collected>','<garbage_collected>') at /usr/local/lib/swipl/boot/init.pl:587
[20] run_with_backtrace('<garbage_collected>') at /usr/local/bin/ooprolog:177
[19] <meta call>
[18] with_output_to(<stream>(0x55b39b7da040),run_with_backtrace(psolve_no_halt(stream(<stream>(0x55b39b67f620))))) <foreign>
[17] setup_call_catcher_cleanup(user:(var('gothic.exe.results')->open_null_stream(<stream>(0x55b39b7da040));open('gothic.exe.results',write,<stream>(0x55b39b7da040))),user:with_output_to(<stream>(0x55b39b7da040),run_with_backtrace(psolve_no_halt(stream(<stream>(0x55b39b67f620))))),_9554218,user:close(<stream>(0x55b39b7da040))) at /usr/local/lib/swipl/boot/init.pl:619
[15] setup_call_catcher_cleanup(user:open('gothic.exe.facts',read,<stream>(0x55b39b67f620)),user:setup_call_cleanup((var('gothic.exe.results')->open_null_stream(<stream>(0x55b39b7da040));open('gothic.exe.results',write,<stream>(0x55b39b7da040))),with_output_to(<stream>(0x55b39b7da040),run_with_backtrace(psolve_no_halt(stream(<stream>(0x55b39b67f620))))),close(<stream>(0x55b39b7da040))),_9554428,user:close(<stream>(0x55b39b67f620))) at /usr/local/lib/swipl/boot/init.pl:619
[12] run([script('/usr/local/bin/ooprolog'),json(_9554706),ground(_9554726),rtti(true),guess(true),config(_9554786),stacklimit(200000000000),tablespace(200000000000),oorulespath(_9554846),halt(true),load_only(false),help(_9554906),facts('gothic.exe.facts'),results('gothic.exe.results'),loglevel(5)]) at /usr/local/bin/ooprolog:235
[9] catch(user:main(['/usr/local/bin/ooprolog','--facts','gothic.exe.facts','--results','gothic.exe.results','--log-level=5']),_9555030,user:(print_message(error,_9555160),halt(1))) at /usr/local/lib/swipl/boot/init.pl:537
[7] catch(user:main,_9555234,'$toplevel':true) at /usr/local/lib/swipl/boot/init.pl:537
[6] catch_with_backtrace('<garbage_collected>','<garbage_collected>','<garbage_collected>') at /usr/local/lib/swipl/boot/init.pl:587
Note: some frames are missing due to last-call optimization.
Re-run your program in debug mode (:- debug.) to get more detail.
ERROR: gothic.exe.facts:248659:
ERROR: Unknown message: error(system_error(initialSanityChecks))
This happened to me with the Pharos docker container (digest sha256:d466e7bb132941e79fd8bebc549a357fc640bf167fa24eadafd7163cfc3ddb16
from Feb. 7th) as well as a build from source with cmu-sei/pharos:master
at af54b6ada58d50c046fa899452addce80e9ce8da
, rose-compiler/rose
at af1323b417efbcdc162b54b667bd0cce4f23be73
, swi-prolog/swipl-devel
at 7fc957f2bad34b97efb65357f545eec684bd8a93
as well as Z3Prover/z3
at 30e7c225cd510400eacd41d0a83e013b835a8ece
.
On the first try I performed analysis using these commands:
partition --serialize=gothic.exe.part --maximum-memory=12000 --threads=8 ../gothic.exe
ooanalyzer --serialize=gothic.exe.part --maximum-memory=16000 --prolog-facts=gothic.exe.facts --threads=8 --per-function-timeout=60 --new-method=0x0055D8C0 --new-method=0x00780660 --delete-method=0x0055D8D0 --delete-method=0x0055D8F0 --delete-method=0x0078066F ../gothic.exe
ooprolog --facts gothic.exe.facts --results gothic.exe.results --log-level=6 | tee ooprolog.log
on another try I added --partitioner=rose
and --no-semantics
in an effort to make it work:
partition --serialize=gothic.exe.part --maximum-memory=12000 --threads=8 --no-semantics --partitioner=rose ../gothic.exe
ooanalyzer --serialize=gothic.exe.part --maximum-memory=16000 --prolog-facts=gothic.exe.facts --threads=8 --no-semantics --per-function-timeout=60 --new-method=0x0055D8C0 --new-method=0x00780660 --delete-method=0x0055D8D0 --delete-method=0x0055D8F0 --delete-method=0x0078066F ../gothic.exe
ooprolog --facts gothic.exe.facts --results gothic.exe.results --log-level=6 | tee ooprolog.log
Both died with the error mentioned above. I can send you the gothic.exe.facts
and ooprolog.log
files via e-mail, if required.
Thanks :>
If you can email gothic.exe.facts
to me I will take a look at what is going on. If you can include gothic.exe
, that can sometimes be helpful as well, though it is not always necessary.
I don't have time to look more right this second, but this is the earliest point where a sanity check fails:
Objects now on 0x80429c: [0x747d60, 0x80429c]
reasonForwardAsManyTimesAsPossible
Consistency checks failed.
Class 0x804320 inherits from 0x80429c at offsets 0 and 0xfc
[eschwartz@pd4 issue211]$ cat gothic.exe.results.log | fgrep -e 0x804320 -e 0x80429c | fgrep rTTI
rTTISelfRef(0x87da38, 0x811ba8, 0x811b98, 0x811b58, 0x80429c, '.?AVoCViewDocument@@').
rTTISelfRef(0x87da58, 0x811c48, 0x811c38, 0x811bf0, 0x804320, '.?AVoCViewDocumentMap@@').
This is the last conclusion:
reasonMergeVFTables_A(constructor, 0x80429c, 0x747d60, 0x80429c, 0, factVFTableWrite(0x747d68, 0x747d60, 0, 0x80429c)).
The arguments are: Rule, VFTableClass, Class, VFTable, Offset, VFTableWrite
So basically, instruction 0x747d68 in method 0x747d60 installs VFTable 0x80429c at offset 0. So it makes sense that we would merge 0x747d60 and 0x80429c.
Looking a bit farther back from the problem:
Concluding mergeVFTables(0x80429c, 0x747d60).
Merging class 0x747d60 into 0x80429c ...
Retracting factClassSizeGTE(0x747d60, 0xf0) and asserting factClassSizeGTE(0x80429c, 0xf0) ...
Retracting factClassSizeLTE(0x747d60, 0xfffffff) and asserting factClassSizeLTE(0x80429c, 0xfffffff) ...
Retracting factNOTMergeClasses(0x747d60, 0x804320) and asserting factNOTMergeClasses(0x80429c, 0x804320) ...
Retracting factNOTMergeClasses(0x747d60, 0x809fd0) and asserting factNOTMergeClasses(0x80429c, 0x809fd0) ...
Retracting factNOTMergeClasses(0x747d60, 0x80a000) and asserting factNOTMergeClasses(0x80429c, 0x80a000) ...
Retracting factObjectInObject(0x804320, 0x747d60, 0) and asserting factObjectInObject(0x804320, 0x80429c, 0) ...
Retracting factObjectInObject(0x804320, 0x747d60, 0xfc) and asserting factObjectInObject(0x804320, 0x80429c, 0xfc) ...
Objects now on 0x80429c: [0x747d60, 0x80429c]
reasonForwardAsManyTimesAsPossible
Consistency checks failed.
Class 0x804320 inherits from 0x80429c at offsets 0 and 0xfc
What is odd is that the consistency check says "inherits", but the fact says "ObjectInObject". Hmm...
Huh. https://github.com/cmu-sei/pharos/blob/master/share/prolog/oorules/insanity.pl#L66
Ok, that explains the message. And it does seem unlikely that we would inherit at one offset and embed at another.
Offset 0: reasonDerivedClass_D(0x87da58, 0x87da38, 0x804320, 0x80429c, 0).
This is because RTTI tells us so.
Offset 0xfc: reasonObjectInObject_D(0x748950, 0x747d60, 0xfc)
Hmm... this is because some constructor on 0x748950 calls a constructor 0x747d60 on offset 0xfc.
validMethodCallAtOffset(0x748983, 0x748950, 0x747d60, 0xfc)
But 0x747d60 does seem to be a constructor for AVoCViewDocument...
It would be very helpful to see the disassembly for 0x748950
Okay, here's the disassembly:
luis:Gothic1/ $ objdump -d --start-address=0x748950 --stop-address=0x748A82 -M intel gothic.exe [6:27:11]
gothic.exe: file format pei-i386
Disassembly of section .text:
00748950 <.text+0x347950>:
748950: 6a ff push 0xffffffff
748952: 68 44 dc 7e 00 push 0x7edc44
748957: 64 a1 00 00 00 00 mov eax,fs:0x0
74895d: 50 push eax
74895e: 64 89 25 00 00 00 00 mov DWORD PTR fs:0x0,esp
748965: 83 ec 0c sub esp,0xc
748968: 53 push ebx
748969: 55 push ebp
74896a: 56 push esi
74896b: 8b e9 mov ebp,ecx
74896d: 57 push edi
74896e: 89 6c 24 18 mov DWORD PTR [esp+0x18],ebp
748972: e8 e9 f3 ff ff call 0x747d60
748977: 33 f6 xor esi,esi
748979: 8d 8d fc 00 00 00 lea ecx,[ebp+0xfc]
74897f: 89 74 24 24 mov DWORD PTR [esp+0x24],esi
748983: e8 d8 f3 ff ff call 0x747d60
748988: 8a 44 24 13 mov al,BYTE PTR [esp+0x13]
74898c: 88 85 00 02 00 00 mov BYTE PTR [ebp+0x200],al
748992: 89 b5 04 02 00 00 mov DWORD PTR [ebp+0x204],esi
748998: 89 b5 08 02 00 00 mov DWORD PTR [ebp+0x208],esi
74899e: 89 b5 0c 02 00 00 mov DWORD PTR [ebp+0x20c],esi
7489a4: c7 85 fc 01 00 00 ec mov DWORD PTR [ebp+0x1fc],0x7f56ec
7489ab: 56 7f 00
7489ae: 83 c9 ff or ecx,0xffffffff
7489b1: 33 c0 xor eax,eax
7489b3: bf 48 db 87 00 mov edi,0x87db48
7489b8: c7 45 00 20 43 80 00 mov DWORD PTR [ebp+0x0],0x804320
7489bf: c7 45 24 fc 42 80 00 mov DWORD PTR [ebp+0x24],0x8042fc
7489c6: c7 85 ec 00 00 00 f4 mov DWORD PTR [ebp+0xec],0x8042f4
7489cd: 42 80 00
7489d0: 89 b5 f8 01 00 00 mov DWORD PTR [ebp+0x1f8],esi
7489d6: f2 ae repnz scas al,BYTE PTR es:[edi]
7489d8: f7 d1 not ecx
7489da: 49 dec ecx
7489db: 8b f9 mov edi,ecx
7489dd: 83 ff fd cmp edi,0xfffffffd
7489e0: c6 44 24 24 02 mov BYTE PTR [esp+0x24],0x2
7489e5: 8d 9d 00 02 00 00 lea ebx,[ebp+0x200]
7489eb: 89 7c 24 14 mov DWORD PTR [esp+0x14],edi
7489ef: 76 05 jbe 0x7489f6
7489f1: e8 9b 2f 05 00 call 0x79b991
7489f6: 8b 4b 04 mov ecx,DWORD PTR [ebx+0x4]
7489f9: 3b ce cmp ecx,esi
7489fb: 74 1e je 0x748a1b
7489fd: 8a 41 ff mov al,BYTE PTR [ecx-0x1]
748a00: 84 c0 test al,al
748a02: 74 17 je 0x748a1b
748a04: 3c ff cmp al,0xff
748a06: 74 13 je 0x748a1b
748a08: 3b fe cmp edi,esi
748a0a: 75 33 jne 0x748a3f
748a0c: fe c8 dec al
748a0e: 88 41 ff mov BYTE PTR [ecx-0x1],al
748a11: 56 push esi
748a12: 8b cb mov ecx,ebx
748a14: e8 57 aa cb ff call 0x403470
748a19: eb 52 jmp 0x748a6d
748a1b: 3b fe cmp edi,esi
748a1d: 75 0b jne 0x748a2a
748a1f: 6a 01 push 0x1
748a21: 8b cb mov ecx,ebx
748a23: e8 48 aa cb ff call 0x403470
748a28: eb 43 jmp 0x748a6d
748a2a: 8b 43 0c mov eax,DWORD PTR [ebx+0xc]
748a2d: 83 f8 1f cmp eax,0x1f
748a30: 77 04 ja 0x748a36
748a32: 3b c7 cmp eax,edi
748a34: 73 11 jae 0x748a47
748a36: 6a 01 push 0x1
748a38: 8b cb mov ecx,ebx
748a3a: e8 31 aa cb ff call 0x403470
748a3f: 57 push edi
748a40: 8b cb mov ecx,ebx
748a42: e8 89 aa cb ff call 0x4034d0
748a47: 8b 44 24 14 mov eax,DWORD PTR [esp+0x14]
748a4b: 8b cf mov ecx,edi
748a4d: 8b 7b 04 mov edi,DWORD PTR [ebx+0x4]
748a50: 8b d1 mov edx,ecx
748a52: c1 e9 02 shr ecx,0x2
748a55: be 48 db 87 00 mov esi,0x87db48
748a5a: f3 a5 rep movs DWORD PTR es:[edi],DWORD PTR ds:[esi]
748a5c: 8b ca mov ecx,edx
748a5e: 83 e1 03 and ecx,0x3
748a61: f3 a4 rep movs BYTE PTR es:[edi],BYTE PTR ds:[esi]
748a63: 8b 4b 04 mov ecx,DWORD PTR [ebx+0x4]
748a66: 89 43 08 mov DWORD PTR [ebx+0x8],eax
748a69: c6 04 08 00 mov BYTE PTR [eax+ecx*1],0x0
748a6d: 8b 4c 24 1c mov ecx,DWORD PTR [esp+0x1c]
748a71: 5f pop edi
748a72: 5e pop esi
748a73: 8b c5 mov eax,ebp
748a75: 5d pop ebp
748a76: 5b pop ebx
748a77: 64 89 0d 00 00 00 00 mov DWORD PTR fs:0x0,ecx
748a7e: 83 c4 18 add esp,0x18
748a81: c3 ret
Well, it really is calling 0x747d60 on offset 0 and offset 0xfc. I can think of two possibilities:
- Offset 0 is inherited and offset 0xfc is embedded
- Offset 0 is inherited, and offset 0xfc inherits a shim class that inherits from ViewDoc
Here is a godbolt that includes both of these. https://godbolt.org/z/c8Y7ev4Ya The base constructor is called before the vftable is installed. The embedded constructor is called after. Looking at the disassembly above, the constructor at offset 0xfc is called before the vbtable is installed, which would imply that it is inherited (indirectly).
But if that is the case, why doesn't the base at offset 0xfc show up in the RTTI?
If you need anything else, just let me know :) I'm a noob when it comes to disassembly/de-compilation and binary analysis so I don't really understand any of this in-depth tbh. So if you need anything you just gotta say so :>
I just turned off the relevant sanity checking rule to see if that would be sufficient, but no, we get another error:
Contradictory information about merging classes: Method1=0x7f7ee4 Method2=0x7f7f58
rTTISelfRef(0x85dce0, 0x80d1f0, 0x80d1e0, 0x80d1c0, 0x7f7ee4, '.?AVzCMusicTheme@@').
rTTISelfRef(0x85e010, 0x80d2a0, 0x80d290, 0x80d270, 0x7f7f58, '.?AVzCMusicJingle@@').
They are indeed not the same class. So why do we think they should be merged?
reasonMergeClasses_K(0x7f7f58, 0x7f7ee4, 0x4e56f0).
reasonMergeClasses_K(0x7f7ee4, 0x7f7f58, 0x4e5730).
reasonMergeClasses_K says that 0x4e56f0 is callable on both class 0x7f7f58 and class 0x7f7ee4, but neither have base classes, so the two classes must be the same. But judging from the name, it sure sounds like CMusicJingle inherits from CMusicTheme. According to RTTI, that is not so.
reasonClassRelatedMethod_B(function=0x4e4e30, class1=0x4e56f0, class2=0x4e5730, method1=0x4e56f0, method2=0x4e5730).
Can you provide the disassembly for 0x4e4e30 as well?
It's quite a large function. Here's a gist.
reasonMergeVFTables_A(constructor, 0x7f7f58, 0x4e5730, 0x7f7f58, 0, factVFTableWrite(0x4e5763, 0x4e5730, 0, 0x7f7f58)).
This is connecting CMusicJingle's VFTable with constructor 0x4e5730. But several other methods also install this VFTable:
Concluding factVFTableWrite(0x6176de, 0x617690, 0, 0x7f7f58).
Concluding factVFTableWrite(0x4e578c, 0x4e5780, 0, 0x7f7f58).
Concluding factVFTableWrite(0x4e5763, 0x4e5730, 0, 0x7f7f58).
Concluding factVFTableWrite(0x4e783e, 0x4e76f0, 0, 0x7f7f58).
Concluding factVFTableWrite(0x61774e, 0x617700, 0, 0x7f7f58).
I suspect that maybe 0x4e5730 is not a constructor for CMusicJingle, but is a constructor for a class that embeds CMusicJingle at offset 0. I will look more at the other methods 0x617690, 0x4e5780, 0x4e76f0, and 0x617700 later.
@lmichaelis Can you share this executable with me? This is a complicated problem. I'm not sure that I'll be able to resolve the problem without it.
I've never seen this before; the EXE you sent me actually has debug symbols embedded in it that IDA can read. This makes things a LOT easier!
I've never seen this before; the EXE you sent me actually has debug symbols embedded in it that IDA can read. This makes things a LOT easier!
I figured. Though Ghidra doesn't seem to be able to find them, weirdly enough. I looked at this file about two / two and a half years ago and back then I remember Ghidra showing hundreds of classes and functions but now it just shows three or so :shrug:
Going back to the original sanity problem where we were inheriting from two offsets, here is the class hierarchy according to IDA:
.data:0087DA58 ; public class oCViewDocumentMap /* mdisp:0 */ :
.data:0087DA58 ; public class oCViewDocument /* mdisp:0 */ :
.data:0087DA58 ; public class zCViewDialog /* mdisp:0 */ :
.data:0087DA58 ; public class zCViewPrint /* mdisp:0 */ :
.data:0087DA58 ; public class zCViewFX /* mdisp:0 */ :
.data:0087DA58 ; public class zCViewDraw /* mdisp:0 */ :
.data:0087DA58 ; public class zCViewObject /* mdisp:0 */ :
.data:0087DA58 ; public class zCObject /* mdisp:0 */,
.data:0087DA58 ; public class zCViewBase /* mdisp:36 */,
.data:0087DA58 ; public class zCInputCallback /* mdisp:236 */
It inherits from CViewDocument once. This means the other object at 0xfc must be embedded. So the assumption we made in the sanity rule is not quite right. That's not a big deal.
I suspect that maybe 0x4e5730 is not a constructor for CMusicJingle
That was wrong. 0x4e5730 is a constructor for CMusicJingle
0x4e4e30 is zCMusicSys_DirectMusic::zCMusicSys_DirectMusic.
It's still pretty hard to understand even with symbols! These are not necessarily adjacent lines in the decompilation.
zCMusicTheme::zCMusicTheme((zCMusicTheme *)the_weird_object);
the_weird_object[0] = (int)&zCMusicTheme::`vftable';
zSTRING::~zSTRING((zSTRING *)probably_part_of_weird_object);
zSTRING::~zSTRING((zSTRING *)&the_weird_object[1]);
zCMusicJingle::zCMusicJingle((zCMusicJingle *)the_weird_object);
zCMusicJingle::~zCMusicJingle((zCMusicJingle *)the_weird_object);
memset(the_weird_object, 0, 0x24u);
the_weird_object[3] = 1;
the_weird_object[6] = 1;
the_weird_object[0] = 36;
the_weird_object[1] = 35;
the_weird_object[2] = 64;
So the decompiler is confused about the size of the class. But it does clearly call zCMusicJingle and zCMusicTheme on the same stack offset. There is also no obvious call of zCMusicTheme's destructor.
Does zCMusicJingle embed zCMusicTheme (or vice versa)? Or is the compiler somehow reusing stack space for objects with different lifetimes?
I think I got it! https://godbolt.org/z/fz4qhhGTn
In this example, the object is not really needed, but the compiler still needs to allocate memory so it can run the constructors. So it reuses the same stack position for both the X and Y objects.
This is bad news for the reasonClassRelatedMethod_B rule.
Here's a better example showing the classes don't even need to be related: https://godbolt.org/z/7T4vb9Yc9
My plan here is to disable reasonClassRelatedMethod_B for objects on the stack (and maybe globals?). We would make it into a guess for these objects.
@sei-ccohen Is there a way to detect in prolog whether a thisptr points to a stack object? Or do we need to export more facts for that?
A small issue is that reasonClassRelatedMethod_B is that it is a trigger rule, so turning it into a guessing rule could harm performance.
ThisPtrAllocation facts...
https://github.com/cmu-sei/pharos/blob/af54b6ada58d50c046fa899452addce80e9ce8da/share/prolog/oorules/facts.pl#L123
On Sun, Feb 13, 2022, 11:30 AM sei-eschwartz @.***> wrote:
My plan here is to disable reasonClassRelatedMethod_B for objects on the stack (and maybe globals?). We would make it into a guess for these objects.
@sei-ccohen https://github.com/sei-ccohen Is there a way to detect in prolog whether a thisptr points to a stack object? Or do we need to export more facts for that?
A small issue is that reasonClassRelatedMethod_B is that it is a trigger rule, so turning it into a guessing rule could harm performance.
— Reply to this email directly, view it on GitHub https://github.com/cmu-sei/pharos/issues/211#issuecomment-1038234832, or unsubscribe https://github.com/notifications/unsubscribe-auth/AATGEOB2PVXNS5KQJO27IHDU27MCXANCNFSM5N5HCWPA . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.
You are receiving this because you are subscribed to this thread.Message ID: @.***>
@sei-ccohen
It's likely that only "type_Heap" is exported as a Prolog fact currently
Seems like a problem :-)
@sei-ccohen Just a reminder that you are still looking into why thisPtrAllocation facts are not present for stack objects (I think)
@lmichaelis It is not done, but I have a branch where I've been working on this. I've been able to run through the binary and get some results. Please take a look and let us know if there are any obvious problems.
Hm, loading the JSON file into Ghidra using Kaiju produces really weird results. I've picked zSTRING
as an example since I know from previous reverse-engineering work that it is a simple wrapper around a std::string
, adding some functionality.
Before loading the JSON file, there are basically no classes recognized:
After loading it, classes are shown but zSTRING
, has way too many constructors:
Additionally, there seem to be duplicated constructor definitions. According to the analysis 0x0074d8c0
is a constructor zSTRING::zSTRING()
but 0x006e7900
, 0x006e7a80
, 0x006fd960
, 0x0070d420
have the same definition though different bodies.
It also seems to find waaay too many members:
Thanks, I'll look into this. Definitely sounds like it merged way too many methods into zSTRING.
finalClass(0x7f56ec, 0x7f56ec, 0x920, 0x920, 0x599de0, [0x402cd0, 0x41daf0, 0x460a00, 0x46bc00, 0x46c090, 0x46c190, 0x46c290, 0x46c390, 0x46c4e0, 0x46ded0, 0x46e010, 0x4804b0, 0x4804f0, 0x488fe0, 0x489590, 0x4897d0, 0x49cdb0, 0x4d1740, 0x4e4b30, 0x4f7130, 0x517b30, 0x5783f0, 0x593d30, 0x593ee0, 0x599de0, 0x59b690, 0x59b820, 0x59bce0, 0x5d31f0, 0x5d32e0, 0x5d44f0, 0x6a63f0, 0x6e7900, 0x6e7a80, 0x6f6370, 0x6f6580, 0x6fd6a0, 0x6fd960, 0x6fe130, 0x70d420, 0x70d460, 0x70d4b0, 0x717e10, 0x74d770, 0x74d8c0, 0x80b1d8]).
If I was better with Hex-Rays I would just output all the names of the methods. Maybe I'll try that later.
I think that these are all legitimately zSTRING methods: 0x402cd0, 0x41daf0, 0x460a00, 0x46bc00, 0x46c090, 0x46c190, 0x46c290, 0x46c390, 0x46c4e0, 0x46ded0, 0x46e010
There are a whole bunch of constructors for zSTRING that take non-strings as args and convert to string.
There are a lot of methods that are NOT really on zSTRING that are merged though. For example, 4804B0 ; public: __thiscall oSMenuInfoAttribute::oSMenuInfoAttribute(void)
. This method installs the same zSTRING vftable at offsets 0 and 14. That alone should probably be enough to tell us zSTRING is being embedded.
Here is the relevant log: Concluding mergeVFTables(0x7f56ec, 0x4804b0).
I did not run with lots of debugging, but this is probably coming from the constructor rule of VFTableBelongsToClass. In other words, #214.
I added a new rule that is very simple. It says that if a method install the same VFTable at two offsets, then the VFTable's object is contained at both offsets. As a result, the method and VFTable can't be on the same class.
This seems pretty obvious and I'm not sure why we didn't have this as a rule. Maybe because we didn't always have VFTable's as a separate object, we couldn't express this in the past and then just never added it?
Anyway, running again to see if this helps.
It seems to help on quite a few of those methods, but not all. 0x74d8c0 is the first example I've seen that the new rule does not apply to.