Long time ago we started publishing here the articles written by developers and reversers from our team. Here is the new one – our reverser gives some advices on reversing for Symbian. Any comments are appreciated.
Retrieving classes for Symbian OS isnโt totally different from reversing for x86. You just use GNU Compiler and ARM code here. A pointer to this variable is passed to the methods via R0
and a pointer to the newly created class is also returned into R0
.
Contents:
Creating a class
Letโs look at a real example.
ROM:505CFB84 sub_505CFB84 ;
CODE XREF: JarDownloader_dll_1+7Ap
ROM:505CFB84 PUSH {R4-R7,LR}
ROM:505CFB86 ADD R5, R0, #0
ROM:505CFB88 ADD R6, R1, #0
ROM:505CFB8A ADD R7, R2, #0
ROM:505CFB8C MOV R0, #0x34
ROM:505CFB8E BL newL__5CBaseUi_129 ;
CBase::newL(uint)
ROM:505CFB92 ADD R4, R0, #0
ROM:505CFB94 CMP R4, #0
ROM:505CFB96 BEQ loc_505CFBA0
ROM:505CFB98 ADD R1, R5, #0
ROM:505CFB9A BL sub_505CFBBC
ROM:505CFB9E ADD R4, R0, #0
ROM:505CFBA0
ROM:505CFBA0 loc_505CFBA0 ;
CODE XREF: sub_505CFB84+12j
ROM:505CFBA0 ADD R0, R4, #0
ROM:505CFBA2 BL
PushL__12CleanupStackP5CBase_130 ;
CleanupStack::PushL(CBase *)
ROM:505CFBA6 ADD R0, R4, #0
ROM:505CFBA8 ADD R1, R6, #0
ROM:505CFBAA ADD R2, R7, #0
ROM:505CFBAC BL sub_505CFC00
ROM:505CFBB0 BL Pop__12CleanupStack_123 ;
CleanupStack::Pop
ROM:505CFBB4 ADD R0, R4, #0
ROM:505CFBB6 POP {R4-R7}
ROM:505CFBB8 POP {R1}
ROM:505CFBBA BX R1
The 505CFB84 function itself is NewL method of some unknown class, i.e. Class::NewL()
. Thus, at first, the function allocates memory for the class structure:
ROM:505CFB8C MOV R0, #0x34
ROM:505CFB8E BL newL__5CBaseUi_129 ;
CBase::newL(uint)
Then, if everything is fine (the memory was allocated), the constructor proper is called:
ROM:505CFB94 CMP R4, #0
ROM:505CFB96 BEQ loc_505CFBA0
ROM:505CFB98 ADD R1, R5, #0
ROM:505CFB9A BL sub_505CFBBC
That means that sub_505CFBBC
is Class::Class();
Further, we see the object pushed to the stack and Class:ConstructL()
method call:
ROM:505CFBA0 ADD R0, R4, #0
ROM:505CFBA2 BL
PushL__12CleanupStackP5CBase_130 ;
CleanupStack::PushL(CBase *)
ROM:505CFBA6 ADD R0, R4, #0
ROM:505CFBA8 ADD R1, R6, #0
ROM:505CFBAA ADD R2, R7, #0
ROM:505CFBAC BL sub_505CFC00
ROM:505CFBB0 BL Pop__12CleanupStack_123 ;
CleanupStack::Pop
sub_505CFC00 = Class:ConstructL();
In total, we get the following:
ROM:505CFB84 Class__NewL ;
CODE XREF: JarDownloader_dll_1+7Ap
ROM:505CFB84 PUSH {R4-R7,LR}
ROM:505CFB86 ADD R5, R0, #0
ROM:505CFB88 ADD R6, R1, #0
ROM:505CFB8A ADD R7, R2, #0
ROM:505CFB8C MOV R0, #0x34
ROM:505CFB8E BL newL__5CBaseUi_129 ;
CBase::newL(uint)
ROM:505CFB92 ADD R4, R0, #0
ROM:505CFB94 CMP R4, #0
ROM:505CFB96 BEQ loc_505CFBA0
ROM:505CFB98 ADD R1, R5, #0
ROM:505CFB9A BL Class__Class
ROM:505CFB9E ADD R4, R0, #0
ROM:505CFBA0
ROM:505CFBA0 loc_505CFBA0 ;
CODE XREF: Class__NewL+12j
ROM:505CFBA0 ADD R0, R4, #0
ROM:505CFBA2 BL
PushL__12CleanupStackP5CBase_130 ;
CleanupStack::PushL(CBase *)
ROM:505CFBA6 ADD R0, R4, #0
ROM:505CFBA8 ADD R1, R6, #0
ROM:505CFBAA ADD R2, R7, #0
ROM:505CFBAC BL Class__ConstructL
ROM:505CFBB0 BL Pop__12CleanupStack_123 ;
CleanupStack::Pop
ROM:505CFBB4 ADD R0, R4, #0
ROM:505CFBB6 POP {R4-R7}
ROM:505CFBB8 POP {R1}
ROM:505CFBBA BX R1
ROM:505CFBBA ; End of function Class__NewL
All classes have usual constructor, like Class::Class()
. But not all of them have NewL()
and ConstructL()
.
When object is created in the stack, only the call of usual constructor is used:
ROM:5010EED0 ADD R0, SP, #0xC
ROM:5010EED2 MOV R1, #0x80
ROM:5010EED4 BL TBufBase16::TBufBase16(int)
In other words, TBufBase16::TBufBase16()
function obtains a pointer to the stack, as well as NewL
obtains a pointer to already allocated memory.
Reversing of class structure
If we have NewL(), then size of class structure is clear, since it is passed to the memory allocation function. But if we deal with a stack, then weโll have to analyze the whole stack of the function, which creates the class in its stack.
Letโs try to retrieve the structure of the class which had been examined in the beginning of this article. So, the structure size is equal to 0x34 because of:
ROM:505CFB8C MOV R0, #0x34
ROM:505CFB8E BL
newL__5CBaseUi_129 ; CBase::newL(uint)
Now we go to the constructor, i.e. Class::Class()(sub_505CFBBC)
:
ROM:505CFBBC Class__Class ;
CODE XREF: Class__NewL+16p
ROM:505CFBBC PUSH {R4,LR}
ROM:505CFBBE ADD R4, R0, #0
ROM:505CFBC0 BL sub_505CF72C
ROM:505CFBC4 LDR R0, =dword_505D08A8
ROM:505CFBC6 STR R0, [R4,#Class.field_C]
ROM:505CFBC8 LDR R0, =off_505D08B8
ROM:505CFBCA STR R0, [R4,#Class.field_10]
ROM:505CFBCC LDR R0, =dword_505D088C
ROM:505CFBCE STR R0, [R4,#Class.field_C]
ROM:505CFBD0 LDR R0, =dword_505D089C
ROM:505CFBD2 STR R0, [R4,#Class.field_10]
ROM:505CFBD4 LDR R0, =dword_505D0878
ROM:505CFBD6 STR R0, [R4,#Class]
ROM:505CFBD8 MOV R0, #0
ROM:505CFBDA STR R0, [R4,#Class.field_20]
ROM:505CFBDC STR R0, [R4,#Class.field_24]
ROM:505CFBDE STR R0, [R4,#Class.field_28]
ROM:505CFBE0 STR R0, [R4,#Class.field_2C]
ROM:505CFBE2 ADD R0, R4, #0
ROM:505CFBE4 POP {R4}
ROM:505CFBE6 POP {R1}
ROM:505CFBE8 BX R1
ROM:505CFBE8 ; End of function Class__Class
sub_505CF72C
is the base-class constructor call. Then we see assignment of values in Class.field_C
and Class.field_10
. Letโs see what is assigned:
ROM:505D08A8 dword_505D08A8 DCD 0
ROM:505D08AC DCD 0
ROM:505D08B0 DCD __pure_virtual_92+1
ROM:505D08B4 DCD __pure_virtual_92+1
ROM:505D08B8 off_505D08B8 DCD 0
ROM:505D08BC DCD 0
ROM:505D08C0 DCD __pure_virtual_92+1
In other words, these are the tables of virtual functions. And why they have the first two pointers equal to 0? It is a feature of GCC – it forms the tables of virtual functions in just such a way. The first dword is a displacement to the beginning of the class structure at multiple inheritance. It is used to find “container” class from encapsulated class. What is the purpose of the second one, I donโt know. Whenever I looked, there was a zero. Perhaps, experts in GCC will give me an answer once.
So, it turns out that pointers to the tables of virtual functions were loaded in Class.field_C
and Class.field_10
fields. Since they were loaded not in Class.field_0
, we can say that Class.field_C
and Class.field_10
are the beginnings of the classes which are being encapsulated in our Class
. And the fact that their tables of virtual functions contain pointers to pure_virtual means that these are the tables of virtual functions of base classes for Class.field_C
and Class.field_10
classes (aka interfaces). Therefore, now the real tables of virtual functions are loaded in Class.field_C
and Class.field_10
:
ROM:505D088C dword_505D088C DCD -0xC
ROM:505D0890 DCD 0
ROM:505D0894 DCD unk_505D0645
ROM:505D0898 DCD unk_505D0639
ROM:505D089C dword_505D089C DCD -0x10
ROM:505D08A0 DCD 0
ROM:505D08A4 DCD sub_505D0664+1
Now we can see that same displacements to the beginning of the container class. For Class.field_C
it is equal to 0xC
and for Class.field_10
it is -0x10
.
And the last table of virtual functions is the table of Class class:
ROM:505D0878 dword_505D0878 DCD 0
ROM:505D087C DCD 0
ROM:505D0880 DCD sub_505CFCD0+1
ROM:505D0884 DCD sub_505CFD28+1
ROM:505D0888 DCD sub_505CFFB8+1
It is written to Class.field_0
.
Letโs rewrite everything properly:
ROM:505CFBBC Class__Class ;
CODE XREF: Class__NewL+16p
ROM:505CFBBC PUSH {R4,LR}
ROM:505CFBBE ADD R4, R0, #0
ROM:505CFBC0 BL ClassBase__ClassBase
ROM:505CFBC4 LDR R0, =IncapsClass1Base__Vtbl
ROM:505CFBC6 STR R0, [R4,#Class.IncapsClass1]
ROM:505CFBC8 LDR R0, =IncapsClass2Base__Vtbl
ROM:505CFBCA STR R0, [R4,#Class.IncapsClass2]
ROM:505CFBCC LDR R0, =IncapsClass1Real_Vtbl
ROM:505CFBCE STR R0, [R4,#Class.IncapsClass1]
ROM:505CFBD0 LDR R0, =IncapsClass2Real_Vtbl
ROM:505CFBD2 STR R0, [R4,#Class.IncapsClass2]
ROM:505CFBD4 LDR R0, =Class__Vtbl
ROM:505CFBD6 STR R0, [R4,#Class.pVtbl]
ROM:505CFBD8 MOV R0, #0
ROM:505CFBDA STR R0, [R4,#Class.field_20]
ROM:505CFBDC STR R0, [R4,#Class.field_24]
ROM:505CFBDE STR R0, [R4,#Class.field_28]
ROM:505CFBE0 STR R0, [R4,#Class.field_2C]
ROM:505CFBE2 ADD R0, R4, #0
ROM:505CFBE4 POP {R4}
ROM:505CFBE6 POP {R1}
ROM:505CFBE8 BX R1
ROM:505CFBE8 ; End of function Class__Class
Then zeroing of Class.field_20
, Class.field_24
, Class.field_28
, Class.field_2C
members is performed.
Now letโs go to the base class constructor:
ROM:505CFBBE ADD R4, R0, #0
ROM:505CFBC0 BL ClassBase__ClassBase
There we see:
ROM:505CF72C ClassBase__ClassBase ;
CODE XREF: Class__Class+4p
ROM:505CF72C PUSH {R4,R5,LR}
ROM:505CF72E ADD R4, R0, #0
ROM:505CF730 ADD R5, R1, #0
ROM:505CF732 BL __5CBase_113 ;
CBase::CBase(void)
ROM:505CF736 LDR R0, =off_505D0828
ROM:505CF738 STR R0, [R4,#Class.pVtbl]
ROM:505CF73A STR R5, [R4,#Class.field_4]
ROM:505CF73C ADD R0, R4, #0
ROM:505CF73E POP {R4,R5}
ROM:505CF740 POP {R1}
ROM:505CF742 BX R1
ROM:505CF742 ; End of function ClassBase__ClassBase
Fine, it means that our class is inherited from CBase
class. Also we can see here how the table of virtual functions for Class
is set:
ROM:505D0828 off_505D0828 DCD 0
ROM:505D082C DCD 0
ROM:505D0830 DCD sub_505CF748+1
ROM:505D0834 DCD __pure_virtual_92+1
ROM:505D0838 DCD __pure_virtual_92+1
Finally, we have:
ROM:505CF72C ClassBase__ClassBase ;
CODE XREF: Class__Class+4p
ROM:505CF72C PUSH {R4,R5,LR}
ROM:505CF72E ADD R4, R0, #0
ROM:505CF730 ADD R5, R1, #0
ROM:505CF732 BL __5CBase_113 ;
CBase::CBase(void)
ROM:505CF736 LDR R0, =ClassBase__Vtbl
ROM:505CF738 STR R0, [R4,#Class.pVtbl]
ROM:505CF73A STR R5, [R4,#Class.field_4]
ROM:505CF73C ADD R0, R4, #0
ROM:505CF73E POP {R4,R5}
ROM:505CF740 POP {R1}
ROM:505CF742 BX R1
ROM:505CF742 ; End of function ClassBase__ClassBase
Now weโll run into sub_505CFC00 = Class:ConstructL()
:
ROM:505CFC00 PUSH {R4-R7,LR}
ROM:505CFC02 SUB SP, SP, #0xA4
ROM:505CFC04 ADD R7, R0, #0
ROM:505CFC06 ADD R5, R1, #0
ROM:505CFC08 ADD R6, R2, #0
...
ROM:505CFC3E ADD R0, R4, #0
ROM:505CFC40 BL CUri16::Uri(void)
ROM:505CFC44 BL
UriUtils::ConvertToInternetFormL(TUriC16 const &)
ROM:505CFC48 STR R0, [R7,#Class.field_30]
ROM:505CFC4A BL
PopAndDestroy__12CleanupStack_106
ROM:505CFC4E ADD R4, SP, #0x8C
ROM:505CFC50 LDR R0, [R7,#Class.field_30]
ROM:505CFC52 BL CUri8::Uri(void)
...
ROM:505CFCB8 STR R0, [R2]
ROM:505CFCBA STR R1, [R2,#4]
ROM:505CFCBC ADD R0, R2, #0
ROM:505CFCBE BL TDesC16::AllocL(void)
ROM:505CFCC2 STR R0, [R7,#Class.field_8]
I donโt cite the whole code because itโs rather big. Thatโs why I show only references to the structure of Class
class. We can see from the code that Class.field_30
contains a pointer to the CUri8
class and Class.field_8
is a pointer to the buffer.
Now we have the following class structure:
00000000 Class struc ; (sizeof=0x34)
00000000 pVtbl DCD ? ; offset
00000004 field_4 DCD ?
00000008 iBuffer DCD ? ; offset
0000000C IncapsClass1 DCD ? ; offset
00000010 IncapsClass2 DCD ? ; offset
00000014 DCB ? ; undefined
00000015 DCB ? ; undefined
00000016 DCB ? ; undefined
00000017 DCB ? ; undefined
00000018 DCB ? ; undefined
00000019 DCB ? ; undefined
0000001A DCB ? ; undefined
0000001B DCB ? ; undefined
0000001C DCB ? ; undefined
0000001D DCB ? ; undefined
0000001E DCB ? ; undefined
0000001F DCB ? ; undefined
00000020 field_20 DCD ?
00000024 field_24 DCD ?
00000028 field_28 DCD ?
0000002C field_2C DCD ?
00000030 iCUri8 DCD ? ; offset
00000034 Class ends
But there are still some empty fieldsโฆ Where should we look for them? Letโs go through the table of virtual functions for our class:
ROM:505D0878 Class__Vtbl DCD 0
ROM:505D087C DCD 0
ROM:505D0880 DCD Class__Method1+1
ROM:505D0884 DCD Class__Method2+1
ROM:505D0888 DCD Class__Method3+1
By the way, as for the table of virtual functionsโฆ Our class had been inherited from CBase
and, accordingly, there are firstly the virtual methods of CBase
class in the table of virtual functions of our class, and only then we see its private methods.
class CBase
{
public:
IMPORT_C virtual ~CBase();
inline TAny* operator new(TUint aSize,TAny *aBase);
IMPORT_C TAny* operator new(TUint aSize);
inline TAny* operator new(TUint aSize, TLeave);
IMPORT_C TAny* operator new(TUint aSize,TUint anExtraSize);
protected:
IMPORT_C CBase();
private:
CBase(const CBase&);
CBase& operator=(const CBase&);
IMPORT_C static TAny* newL(TUint aSize);
};
CBase has only one virtual method, itโs a destructor. Accordingly, Class__Method1
is a destructor. I.e. it can be named as Class__Destroy
.
Letโs look into it:
ROM:505CFCD0 Class__Destroy ;
DATA XREF: ROM:505D0880o
ROM:505CFCD0 PUSH {R4,R5,LR}
ROM:505CFCD2 ADD R4, R0, #0
ROM:505CFCD4 ADD R5, R1, #0
ROM:505CFCD6 LDR R0, =IncapsClass1Real_Vtbl
ROM:505CFCD8 STR R0, [R4,#Class.IncapsClass1]
ROM:505CFCDA LDR R0, =IncapsClass2Real_Vtbl
ROM:505CFCDC STR R0, [R4,#Class.IncapsClass2]
ROM:505CFCDE LDR R0, =Class__Vtbl
ROM:505CFCE0 STR R0, [R4,#Class]
ROM:505CFCE2 LDR R1, [R4,#Class.iCUri8]
ROM:505CFCE4 CMP R1, #0
ROM:505CFCE6 BEQ loc_505CFCF4
ROM:505CFCE8 LDR R0, [R1]
ROM:505CFCEA LDR R2, [R0,#8]
ROM:505CFCEC ADD R0, R1, #0
ROM:505CFCEE MOV R1, #3
ROM:505CFCF0 BL sub_505D01A0
ROM:505CFCF4
ROM:505CFCF4 loc_505CFCF4 ;
CODE XREF: Class__Destroy+16j
ROM:505CFCF4 ADD R0, R4, #0
ROM:505CFCF6 ADD R0, #Class.field_20
ROM:505CFCF8 BL RFsBase::Close(void)
ROM:505CFCFC ADD R0, R4, #0
ROM:505CFCFE ADD R0, #Class.field_28
ROM:505CFD00 BL RHTTPSession::Close(void)
ROM:505CFD04 ADD R0, R4, #0
ROM:505CFD06 ADD R0, #Class.field_2C
ROM:505CFD08 BL RHTTPTransaction::Close(void)
ROM:505CFD0C ADD R0, R4, #0
ROM:505CFD0E ADD R1, R5, #0
ROM:505CFD10 BL sub_505CF748
ROM:505CFD14 POP {R4,R5}
ROM:505CFD16 POP {R0}
ROM:505CFD18 BX R0
ROM:505CFD18 ; End of function Class__Destroy
Weeell. Itโs interesting: Class.field_20 = RFsBase
, Class.field_28 = RHTTPSession
, Class.field_2C = RHTTPTransaction
.
Now we go to Class__Method2
:
ROM:505CFD28 PUSH {R4-R6,LR}
ROM:505CFD2A SUB SP, SP, #0x50
ROM:505CFD2C ADD R4, R0, #0
ROM:505CFD2E ADD R5, R1, #0
ROM:505CFD30 ADD R6, R2, #0
...
ROM:505CFD38 STR R2, [R4,#Class.field_14]
ROM:505CFD3A LDR R1, =0x80000001
ROM:505CFD3C STR R1, [R2]
ROM:505CFD3E STR R0, [R4,#Class.field_18]
ROM:505CFD40 STR R3, [R4,#Class.field_1C]
...
ROM:505CFD66 ADD R0, R4, #0
ROM:505CFD68 BL Class__Method4
There weโve found missing fields of the structure. And also weโve found non-virtual method. Letโs take a look at it.
ROM:505CFF38 PUSH {R4,R5,LR}
ROM:505CFF3A SUB SP, SP, #0x50
ROM:505CFF3C ADD R5, R0, #0
...
ROM:505CFF70 LDR R1, [R5,#Class.field_18]
ROM:505CFF72 MOVL R4, 0x20C
ROM:505CFF76 ADD R1, R1, R4
ROM:505CFF78 ADD R0, R5, #0
ROM:505CFF7A BL Class__Method5
...
ROM:505CFF90 LDR R0, [R5,#Class.field_4]
ROM:505CFF92 LDR R1, [R5,#Class.field_18]
ROM:505CFF94 MOVL R4, 0x20C
ROM:505CFF98 ADD R1, R1, R4
ROM:505CFF9A BL
RFs::Delete(TDesC16 const &)
ROM:505CFF9E
ROM:505CFF9E loc_505CFF9E ;
CODE XREF: Class__Method4+56j
ROM:505CFF9E ADD R0, R5, #0
ROM:505CFFA0 ADD R0, #Class.field_14
ROM:505CFFA2 LDR R1, [SP,#0x4C]
ROM:505CFFA4 BL
User::RequestComplete(TRequestStatus *&,int)
ROM:505CFFA8 MOV R0, #0
ROM:505CFFAA STR R0, [R5,#Class.field_14]
Weโve found one more non-virtual method. The purpose of Class.field_4
and Class.field_14
fields has now been cleared: Class.field_4
is RFs
, and Class.field_14
is TRequestStatus
.
So, we already have the following structure:
00000000 Class struc ; (sizeof=0x34)
00000000 pVtbl DCD ? ; offset
00000004 iRfs DCD ?
00000008 iBuffer DCD ? ; offset
0000000C IncapsClass1 DCD ? ; offset
00000010 IncapsClass2 DCD ? ; offset
00000014 iRequestStatus DCD ?
00000018 field_18 DCD ?
0000001C field_1C DCD ?
00000020 iRFsBase DCD ?
00000024 field_24 DCD ?
00000028 iRHTTPSession DCD ?
0000002C iRHTTPTransaction DCD ?
00000030 iCUri8 DCD ? ; offset
00000034 Class ends
By analyzing methods in such a way you can retrieve all fields, find all methods. After that you can get parameters for the methods and make libs to use the otherโs classes.
Want to get more reversing tips – take a look at our iOS reverse engineering article.
Get more insights about the reversing process and tools for reverse engineering.