mIRC on IPv6


    by davy hollevoet
  1. First Things First

    1. Downloads

      mircv6 (project page with direct link) Latest version: works with mIRC 6.35 and has IPv6 identd. Also: SOURCECODE!

    2. Overview

      0 First Things First
      0.1 Downloads
      0.2 Overview
      0.3 Version history
      0.4 What is this text anyway?
      0.5 What this text is not
      0.6 Assumptions
      1 Getting Round The Problem
      1.1 mIRC is the problem
      1.2.A dummy program
      1.3 Longshot
      1.4 But...how...bweu...rogan?
      1.5 WriteProcessMemory and its friends
      1.6 Where is waldo? At the code-injection party
      1.7 Advantages
      1.8 Disadvantages
      2 Doing The Right Thing (TM)
      2.1 What is The Right Thing (TM)?
      2.2 Socket-number-translation
      2.3 Caching DNS-data
      2.4 Other stuff
      3 Conclusion
      4 Other stuff

    3. Version history

      • 1.0.2.1
        The faked functions now reside in a separate dll of which mIRC is not aware, so it cannot unload it. This way the faked socketfunctions stay in memory when mIRC unloads all plugins, which allows a nice shutdown of the dccserver instead of a crash.

      • 1.0.2.0
        There's a problem with the dcc(server) feature. It should be better now, but not quite perfect. mIRC still crashes when quiting while the dccserver is enabled. That's because mIRC closes the dccserver socket after unloading my dll, causing an illegal call. I could fix it by moving the fake socketcode to a separate dll, but I'm lazy. Thanks [R]u[F]y.

      • 1.0.1.1
        SSL should be fixed with this version. Now I also patch the importtable of libeay32.dll (a SSL dll) when it's loaded. Previously that dll tried to manipulate our fake sockets with real socket calls. Thanks to Tracer for telling me that SSL was broken.

      • 1.0.1.0
        Apparently I 'overlooked' the dll-loading function of mirc... We don't need a loader to force mIRC to load our dll, we can make mIRC load the dll wi
        th a script. “on 1:START:/dll mircv6.dll mircv6 0” in your remote should do it. But you need the new dll! I will not alter the article though. Thanks to Denhomer for pointing me out that my loader and nnscript was a lethal mix. Thanks to Bart Coppens for inspiring me :)

      • 1.0.0.1
        Somehow WSAStartup didn't get called, so no initializing was done, causing the allocs to fail and hang. separate initialize function was added to mircv6.dll. Thanks to Appy!

      • 1.0.0.0
        First version

    1. What is this text anyway?
      In this document I'll try to explain how I presuaded mIRC to connect to IPv6 servers. We'll take a dive into the wonderfull world PE headers, DLL's, Windows API, debug API, socket API, hexeditors and disassemblers.
      It's my attempt to explain how my mircv6loader works.

    2. What this text is not
      This text is NOT a cracking guide of any kind! It is supposed to be a document to learn from, a document to inspire other people, a document to share my experiences. I am not responsible for what readers do after they have read this document, damage or any other bad stuff.

    3. Assumptions
      I assume the reader already knows somethings about Windows API, socket calls, assembly and PE headers. Explaining everything in detail would make this document very large and not to-the-point. For details about any of this topics, I refer to the omniscient God.

  1. Getting Round The Problem

    1. mIRC is the problem
      So we want to connect to IPv6 servers using mIRC. The Problem is that mIRC was designed for IPv4 only, so as we would expect, only connects to IPv4. To fully understand The Problem, we take a look at

    2. A dummy program
      Let's take a look at this dummy program of which we normally don't have the source code:

      int main(int argc, char *argv[])
      {
              int len;
              char buffer[100];
              struct sockaddr_in info;
              int sock=socket(AF_INET,SOCK_STREAM,0);
              info.sin_addr.s_addr=inet_addr(argv[1]);
              info.sin_port=htons(21);
              info.sin_family=AF_INET;
              if(connect(sock,(struct sockaddr*)&info,sizeof(struct sockaddr))==0)
              {
                      printf("connected\n");
                      while((len=recv(sock,buffer,99,0))>0)
                      {
                              buffer[len]='\0';
                              printf("%s",buffer);
                      }
              }
              close(sock);
              return 0;
      }

    This dummy program is not only crappy coded, it's also highly unfit to use IPv6. If you'd supply a IPv6 IP at the command line, inet_addr wouldn't know what to do with it. And even if it would return something, it would be wrong a priori, because sizeof(long)<16 (128 bits, length of a IPv6 address). Same problem with struct sockaddr, since it only has place for 4 bytes of address. But for hope's sake, let's assume it magically works and the sockaddr is correct. Even then it would explode and kill all living creatures in a 2 centimetre radius, because int sock is an IPv4 socket. There is no way it's going to connect to an IPv6 server. Same for mIRC. panic()? No, not yet.

    1. Longshot
      Let's take a good look at our socket API. We work with socket arguments that are integers, plain numbers. So there will be an internal mapping of some kind, a mapping userland programs cannot see, and of which we'll take advantage. Userland programs call API functions and say they want this or that function apply to some socket. We could intercept and modify those socket calls. Hmm, that could serve our purpose...

      Our masterplan is to intercept all socket API calls and to add a new layer of integer mapping. Our program calls recv(). We intercept that call and conclude the target program wants to receive from socket number fsock. But in fact, we know that fsock is fake (we faked it), and the real socket is rsock, so we call the real recv() with first argument rsock and return the return value to the target program. Since it's transparant to the target program, it should work.

    2. But...how...bweu...rogan?
      Indeed, a good (well yeah) plan, but how can we trick mIRC into such an evil scheme? Let's dive into the asm of mIRC. Here is three lines taken from windasm:

      * Reference To: WSOCK32.socket, Ord:0017h
                                        |
      :00499717 FF1548075800            Call dword ptr [00580748]

    An obvious idea comes to our mind. What if we hexedit those 5 bytes at every offset of every call we want to intercept, that might work (and it does). But there are some issues.

      • “at every offset of every call”: twice “every” implies a lot of work... but we can automate that can't we?!? Consider this fixed.

      • “at every offset of every call”: How can we assure that we intercept ALL calls? There's always a (small) chance that we forget some... stuff like

        move edi, offsetofsocketfunction
        <other code>
        call edi

      So this method is not quite bulletproof. We should find a way to “centralize our actions”. And this is where the PE stuff kicks in. As you might or might not know, the addresses of imported dll functions are stored in an array somewhere in memory. In casu, the address of the wsock32.socket function is stored in the dword at address 0x580748. So let's modify that array and “reroute” all socket calls! But that array is built up by the loader (at pre-run-time), so we have two options (i love those <ul>bullets):

      • Trick the loader into loading a custom wsock32.dll and let it fill in custom addresses or

      • somehow “change” the array after the loader made its moves.

      The first option would again require hexediting mIRC, while the second option doesn't. Simply find the array and change it in memory.

    1. WriteProcessMemory and its friends
      Yes, WriteProcessMemory lets you change memory of an other process, as you might have guessed. I love it when a plan comes together! So we take the old car, weld some iron plates to it and drive through the gate like a maniac. But first we should finish mIRC.

    2. Where is waldo? At the code-injection party
      T
      he only unsolved problem is where to make the array-members point at. Normally the array-members point at the offset in the loaded dll (in casu wsock32.dll), if we change those pointers, where should they point at after they are changed? Depends on how we inject our code ofcourse. We could allocate some memory with VirtualAllocEx and then copy our code to it, but since the relatively large amount of code, we won't do that. Instead we'll make mIRC load a DLL with our code. To make mIRC do that, we inject even more code, and this time we will use VirtualAllocEx. We put an “int 3h” at the entrypoint, this will raise a debug event, on which we change the EIP to our VirtualAllocEx'ed memory, where resides some code to load our dll. After that we change the EIP back to the entrypoint to resume normal execution of mIRC.exe .

    3. Advantages

      • No hexediting!

      • Works on mIRC 6.14, 6.1 and hopefully on the next 10+ releases of mIRC, due to the fact that it doesn't rely on version-specific properties. (well allmost...)

    4. Disadvantages

      • Our loader must be kept running while mIRC is loaded. We can't let it terminate without it taking mIRC down with it. (If you know a solution, please inform me)

      • Dirty. IPv6 support should be native! (But since support for SSL took that long, I don't think we'll see IPv6 support soon)

  2. Doing The Right Thing (TM)

    1. What is The Right Thing (TM)?
      As we saw, mIRC is unable to do the right thing since it was not designed to do so. Thus we must bear that responsibility. In geek-language: keep track of our sockets, resolved Ips/hostnames etc.

    2. Socket-number-translation
      This is really simple: a little table where we can look up the number mIRC is using and find out what the real socketnumber is. Another advantage of this approach is that we can manipulate that internal socket without mIRC having to know about it. This way we can change our default IPv4 socket to a IPv6 socket when we want to.

    3. Caching DNS-data
      When mIRC wants to connect to a server, it first calls a function to resolve the hostname. Say that it happens to resolve to an IPv6 address. We can't return that address to mIRC, because 128 bits is longer than 32. Instead we return a magic 32-bit value, something we can recognise when we see it. So when mIRC calls connect(), with somewhere among the arguments our magic 32-bit value, we know what to do: we close our default IPv4 socket, create a IPv6 socket and connect to our cached dns-data we looked up using our magic 32-bit value. We shouldn't forget that mIRC doesn't HAVE to call connect() after resolving a hostname (think about the /dns command). So we shouldn't cache our DNS-data forever, a sufficiently large circular array will do the trick.

    4. Other stuff
      The Right Thing also consists of some minor things one would encounter while trying to let mIRC do IPv6.

  3. Conclusion
    The only thing I can concluse is that IPv6 still has a long way to go and that we should keep on pushing. Small efforts (like native IPv6 support for mIRC, enforced by hacked IPv6 support for mIRC :p) could really help IPv6.
    My second conclusion is that magic 32-bit numbers are cool. I'm still doing research on magic 64-bit numbers so it's too early to jump to conclusions about them being cool or not.

    Note: this tutorial could also have been called 'mIRC on SSL', but since mIRC 6.14 already supports SSL, that would be very redundant.

  4. Other stuff
    Writing this article was only possible thanks to:
    -Google
    -Iczelion's article about PE Import Table
    -Bert Claesen for correcting some typo's
    -Kellogg's Honey Pops
    -dudes I forgot

    If you'd have found an error in my article (any kind), please inform me, so I can fix it and pretend it never happened. My email address is davyhollevoet at hotmail dot com.
    Thanks for reading this! :)

by davy hollevoet