Results 1 to 1 of 1
  1. #1
    Dwar
    Dwar is offline
    Veteran Dwar's Avatar
    Join Date
    2010 Mar
    Posts
    2,222
    Thanks Thanks Given 
    211
    Thanks Thanks Received 
    2,230
    Thanked in
    292 Posts
    Rep Power
    10

    IDA offset finding examples

    In this text I'll try to explain some basics of offset searching and also point out to some things IDA has, but are very rarely used because people aren't aware of them.

    So for start, let's find offsets of most used cgame's structs: cg, cgs and cg_entities - this was probably explained million times already, but let's show it the IDA way. It's nothing different, but probably the easiest thing to start with.

    If you ever looked into ET sdk, you probably noticed how those structs are being memsetted to 0 on CG_Init:
    [syntax]memset( &cgs, 0, sizeof( cgs ) );
    memset( &cg, 0, sizeof( cg ) );
    memset( cg_entities, 0, sizeof(cg_entities) );[/syntax]
    However, to find those offsets first we have to locate CG_Init. So let's look at it in sdk source. One thing that should interest you are strings - if a function has a string inside, it'll be very easy to find it's offset because IDA has a nice feature which gives you a big list of all strings used and also allows you to check where it's being referenced from. So let's take a look at strings in CG_Init, preferably ones that are most likely used only in our function.
    To me this line looks very good for start:

    CG_Error( "Client/Server game mismatch: '%s/%s'", GAME_VERSION, s );

    Let's open etmain's cgame_mp_x86.dll in ida now, this is what you will have on your screen:

    1. Click on Strings tab
    2. Press ALT + T
    3. Paste: Client/Server game mismatch: '%s/%s' and press OK
    4. If string is found, it will jump to it. Now press ENTER or [/b]double click on it[/b]
    5. It will now jump to the first tab, named IDA View-A and will show you .rdata section where that string is stored. Press CTRL + X
    6. And you get a list of places it is referenced from:


    Good, only one reference. Must be CG_Init()

    As you can see, it says:

    Up o sub_3003D3E0+1D0 push* * offset aClientServerGa; "Client/Server game mismatch: '%s/%s'"

    Which means: It's referenced from function 0x3003D3E0. 0x1D0 is just the real place it's referenced from (function + 0x1D0), but inside the function which starts at 0x3003D3E0.

    However, default base address of cgame is 0x30000000 so you need to subtract it because cgame might not always load at the default address, so the offset you're looking for is: 0x3D3E0
    If you were gonna hook CG_Init in a hook, the address of function you're going to hook would be:

    DWORD CG_Init_add = (DWORD) GetModuleHandle("cgame_mp_x86.dll") + 0x3D3E0;

    Okay, so that's the CG_Init offset, now let's get what we were originally looking for, offsets of cgame structs.

    Scroll up a bit until you get to start of CG_Init, you will see a line saying:

    ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ ¦¦¦¦¦¦¦¦¦¦¦¦¦¦

    That is function start. For further reversing, let's keep a backup of our CG_Init offset. You have few lines starting with .text:3003D3E0. Now right click on 3003D3E0 and click Rename. A new window will popup, in Name field type 'CG_Init' and press OK.

    Go back to ET sdk and check what CG_Init looks like. As you can see, struct memsets appear right on start of the function.. so let's go back to IDA and look at it.
    [syntax].text:3003D3E0 sub_3003D3E0* * proc near* * * * * * * *; CODE XREF: vmMain+28p
    .text:3003D3E0
    .text:3003D3E0 arg_0* * * * * *= dword ptr* 8
    .text:3003D3E0 arg_4* * * * * *= dword ptr* 0Ch
    .text:3003D3E0 arg_8* * * * * *= dword ptr* 10h
    .text:3003D3E0
    .text:3003D3E0* * * * * * * * *push* * ebx
    .text:3003D3E1* * * * * * * * *xor* * *eax, eax
    .text:3003D3E3* * * * * * * * *push* * esi
    .text:3003D3E4* * * * * * * * *push* * edi
    .text:3003D3E5* * * * * * * * *mov* * *ecx, 682A6Ch
    .text:3003D3EA* * * * * * * * *mov* * *edi, offset dword_31B11180
    .text:3003D3EF* * * * * * * * *rep stosd
    .text:3003D3F1* * * * * * * * *mov* * *ecx, 1B8DAh
    .text:3003D3F6* * * * * * * * *mov* * *edi, offset dword_33525820
    .text:3003D3FB* * * * * * * * *rep stosd
    .text:3003D3FD* * * * * * * * *mov* * *ecx, 0A7400h
    .text:3003D402* * * * * * * * *mov* * *edi, offset dword_335945C0
    .text:3003D407* * * * * * * * *rep stosd
    .text:3003D409* * * * * * * * *mov* * *ecx, 0D380h
    .text:3003D40E* * * * * * * * *mov* * *edi, offset unk_31ABB020
    .text:3003D413* * * * * * * * *rep stosd
    .text:3003D415* * * * * * * * *mov* * *ecx, 800h
    .text:3003D41A* * * * * * * * *mov* * *edi, offset unk_3351F6E0[/syntax]
    Those are our memsets. First one, cgs memset].text:3003D3E5* * * * * * * * *mov* * *ecx, 682A6Ch
    .text:3003D3EA* * * * * * * * *mov* * *edi, offset dword_31B11180
    0x682A6C == sizeof(cgs_t)
    0x31B11180 == cgs - but when you subtract 0x30000000, cgs offset is: 0x1B11180[/syntax]

    Follow the same logic for other 2 structs:

    • sizeof(cg_t) == 0x1B8DA
    • 0x33525820 == cg - subtracted: 0x3525820
    • sizeof(cg_entities) == 0xA7400 - that is size of entire cg_entities array which is consisted of 256
    • centity_t structs, so sizeof(centity_t) is: 0xA74
    • 0x335945C0 == cg_entities - subtracted: 0x35945C0


    Let's find something harder this time, a function which doesen't have strings inside - CG_Trace. Go to SDK and check it - there is no strings in it, so we cannot use the same method we used for CG_Init. Let's try finding calls to CG_Trace instead. In SDK session in visual studio click on Edit -> Find and replace -> Find in files and search for: CG_Trace(
    Leave the '(' so it only shows real calls to it and click Find all. Check the search results window and search for a 'unique' call to it, the one which has such arguments passed to it which appear only on that place. This one is interesting:

    C:\ET_SDK\src\cgame\cg_effects.c(1193): CG_Trace( &tr, start, NULL, NULL, camloc, -1, MASK_ALL &~(CONTENTS_MONSTERCLIP|CONTENTS_AREAPORTAL|CONTEN TS_CLUSTERPORTAL));

    So what to do with it? Open your hook, and somewhere on init use your logging function. If you don't have it, create one. And do something like this:

    Log("Interesting arg: 0x%x", MASK_ALL &~(CONTENTS_MONSTERCLIP|CONTENTS_AREAPORTAL|CONTEN TS_CLUSTERPORTAL));

    Once logged, you will see something like this in your log file:

    Interesting arg: 0xffed7fff

    Now back to IDA, open 'IDA View-A' tab. Press ALT + T (shortcut for text search) and in the search box type: ffed7fff. Press OK to start search. IDA will bring you to this line:

    .text:30023C84* * * * * * * * *push* * 0FFED7FFFh

    That is where it gets sent to CG_Trace. Now just search first line which has a 'call sub_OFFSET' - that is where a function to which previous args were passed gets called, in our case CG_Trace.
    [syntax].text:30023C84* * * * * * * * *push* * 0FFED7FFFh
    .text:30023C89* * * * * * * * *push* * 0FFFFFFFFh
    .text:30023C8B* * * * * * * * *lea* * *ecx, [esp+0DC4h+var_D80]
    .text:30023C8F* * * * * * * * *push* * ecx
    .text:30023C90* * * * * * * * *push* * 0
    .text:30023C92* * * * * * * * *push* * 0
    .text:30023C94* * * * * * * * *lea* * *edx, [esp+0DD0h+var_DA8]
    .text:30023C98* * * * * * * * *fsubr* *ds:flt_3008068C
    .text:30023C9E* * * * * * * * *push* * edx
    .text:30023C9F* * * * * * * * *lea* * *eax, [esp+0DD4h+var_C70]
    .text:30023CA6* * * * * * * * *push* * eax
    .text:30023CA7* * * * * * * * *fstp* * [esp+0DD8h+var_D98]
    .text:30023CAB* * * * * * * * *call* * sub_30049080[/syntax]
    This code block starts where we found the arg being pushed to CG_Trace and ends with the first call, being CG_Trace. Subtract the mentioned offset and get address of CG_Trace: 0x49080

    That was quite easy as well, let's find a function which doesen't have a string inside and doesen't have unique args passed to it. One of that type which is used a lot is CG_DamageFeedback. Check it in SDK, it looks like this:

    void CG_DamageFeedback( int yawByte, int pitchByte, int damage )

    and is quite big, but nothing unique in it and no strings as I mentioned few lines above.

    We have nothing else to do, but to search SDK for it. So let's find where it gets called from. As you can see, it only gets called from one place:

    * C:\ET_SDK\src\cgame\cg_playerstate.c(493): CG_DamageFeedback( ps->damageYaw, ps->damagePitch, ps->damageCount );

    so let's go there and see if there is anything interested for us. It gets called by function CG_TransitionPlayerState which has one string inside - '-zoom\n'

    Search for it in IDA. Ooops, this time we have 4 references]Up o sub_30047880+68* push* * offset aZoom* *; "-zoom\n"
    Up o sub_30047D20+1B2 push* * offset aZoom* *; "-zoom\n"
    Up o sub_3005DFB0+14A push* * offset aZoom* *; "-zoom\n"
    Up o sub_3005E200+139 push* * offset aZoom* *; "-zoom\n"[/syntax]
    So let's check them one by one. Remember, CG_TransitionPlayerState takes 2 args.
    First reference, go to it and scroll up until you get to the top of function which referenced the string. All I can see is].text:30047880 sub_30047880* * proc near* * * * * * * *; CODE XREF: sub_30047D20+14Dp
    .text:30047880* * * * * * * * * * * * * * * * * * * * *; sub_30047D20+160p ...
    .text:30047880
    .text:30047880 arg_0* * * * * *= dword ptr* 4[/syntax]
    Only one arg, this isn't our function. Time to check 2nd reference. Do the same and go to the top of referencing function. This time I see].text:30047D20 sub_30047D20* * proc near* * * * * * * *; CODE XREF: sub_30049660+8DBp
    .text:30047D20* * * * * * * * * * * * * * * * * * * * *; sub_30050370+231p
    .text:30047D20
    .text:30047D20 var_4* * * * * *= dword ptr -4
    .text:30047D20 arg_0* * * * * *= dword ptr* 4
    .text:30047D20 arg_4* * * * * *= dword ptr* 8[/syntax]
    Good 2 args, but we're not 100% sure it's our function. Let's do more checks: scroll down until you get to the end of this function and see if any other string is used inside. I got down to:

    .text:30047F69 sub_30047D20* * endp

    which is the end and haven't noticed any other string in it. Pretty nice, but we're still not 100% sure that it's our func. Let's check 3rd reference. Same precedure, but as soon as I opened the place it gets referenced from I noticed some other strings used, such as "%d" or "cg_drawCrossHair". Even though this func takes 2 args like the one we need, it 100% isn't ours because of used strings which don't appear in function we're looking for. To remind you, it's CG_TransitionPlayerState we're searching. And time to check last, 4th reference. Same procedure, browse to top of function and you'll see this].text:3005E200 sub_3005E200* * proc near* * * * * * * *; DATA XREF: .data:30095E94o
    .text:3005E200
    .text:3005E200 var_C* * * * * *= dword ptr -0Ch
    .text:3005E200 var_8* * * * * *= dword ptr -8
    .text:3005E200 var_4* * * * * *= dword ptr -4
    [/syntax]
    No args used and thus isn't our function either. Until now we have noticed only one possiblity so that means it was the right one - we found it in 2nd reference to "-zoom\n" string. So let's check what 2nd reference was about. It said:

    Up o sub_30047D20+1B2 push* * offset aZoom* *; "-zoom\n"

    So CG_TransitionPlayerState offset is 0x30047D20 (0x47D20). To quickly jump to an address in IDA, press G and type in the wanted offset, 0x30047D20 in this case. Good, we're on start of CG_TransitionPlayerState now. What do we do next? One of possiblities I have in mind now is this:
    Go back to SDK and check CG_TransitionPlayerState. Count how many functions get called before CG_DamageFeedback. There is only one: CG_CheckLocalSounds

    Time to get back to IDA, we are on start of CG_TransitionPlayerState now. Now what we can do is count calls and find the real function we're looking for. So first call you'll find is:

    .text:30047D74* * * * * * * * *call* * sub_30047A60

    This is when CG_CheckLocalSounds gets called, skip that one. Scroll down until you find another call and.. bingo!

    .text:30047E41* * * * * * * * *call* * sub_30047500

    That is our CG_DamageFeedback offset: 0x30047500 or when subtracted, relative to cgame module base: 0x47500

    Those were some easier and a bit harder examples of finding function offsets. Now let's see how would you find an offset to a variable. I will use cg.time as an example.

    It gets update in CG_DrawActiveFrame so let's find that one first. It has string "cg.clientFrame:%i\n" inside. Quite easy now in IDA. Using methods I described on start of this text, find it. It is: 0x30059D60 (0x59D60).
    Now* when we have address of CG_DrawActiveFrame, go to sdk to see what it looks like.

    void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback )

    stereoFrame_t and qboolean are basically int variables, they're just named differently (typedefs).

    In IDA jump to 0x30059D60, right click on sub_30059D60 and press Set function type. What you will get is this:

    int __cdecl sub_30059D60(int,int,int);

    Change it to:

    int __cdecl sub_30059D60(int serverTime, int stereoView, int demoPlayback);

    and press OK. IDA doesent recognize stereoFrame_t and qboolean, so I changed them to int - the reason why it doesent matter is described few lines above.

    In sdk it looks like this]cg.time = serverTime;
    cgDC.realTime = cg.time;
    cg.demoPlayback = demoPlayback;[/syntax]
    While now in IDA you have disassembled code with named variables].text:30059D60* * * * * * * * *mov* * *eax, [esp+serverTime]
    .text:30059D64* * * * * * * * *mov* * *dword_3356E424, eax
    .text:30059D69* * * * * * * * *mov* * *dword_31AF0960, eax
    .text:30059D6E* * * * * * * * *mov* * *eax, [esp+demoPlayback]
    .text:30059D72* * * * * * * * *sub* * *esp, 0Ch
    .text:30059D75* * * * * * * * *mov* * *dword_33525830, ea[/syntax]
    What it does is:
    moves serverTime arg to eax and then moves eax to 0x3356E424 (cg.time) and 0x31AF0960 (cgDC.realTime). Then it moves demoPlayback arg to eax and then eax to 0x33525830 (cg.demoPlayback)

    That is all for now. I hope you enjoyed reading and that it will be helpful to new coders who get stuck at functions which i.e. don't have strings inside
    Credits: ascii
    Please, post your questions on forum, not by PM or mail

    I spend my time, so please pay a little bit of your time to keep world in equilibrium

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •