0

While looking at that old game I've found a class CStr that is used in an unusual (to me) manner. Most of the times a member of CStr is used, it's done as follows. In both cases, this is a CStr *.

Decompilation:

pcVar6 = *(char **)(*(int *)(this + 4) + 8);
// pcVar6 is then used

Disassembly

// since the func is a __thiscall, ECX contains "this"
MOV  ESI ,ECX
MOV  EAX ,dword ptr [ESI  + 0x4]
MOV  EDI ,dword ptr [EAX  + 0x8]
// EDI is then used

This strikes me as odd. If one of the members of CStr is a char array, why isn't it just a single offset? I'm thinking that this has something to do with inheritance, but I'm lacking experience with that to be certain.

For context, this particular member seems to be a C string. The code comes from a Win32 DLL.

How do I interpret this correctly? And how do I tell Ghidra how to interpret this?

EDIT: More examples

I put a lot more here at first, but deleted everything that isn't a case where the CStr object itself is used. It's just a lot more of the line already posted and I doubt that it'll clear anything up. Also, I'm yet to make significat progress in determining what the fields of the objects are, sadly :(


Example of CStr being used in constructor. this is a CMachineController here.

// CMachineController::CMachineController
CStr::CStr((CStr *)&this->field_0x4); // could be labeled as "cstring"?

Part of a longer function. It seems to acquire an instance by reading a config object, then does some manipulation and destroys the CStr. Var types as follows: - bool bVar3; - CConfig* pcVar8; - CStr aCStack20[8]; - undefined4 local4; (a 4-byte-wide number of some kind) - stack0xff... is created by (`MOV this, ESP / MOV dword ptr [ESP+0x3c], ESP`) - CMachineController* this;
// CMachineController::Init()
...
bVar3 = CConfig::HasValue(pCVar8,s_AutoStart_10056dc4);
if (bVar3 != false) {
  CConfig::GetValue(this->config,(char *)aCStack20);
  CStr::CStr((CStr *)&stack0xffffffd0,aCStack20);
  (**(code **)(**(int **)&this->field_0x18 + 0x14))();
  CStr::~CStr(aCStack20);
}
...

Without any more info on what would be considered useful, this is the best I can think of atm.

1
  • I'n not sure why, but the formatting for the list broke, at least for me. It looks fine in the editor preview, but not here. Sep 8 at 11:13
1

You probably didn't catch that CStr is a variable in another class. This should be how it looks like from the data I can see.

class CStr {
    int placeholder[2]; // unknown 0x8 bytes
    char* somestring;
}
class some1 {
   int placeholder; // unknown 0x4 bytes
   CStr* cstr;
}

char* ptr = &(some1->cstr->somestring) would result in the pseudocode you see generated

*(char **)(*(int *)(this + 4) + 8);

EDIT : update after OP posted new pseudocode I should say first that I don't actually use Ghidra, so I am not very familiar with its pseudocode syntax and reliability(in how accurately it translated the asm code to pseudocode). I would suggest that you dont always trust what you see in the pseudocode, because it won't always be accurate, especially when it comes to the handling of registers and stack (frames).

Here's my take based on the information you gave. Not sure how helpful it is to what your finding exactly, but i'll just write my analysis based on what I see.

CStr::CStr((CStr *)&this->field_0x4); // could be labeled as "cstring"?

If Ghirda accurately converted the type, then yes you can just take field_0x4 to be CStr. Its hard to say that based on just one line of code though, so lets look further.

// CMachineController::Init()
...
bVar3 = CConfig::HasValue(pCVar8,s_AutoStart_10056dc4);
if (bVar3 != false) {
  CConfig::GetValue(this->config,(char *)aCStack20);
  CStr::CStr((CStr *)&stack0xffffffd0,aCStack20);
  (**(code **)(**(int **)&this->field_0x18 + 0x14))();
  CStr::~CStr(aCStack20);
}
...

This line is retrieving some sort of config value ? or config string, before storing it into aCStack20

CConfig::GetValue(this->config,(char *)aCStack20);

Take a look at the code below.

  CStr::CStr((CStr *)&stack0xffffffd0,aCStack20);
  (**(code **)(**(int **)&this->field_0x18 + 0x14))();
  CStr::~CStr(aCStack20);

Notice how the function call doesn't have any parameters? Although the line before seems to manipulate/create a CStr instance and destroys it right after?

 (**(code **)(**(int **)&this->field_0x18 + 0x14))();

If a data is manipulated, it is meant to be used. Otherwise, there is no need to manipulate the data. Hence, it is likely that

  CStr::CStr((CStr *)&stack0xffffffd0,aCStack20);

Is copying aCStack20 into stack0xffffffd0.

Currently we see that there are to ways to initialize a CStr class

CStr::CStr((CStr *)&this->field_0x4); // CStr(CStr* callervar) - perhaps initialize with some default values? 
CStr::Cstr((CStr *)&stack0xffffffd0,aCStack20); // CStr(CStr* copyto, CStr* copyfrom) - initialize copyto with copyfrom ?

When you said

make significat progress in determining what the fields of the objects are, sadly :(

Are you referring to the CStr class? Or &this->field_0xOFFSET? At this point we still do not actually know what the int placeholder[2] // unknown 0x8 bytes is. But my guess is probably data relating to the string, e.g size of string, type of string, what the string is used for / what does it represent and so on. It may just be easier to debug the program and see what values are at the first 8 bytes of the CStr instance.

8
  • IIRC I've also seen this in CStr::operator[], but I'll look at later today to be sure. Would this still make sense in that case? If I can provide more context for you to improve this answer even further, let me know. Aug 31 at 7:08
  • The class definition might change a little. But yes, more context would be helpful. I put placeholders because I am not able to identify what they are based on the existing data you gave.
    – Niraeth
    Aug 31 at 8:55
  • @mindoverflow you can take a look at my analysis and see if it helps. I think there still isnt enough information if you want me to say what the fields of the class are. May just be easier to debug and see yourself, or look at other parts of the code that uses the first 8 bytes of CStr.
    – Niraeth
    Sep 10 at 8:01
  • 1
    also, regarding the CStr::operator[], this is necessary because they probably want to access CStr like a char array, but yet have some additional details about the string (kinda like your std::string).
    – Niraeth
    Sep 10 at 8:04
  • 1
    That said, RE is easiest when you are able to perform both static analysis and live debugging together. One without the other is possible, but just harder. So if you're able to do both, might as well make your life easier. This question has probably served its purpose already, so feel free to open a new question and tag me there if you need help. All the best :)
    – Niraeth
    Sep 10 at 18:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.