Logo
blank Skip to main content

Retrieving classes for Symbian

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.

Creating a class

Letโ€™s look at a real example.

ShellScript
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:

ShellScript
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:

ShellScript
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:

ShellScript
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:

ShellScript
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:

ShellScript
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:

ShellScript
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):

ShellScript
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:

ShellScript
ROM:505D08A8 dword_505D08A8 DCD 0
  ROM:505D08AC         DCD    0
  ROM:505D08B0         DCD    __pure_virtual_92+1
  ROM:505D08B4         DCD    __pure_virtual_92+1
ShellScript
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:

ShellScript
ROM:505D088C dword_505D088C DCD -0xC
  ROM:505D0890         DCD    0
  ROM:505D0894         DCD    unk_505D0645
  ROM:505D0898         DCD    unk_505D0639
ShellScript
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:

ShellScript
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:

ShellScript
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:

ShellScript
ROM:505CFBBE            ADD   R4, R0, #0
  ROM:505CFBC0            BL   ClassBase__ClassBase

There we see:

ShellScript
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:

ShellScript
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:

ShellScript
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():

ShellScript
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:

ShellScript
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:

ShellScript
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.

C++
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:

ShellScript
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:

ShellScript
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.

ShellScript
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:

ShellScript
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.

Have a question?

Ask our expert!

Tell us about
your project

...And our team will:

  • Process your request within 1-2 business days.
  • Get back to you with an offer based on your project's scope and requirements.
  • Set a call to discuss your future project in detail and finalize the offer.
  • Sign a contract with you to start working on your project.

Do not have any specific task for us in mind but our skills seem interesting? Get a quick Apriorit intro to better understand our team capabilities.