Results 1 to 13 of 13

Thread: I'm reversing a compiled TCL+Tk under Linux, can't find where it connects to the net

  1. #1
    seriousman
    Guest

    Post I'm reversing a compiled TCL+Tk under Linux, can't find where it connects to the net

    Hi,

    So, I've been trying to crack a program under Linux using IDA.
    Interestingly, the program seems to be written in TCL/TK, which switched my interests from cracking the program to understanding how it works. I tried searching in the Internet information about reversing/cracking Tcl executable but found none.
    So here is the result of what I found, that can be helpful for interested in the future (You'll find my question in the end):

    After being called, the program calls _Tcl_findexecutable to find the tcl interpreter then start a new interpreter using _Tcl_CreateInterp.
    Now using the call _Tcl_SetVar it passes the arguments (argv0..) to the TCL interpreter, calls the Tk interepreter through the call _Tk_MainLoop.

    Now through the two commands _Tcl_CreateCommand and _Tcl_CreateObjCommand the program creates the tcl functions using their "real" name and links to their address in the program, a little example:

    Code:
    .text:08054EE6                 mov     [esp+638h+var_628], 0
    .text:08054EEE                 mov     [esp+638h+var_62C], 0Fh
    .text:08054EF6                 mov     [esp+638h+var_630], offset sub_805FB00
    .text:08054EFE                 mov     [esp+638h+var_634], offset aStoreactivatio ; "storeActivationKey"
    .text:08054F06                 mov     [esp+638h+var_638], edi
    .text:08054F09                 call    _Tcl_CreateCommand
    and
    Code:
    .text:08055026                 mov     [esp+638h+var_628], 0
    .text:0805502E                 mov     [esp+638h+var_62C], 16h
    .text:08055036                 mov     [esp+638h+var_630], offset sub_804E020
    .text:0805503E                 mov     [esp+638h+var_634], offset aShowhelp ; "showHelp"
    .text:08055046                 mov     [esp+638h+var_638], edi
    .text:08055049                 call    _Tcl_CreateObjCommand
    I would say that _Tcl_CreateObjCommand creates classes where _Tcl_CreateCommand create simple command, but I'm not sure. Remark, that the commentaries are added by IDA and are the values of the used offsets.
    So you probably maybe think, cracking a executable Tcl program is simple, as you have the original name of all procedures.
    Unfortunately it is not.

    For example in my case, I was trying to crack a software protection that required a serial that will be checked, the serial then is stored in a file and a connection to the internet is made to activate it. Now I cracked the part were the program checks if the serial is valid and write to a file, but I can't find how and were the program connects to the internet, there is no calls, no function nothing.

    So, if you have cracked before a compiled Tcl/Tk program and have some ideas, tell me. I will keep you updated.

    p.s. This is my first post, so I'm not sure if it is ok to say the name of the software I'm cracking. Please tell me if it is. Anyone interested in the software please msg me and I will give you the link for the demo and the name.
    I promise that I have read the FAQ and tried to use the Search to answer my question.

  2. #2
    Sure, PM me a link.

  3. #3
    OK, I think I can tell you what's going on here. The main exe houses 2 things, it houses the code that instantiates the TCL interpreter, and it houses some "native methods". These are the things that you can see, things like checkLicenseType, and storeActivationKey. I think these methods were written in C, and just call the TCL Interpreter's methods to pass the values back and forth to the script. Now, the chunks that do things like access the web to validate the entered key are written in straight TCL, and compiled to bytecode, and stored in one of the .dat files in the Data directory. (It's also possible that they're stored encrypted, and decrypted before being passed to the parser, but that's just conjecture at this point). I believe that the reason you are unable to locate the actual URL is that it's built "on the fly" by the TCL code. So, that code doesn't exist in the .exe.

    So, to complete this target, you have a couple of options. You can either spend some time learning TCL bytecode, so that you can "manually disassemble" the functions stored in the .dat files, or you can use the hosts file to map their host to localhost, and you try to return them what they're looking for. (Granted you'd be flying blind, as that code is for all intents and purposes "missing", but it could be something simple. I did an app once that returned "YES" or "NO" from the server.) So, since you have a log of what was sent to the server, and what it got back, that might be something that you can tackle.

  4. #4
    A couple of quick notes:

    The .dat files in question on Linux are in the usr/lib/<PRODUCT NAME> directory.
    On Windows, they're in the <INSTALL LOCATION>/Data directory.

    deluxeT.dat file is the same on both windows, and Linux.
    zrad.dat and dw.dat don't appear to do anything, as I 00'd them out, and it still asks for the key.
    hw.dat is different between windows and Linux though, but replacing the windows one with the Linux one made no obvious difference. But 00'ing it out caused me to get the "Enter your key" popup box, but it was blank. hw.dat could contain the "resources", and my blanking erased the background image, the text, and the "dialog box template" (for lack of a better name).
    Last edited by FrankRizzo; May 15th, 2010 at 13:38.

  5. #5
    seriousman
    Guest
    Yeah, I have remarked the dat file, they contain surely some resources, but I don't think that they are bytecodes.
    Plus, I researched Tcl bytecode, and it seems that it executable simply using the command
    %tclsh code.tbs
    I've tried and the command didn't even recognize the headers.
    I promise that I have read the FAQ and tried to use the Search to answer my question.

  6. #6
    OK, I've found some stuff. (I'm working on both the windows & Linux versions at the same time, since they're basically the same, I figured one might give a little more insight than the other. In this case, it certainly did!)

    sub_8054B70 calls sub_805FD90. This function gets the file size, and passes it to the all important function sub_805F380. This function reads the data in, and does a simple descrambling on it, and stores it in a supplied return buffer. This buffer is then executed with TCL_Eval.

    To make things simpler to understand, I'll paste in the code from the windows version, as it's easier to understand.

    Here's the top level function:

    Code:
    signed int __cdecl sub_412570(int a1, int a2)
    {
      int retVal; // edi@2
      int v3; // ST14_4@3
      FILE *v4; // eax@3
      void *Memory; // [sp+8h] [bp-F8h]@1
      char Filename; // [sp+Ch] [bp-F4h]@1
      unsigned int v8; // [sp+FCh] [bp-4h]@1
    
      v8 = (unsigned int)&Memory ^ SecurityCookie;
      sprintf(&Filename, "%s/%s", &dataDir, a2);
      if ( PreprocessData(&Filename, (int *)&Memory) <= 0 )
        return 1;
      retVal = Tcl_Eval(a1, Memory);
      free(Memory);
      if ( retVal == 1 )
      {
        v3 = *(_DWORD *)(a1 + 8);
        v4 = _iob_func();
        fprintf(v4 + 2, "Error file '%s' line %d\n", a2, v3);
        return 1;
      }
      return 0;
    }
    Which calls PreprocessData:

    Code:
    signed int __cdecl PreprocessData(const char *Filename, int *Memory)
    {
      size_t retVal; // eax@1
      char v4; // [sp+4h] [bp-30h]@1
      size_t fileSize; // [sp+18h] [bp-1Ch]@3
    
      retVal = stat64i32(Filename, &v4);
      if ( retVal != -1 )
        retVal = fileSize;
      return readAndDecodeData(Filename, 0, retVal, Memory);
    }
    And finally, readAndDecodeData:

    Code:
    signed int __cdecl readAndDecodeData(const char *Filename, __int32 Offset, size_t DstBufSize, int *Memory)
    {
      signed int result; // eax@2
      int *tempBuf; // ebx@3
      unsigned int BufferSize; // esi@3
      int Buffer; // eax@3
      FILE *fileHandle; // edi@5
    
      if ( DstBufSize == -1 )
      {
        result = -1;
      }
      else
      {
        BufferSize = DstBufSize - 1;
        Buffer = (int)malloc(DstBufSize);
        tempBuf = Memory;
        *Memory = Buffer;
        if ( Buffer )
        {
          fileHandle = fopen(Filename, "rb");
          if ( fileHandle )
          {
            fseek(fileHandle, Offset, 0);
            if ( fread(&DstBufSize, 1u, 1u, fileHandle) == 1 )   // Variable reuse here, DstBufSize at this point becomes "cryptbyte"
            {
              if ( fread((void *)*tempBuf, 1u, BufferSize, fileHandle) == BufferSize )
              {
                fclose(fileHandle);
                decodeBuffer(*tempBuf, BufferSize, DstBufSize);  // Same thing here, DstBufSize is really "cryptbyte".
                result = BufferSize;
              }
              else
              {
                fclose(fileHandle);
                free((void *)*tempBuf);
                result = -1;
              }
            }
            else
            {
              fclose(fileHandle);
              free((void *)*tempBuf);
              result = -1;
            }
          }
          else
          {
            free((void *)*tempBuf);
            result = -1;
          }
        }
        else
        {
          result = -1;
        }
      }
      return result;
    }
    This should be enough to get you going. If not, let me know.
    Last edited by FrankRizzo; May 16th, 2010 at 15:23. Reason: Add a couple of comments for better understanding.

  7. #7
    OR, you know, if you got REALLY bored, you could write some code that looked like this:

    Code:
    #include <errno.h>
    #include <fcntl.h>
    #include <memory.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    
    
    int main(int argc, char *argv[])
    {
        unsigned char cryptByte;
        unsigned char result;
        unsigned char *buffer;
        int counter;
        int inFile, outFile;
        int fileSize;
    
        if(argc != 3)
        {
            printf("USAGE: %s InputFile OutputFile\n", argv[0]);
            exit(EXIT_FAILURE);
        }
    
        inFile = open(argv[1], O_RDONLY);
        if(inFile == -1)
        {
            printf("Unable to open %s for reading [%s]\n", argv[1], strerror(errno));
            exit(EXIT_FAILURE);
        }
    
        outFile = open(argv[2], O_WRONLY | O_CREAT);
        if(outFile == -1)
        {
            printf("Unable to open %s for writing [%s]\n", argv[2], strerror(errno));
            exit(EXIT_FAILURE);
        }
    
        fileSize = lseek(inFile, 0, SEEK_END);
        lseek(inFile, 0, SEEK_SET);
    
        // The 1st byte in the file is the crypto byte
        if(read(inFile, &cryptByte, 1) == -1)
        {
            printf("Unable to read cryptobyte from %s [%s]\n", argv[2], strerror(errno));
            close(inFile);
            close(outFile);
            unlink(argv[3]);
            exit(EXIT_FAILURE);
        }
    
        buffer = (unsigned char *)malloc(fileSize);
        if(!buffer)
        {
            printf("Unable to malloc %d bytes for buffer\n", fileSize);
            close(inFile);
            close(outFile);
            unlink(argv[3]);
            exit(EXIT_FAILURE);
        }
    
        memset(buffer, 0, fileSize);
    
        if(read(inFile, buffer, (fileSize - 1)) == -1)
        {
            printf("Unable to read buffer full from %s [%s]\n", argv[2], strerror(errno));
            close(inFile);
            close(outFile);
            unlink(argv[3]);
            free(buffer);
            exit(EXIT_FAILURE);
        }
    
        close(inFile);
    
        for(counter = 0; counter < fileSize; counter++)
        {
            result = buffer[counter];
            result -= (counter & 0xFF);
            result -= cryptByte;
            result &= 0xFF;
            buffer[counter] = result;
        }
    
        buffer[counter] = 0x00;
    
        if(write(outFile, buffer, fileSize) == -1)
        {
            printf("Unable to write buffer full to %s [%s]\n", argv[3], strerror(errno));
            close(outFile);
            unlink(argv[3]);
            free(buffer);
            exit(EXIT_FAILURE);
        }
    
        close(outFile);
        free(buffer);
        printf("All Done!\n");
    }
    And run the .dat files through it, and what comes out the other end could be useful to you.

  8. #8
    After a little deobfuscation you might even come up with something that looks like this:

    Code:
    proc ActivateLicense { Parent } 
    {
        global $TARGET_LicenseG $TARGET_EditionG macAddressG URL $TARGET_ActReasonG $TARGET_StatusG
    
        set msg "To use, your license needs to be activated.  You must be connected to the internet so the key can be sent to the server for activation.  Would you like to proceed?"
        set res [MessageBox $msg question okcancel $Parent ""]
        if { $res == "cancel" } 
        {
            destroy $Parent
            return "true"
        }
        set url "${URL}/activate.php?license=${$TARGET_LicenseG}&ma=${macAddressG}&os=[GetOS]&edition=${$TARGET_EditionG}&reason=${$TARGET_ActReasonG}"
        set mbox [DoTasksWhileWaiting "$TARGET_: license activation" "Please wait while server is contacted..." $Parent]
        set cval [catch { set res [http::geturl $url -timeout 15000 ]} err]
        wm transient $mbox ""
        destroy $mbox
    
        if { $cval || ([string first "200" [http::code $res]] == -1) } 
        {
            MessageBox "Error" error ok $Parent "Server may be down, please try later."
            return "true"
        }
    
        set ServerResponse [string trim [http::data $res]]
    
        if { [string first "Error (exceeded)" $ServerResponse] != -1 } 
        {
            MessageBox "Error" error ok $Parent "License already activated.  Please contact us at\n${URL}/support.php with your license key if this seems in error."
            return "true"
        } 
        elseif {[string first "Error (not found)" $ServerResponse] != -1} 
        {
            MessageBox "Error" error ok $Parent "License not found.  Please contact us at\n${URL}/support.php with your license key."
            return "true"
        } 
        elseif {[string first "Error" $ServerResponse] != -1 || [string length $ServerResponse] != 205} 
        {
            MessageBox "Error" error ok $Parent "Server error, please try again later."
            return "true"
        } 
        else 
        {
            if { [storeActivationKey $ServerResponse] == "error" } 
            {
                MessageBox "Error" error ok $Parent "Activation error.  Please contact us at ${URL}/support.php ."
                return "true"
            }
            set $TARGET_StatusG "active"
            MessageBox "License activated" info ok $Parent "Success.  Your license has been activated"
            return "false"
        }
    }
    Where $TARGET_ is the target name.

    Once you got done snooping, and modifying the raw TCL code, you could write a little app like the one above that does the opposite (adds the byte count, and "cryptbyte" to the data, and recreate the .dat files with your fixes in place). If it were me, I'd use $00 for the cryptbyte just to be fancy.

  9. #9
    seriousman
    Guest
    I'm really amazed, just a question how did you get back the C code from the routine sub_805F380 .
    Did you do it by hand, or did you use the tool.

    Thanks for all the help. I will try to finish the rest tomorrow or the day after and post the results here.

    p.s. By the way, I've just found out what the second number in the activate.php is: the MAC address Hopefully it's impossible to trace persons from MAC addresses.

    EDIT: Okay, a bit of research showed me it was Hex-Ray. Man, many things changed since last time I cracked back in 2003. Everything is simpler
    Last edited by seriousman; May 17th, 2010 at 12:04.
    I promise that I have read the FAQ and tried to use the Search to answer my question.

  10. #10
    The tools are much better I'll give you that, but the best tool is still above the shoulders.

    In theory, you can just patch the TCL code, whip up a quick "re-encryptor", and be done.

    If you dig through the TCL more, you can find the place where they display the dialog to ask for the serial, and the code that calls checkLicenseType, etc.

    Let me know if you still have any issues.

  11. #11
    Yeah, sorry, I can't leave it hanging. Here's the tool that does both encryption/decryption.

    Code:
    #include <errno.h>
    #include <fcntl.h>
    #include <getopt.h>
    #include <memory.h>
    #include <stdbool.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <strings.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    
    #define MAXFILELEN  512
    
    int main(int argc, char *argv[])
    {
        unsigned char cryptByte;
        unsigned char result;
        unsigned char *buffer;
        int counter;
        int inFile, outFile;
        int fileSize;
        int opt;
        char input[MAXFILELEN], output[MAXFILELEN];
        bool bDecrypt;
    
        if(argc < 3)
        {
            printf("USAGE: %s -d (for decrytpt) -e (for encrypt) -i <input file> -o <output file> -k <Key (in decimal, for output only)>\n", argv[0]);
            exit(EXIT_FAILURE);
        }
    
        while((opt = getopt(argc, argv, "dei:k:o:")) != -1)
        {
            switch(opt)
            {
                case 'i':
                    memset(input, 0x00, MAXFILELEN);
                    strncpy(input, optarg, (MAXFILELEN - 1));
                    break;
    
                case 'o':
                    memset(output, 0x00, MAXFILELEN);
                    strncpy(output, optarg, (MAXFILELEN - 1));
                    break;
    
                case 'k':
                    cryptByte = atoi(optarg);
                    break;
    
                case 'e':
                    bDecrypt = false;
                    break;
    
                case 'd':
                    bDecrypt = true;
                    break;
    
                default:
                    printf("Unknown option '%c'\n", opt);
                    printf("USAGE: %s -d (for decrytpt) -e (for encrypt) -i <input file> -o <output file> -k <Key (in decimal, for output only)>\n", argv[0]);
                    exit(EXIT_FAILURE);
            }
        }
    
        inFile = open(input, O_RDONLY);
        if(inFile == -1)
        {
            printf("Unable to open %s for reading [%s]\n", input, strerror(errno));
            exit(EXIT_FAILURE);
        }
    
        outFile = open(output, O_WRONLY | O_CREAT);
        if(outFile == -1)
        {
            printf("Unable to open %s for writing [%s]\n", output, strerror(errno));
            exit(EXIT_FAILURE);
        }
    
        fileSize = lseek(inFile, 0, SEEK_END);
        lseek(inFile, 0, SEEK_SET);
    
        if(bDecrypt == true)
        {
            // The 1st byte in the file is the crypto byte
            if(read(inFile, &cryptByte, 1) == -1)
            {
                printf("Unable to read cryptobyte from %s [%s]\n", argv[2], strerror(errno));
                close(inFile);
                close(outFile);
                unlink(output);
                exit(EXIT_FAILURE);
            }
        }
        else
        {
            // The 1st byte in the file is the crypto byte
            if(write(outFile, &cryptByte, 1) == -1)
            {
                printf("Unable to write cryptobyte to %s [%s]\n", argv[2], strerror(errno));
                close(inFile);
                close(outFile);
                unlink(output);
                exit(EXIT_FAILURE);
            }
        }
    
        buffer = (unsigned char *)malloc(fileSize);
        if(!buffer)
        {
            printf("Unable to malloc %d bytes for buffer\n", fileSize);
            close(inFile);
            close(outFile);
            unlink(output);
            exit(EXIT_FAILURE);
        }
    
        memset(buffer, 0, fileSize);
    
        if(read(inFile, buffer, (fileSize - 1)) == -1)
        {
            printf("Unable to read buffer full from %s [%s]\n", argv[2], strerror(errno));
            close(inFile);
            close(outFile);
            unlink(output);
            free(buffer);
            exit(EXIT_FAILURE);
        }
    
        close(inFile);
    
        for(counter = 0; counter < fileSize; counter++)
        {
            result = buffer[counter];
            if(bDecrypt == true)
            {
                result -= (counter & 0xFF);
                result -= cryptByte;
            }
            else
            {
                result += (counter & 0xFF);
                result += cryptByte;
            }
            result &= 0xFF;
            buffer[counter] = result;
        }
    
        buffer[counter] = 0x00;
    
        if(write(outFile, buffer, fileSize) == -1)
        {
            printf("Unable to write buffer full to %s [%s]\n", output, strerror(errno));
            close(outFile);
            unlink(output);
            free(buffer);
            exit(EXIT_FAILURE);
        }
    
        close(outFile);
        free(buffer);
        printf("All Done!\n");
    
        exit(EXIT_SUCCESS);
    }
    Not perfect, but functional.

  12. #12
    And last, but not least. You could write some TCL code that looked like this:

    Code:
    source $fontPropsFileG
    array set defaultChineseFont [array get chineseFontG]
    # crack starts here
    set TARGET_StatusG "active"
    # crack ends here
    if { $TARGET_StatusG == "none" } {if { [OOOOO00OO00OO00O "normal"] == "true" } {exit}
    I show the surrounding code so that you know where to put the "patch". Also, substitute TARGET_ with the target name.

    Now, for those of you NOT involved in this task, thanks for your patience. I promise to drop it now, as the only thing left to do would be to rewrite it in a better language.

  13. #13
    seriousman
    Guest
    You're a perfectionist :P
    Sorry, I was planning to write the tool, but found no time because of my studies.
    Anyway, thanks man for everything, your help was very useful, I've learned much.
    I promise that I have read the FAQ and tried to use the Search to answer my question.

Similar Threads

  1. How to know what ip app connects to?
    By roxaz in forum The Newbie Forum
    Replies: 7
    Last Post: January 18th, 2009, 02:35
  2. Replies: 3
    Last Post: August 12th, 2008, 14:59
  3. Reversing compiled perl files
    By seras in forum The Newbie Forum
    Replies: 4
    Last Post: January 10th, 2008, 11:00
  4. Change Startup Form of a compiled VB Exe
    By Nethacks in forum The Newbie Forum
    Replies: 2
    Last Post: July 14th, 2006, 21:27
  5. Reverse .mde (compiled Access database)
    By    in forum Advanced Reversing and Programming
    Replies: 1
    Last Post: July 5th, 2001, 05:49

Tags for this Thread

Bookmarks

Posting Permissions

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