2

I'm attempting to learn reversing techniques using IDA on an exe built with VC/MFC, and could use a little initial help learning some patterns for simplifying the decompiled code.

IDA found approximately 30K subroutines in my exe, and they make heavy use of the CString class, so that seems like a good place to start. I've read a lot of IDA tutorials and articles, but haven't found anything that covers this specifically.

Here's a relatively simple example, unmodified by me:

void __cdecl sub_428258(int a1, int a2, int a3)
{
  int v3; // esi
  int v4; // eax
  int v5; // eax
  char v6; // [esp+10h] [ebp-10h]
  int v7; // [esp+1Ch] [ebp-4h]

  v7 = 0;
  v3 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::ReverseFind(&a1, 92);// Find '\'
  if ( v3 < 0
    && (v3 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::ReverseFind(&a1, 47), v3 < 0) )// Find '/'
  {
    ATL::CSimpleStringT<wchar_t,1>::Empty(a2);
    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(a3, &a1);// Return empty string in a3
  }
  else
  {
    v4 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Left(&a1, &v6, v3);
    LOBYTE(v7) = 1;
    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(a2, v4);
    LOBYTE(v7) = 0;
    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::~CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(&v6);
    v5 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::Right(
           &a1,
           &v6,
           *(_DWORD *)(a1 - 12) - v3 - 1);
    LOBYTE(v7) = 2;
    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(a3, v5);
    LOBYTE(v7) = 0;
    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::~CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(&v6);
  }
  v7 = -1;
  ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::~CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(&a1);
}

Because IDA was able to use the MFC PDB file and RTTI, the logic is pretty easy to follow in this case. It seems to be doing something like splitting the first CString param on '\' or '/', and returning the split strings in the second and third CString params, i.e.:

void Split(CString source, CString &left, CString &right);

Questions:

  1. Is this likely a class member function, and if so, how do you know?
  2. Why is V7 being set before each call to the CString functions?
  3. What is "*(_DWORD *)(a1 - 12) - v3 - 1" doing?
  4. Is there a way to tell IDA to just use "CString" instead of the fully expanded type?
  5. Can I define a CString struct to help simplify the code?

My guess is that it is a member function, and that the answers to #2 and #3 have to do with the class definition, but I'm just not sure. An example of the simplified code would really help.

2
  • 1
    It might be helpful to know that a CString is a struct with a single pointer in it (it points to the string directly, but in front of it there is extra data). The decompiler doesn’t deal with this nicely at all. Defining the struct can be done but you won’t get much better output other than better CString in front of the casts.
    – mrexodia
    Commented Apr 13, 2018 at 10:48
  • 3
    Oh and I think V7 is used for exception handling (detection of which state the program is in). I’ll write a full answer later.
    – mrexodia
    Commented Apr 13, 2018 at 10:49

3 Answers 3

1
  1. to know that, it's better to look at assembler, not pseudocode. If there's an argument passed to sub_428258 through ECX, most likely yes, this should be a method of some class.
  2. maybe error code, which should be returned by the function. I.e. current function definition might be wrong. Can't tell without seeing the assembler.
  3. seems like it's accessing to some member of the class, pointer to which is stored in a1, so I suppose it's something in ATLString.
  4. if I understand it correct, those long names - are imported functions, so IDA just demangles and shows the names as is. You may only rename them manually.
  5. Yes, it would help you with 3.
1
  • Thanks Anton. I have learned a lot more in the 2 days since posting the question, and your answers confirm what I've learned so far. #2 is still a mystery to me, so thanks for the error code suggestion. Will check it out. Commented Apr 5, 2018 at 15:44
1

What is "*(_DWORD *)(a1 - 12) - v3 - 1" doing?

this is probably undocumented or poorly documented i don't know

these are from memory of my fiddling with this Cstring (be aware for misinterpretation from my part too )

it is trying to return you the substring after *\*

the Cstring when initialized is normally a part of a bigger structure or class or whatever its terminology is you can use windbg to follow on any generic Cstring Constructor

0:000> ?? &s
class ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t> > > * 0x0016fb2c
   +0x000 m_pszData        : 0x00584630  "C:\Program Files\Microsoft Visual Studio\2017\Community\Common7\Tools"

0:000> $$ your a1 is == 0x584630 
0:000> $$ your (a1-12) is == (0x584630 - 0n12) = 0x584624 
0:000> $$ it is accessing the length of cstring->m_pszData because it is a part of bigger structure 

0:000> dt -r cstr!ATL::CNilStringData (0x584630-10)
   +0x000 pStringMgr       : 0x56dc1578 ATL::IAtlStringMgr
      +0x000 __VFN_table : 0x567a3458 
   +0x004 nDataLength      : 0n69
   +0x008 nAllocLength     : 0n69
   +0x00c nRefs            : 0n1
   +0x010 achNil           : [2]  "C:"

0:000> $$ CNilStringData is also a part of bigger struct/class but not relevent to present discussion

0:000> ??  (wchar_t *)(( cstr!ATL::CNilStringData *) @@(0x584630-10))->achNil
wchar_t * 0x00584630
 "C:\Program Files\Microsoft Visual Studio\2017\Community\Common7\Tools"

0:000> ??  (wchar_t *)(( cstr!ATL::CNilStringData *) @@(poi(s)-10))->achNil
wchar_t * 0x00584630
 "C:\Program Files\Microsoft Visual Studio\2017\Community\Common7\Tools"
0:000> $$ so *(dword *) (a1-12) is accessing the nDataLength

you can test with some code like this

int main()
{
    CString s(_T("C:\\Program Files\\Microsoft Visual Studio\\2017\\Community\\Common7\\Tools"));
    CString t(_T("C:\\Program Files"));
    CString u(_T("C:\\Program Files\\Microsoft Visual Studio"));
    CString v(_T("C:\\Program Files\\Microsoft Visual Studio\\2017"));
    CString w(_T("C:\\Program Files\\Microsoft Visual Studio\\2017\\Community"));
    CString x(_T("C:\\Program Files\\Microsoft Visual Studio\\2017\\Community\\Common7"));
    int found;
    CString temp[6] = { s,t,u,v,w,x };
    for (int i = 0; i < 6; i++) {
        printf("%x\n", wcslen(temp[i].GetBuffer()));
        if ((found = temp[i].ReverseFind(_T('\\'))) != -1) {
            printf("%S\n", (LPCWSTR)temp[i].Mid(found));
            printf("%S\n", (LPCWSTR)temp[i].Right(found));
        }
    }

    return 0;
}

compiled and executed you would get

45
\Tools
gram Files\Microsoft Visual Studio\2017\Community\Common7\Tools
10
\Program Files
es
28
\Microsoft Visual Studio
ft Visual Studio
2d
\2017
ogram Files\Microsoft Visual Studio\2017
37
\Community
 Files\Microsoft Visual Studio\2017\Community
3f
\Common7
am Files\Microsoft Visual Studio\2017\Community\Common7
Press any key to continue . . .

put the compiled exe in windbg and give it a stir

0:000> .for(r $t0=0; @$t0<6; r $t0 = @$t0+1) { dx  wcslen(temp[@$t0].m_pszData)}
wcslen(temp[@$t0].m_pszData) : 0x45
wcslen(temp[@$t0].m_pszData) : 0x10
wcslen(temp[@$t0].m_pszData) : 0x28
wcslen(temp[@$t0].m_pszData) : 0x2d
wcslen(temp[@$t0].m_pszData) : 0x37
wcslen(temp[@$t0].m_pszData) : 0x3f
0:000> .for(r $t0=0; @$t0<6; r $t0 = @$t0+1) {dt cstr!ATL::CNilStringData nDataLength @@C++(*(int *)(&temp[@$t0]) - 0x10)}
   +0x004 nDataLength : 0x45
   +0x004 nDataLength : 0x10
   +0x004 nDataLength : 0x28
   +0x004 nDataLength : 0x2d
   +0x004 nDataLength : 0x37
   +0x004 nDataLength : 0x3f
0:000> .for(r $t0=0; @$t0<6; r $t0 = @$t0+1) {.printf "%mu\n" , @@c++((wchar_t *) temp[@$t0].m_pszData)}
C:\Program Files\Microsoft Visual Studio\2017\Community\Common7\Tools
C:\Program Files
C:\Program Files\Microsoft Visual Studio
C:\Program Files\Microsoft Visual Studio\2017
C:\Program Files\Microsoft Visual Studio\2017\Community
C:\Program Files\Microsoft Visual Studio\2017\Community\Common7
1

Yep, I know it would be "necroanswer" but still may be someone find this answer usefull.

  1. If we have suggestion that decompiler skipped an argument (especially this) you can go into function and return back (then F5), IDA Pro automatically resolve it. No need to check assembler. This commonly happens when arguments passed through registers.
  2. v7 is just compiler "metainformation" it's common for MSVC, for my reverse-engineer career I suggests: looks like MSVC set ups points where program goes through function. Just ignore variables like that.
  3. This kinda fishy because all functions of CStringT called against a1 accept a1 with &. But others arguments passed into CStringT without &. This means a2 and a3 are CStringT pointers (Please see NOTE 1 below). Concerning a1 it looks like you have parent structure with CStringT embedded into it. Expression *(_DWORD *)(a1 - 12) - v3 - 1) most likely be PARENT(a1)->Size - v3 - 1 in source code. Where PARENT is a macros returning parent of some variable and a1 some struct containing CStringT. Something like that:
struct Container {
    int Size;
    int Unk0;
    int Unk1;
    CStringT String;  // <- a1 points here and CStringT here not a pointer but a part of structure
    // other fields
}

It's also commonly used practice. You could mitigate it in reverse pseudocode with shifted pointers

Also some words about v6 - it's also a CStringT and because type haven't been defined for it, IDA Pro showed incorrect stack and I suppose other fields present there:

char v6; // [esp+10h] [ebp-10h]
int v7; // [esp+1Ch] [ebp-4h]

You could notice v6 passed with & - it means CStringT for v6 allocated in stack and not a pointer.

  1. You could rename it to just CString but you lose information and in program can be other CString templated function. I'd rather said it is quite bad practice to rename "purple" functions. But in some case IDA Pro can goes mad then, yep it required :)

  2. See NOTE 1, if you have to reverse-engineer quite a large program (may be also not large) you must define structures and names. Otherwise you will look just at mess.

NOTE 1: For nice and accurate reverse-engineering you should always set types (otherwise you always be in mess). If you not sure for now how type is look likes you could just set it as struct CStringT (struct is necessary!). For IDA Pro it like void* but you note actual type and when CStringT structure will be created IDA Pro substitute it.

NOTE 2: Using assembler for reverse-engineering at state of art of IDA Pro decompile is mostly unnecessary. Yeap, IDA Pro have errors and we need to fix them in assembler (for example "immediates" instead of "addresses" for PPC) and also layout firmware into database, etc. But mostly you should use power of decompiler; programs nowaday too large and complex to fiddling with assembler instructions. NOTE 3: Yep, you need reverse-engineering to find out another comp

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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