First Things First
Downloads
mircv6 (project page with direct link) Latest version: works with mIRC 6.35 and has IPv6 identd. Also: SOURCECODE!
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
Version history
1.0.3.2
(April 28, 2009) 1.0.3.0
(4 August 06)
As you can see, it says "request
from 0.0.0.0", because I just memzero the sockaddr_in.
Shouldn't be a problem I hope.
I finally collected enough courage to boot up Windows and make a zip of the
sourcecode. Also, there was a bug in the patcher, leading to troubles when loading a custom openssl dll.
Someone told me mircv6 didn't work anymore with
mIRC v6.2, dunno really why, since I switched compiler from
VC to lcc, which created some extra problems... Anyway, it works
with 6.2 now! As requested I looked into the identd, with some
additional hacking, mIRC now also listens (by default, can be
disabled) on IPv6 for ident requests:
* Connecting to poweredge (6667)
-
* Identd request from 0.0.0.0
* Identd replied: 1082, 6667 : USERID : UNIX : Natox
-
----blabla---
-
Natox is Natox@2001:960:6d7:2:0:0:0:100 * natox
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 with 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
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.
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.
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.
Getting Round The Problem
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
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.
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.
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.
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.
Where is waldo? At the code-injection
party
The 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 .
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...)
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)
Doing The Right Thing (TM)
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.
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.
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.
Other stuff
The
Right Thing also consists of some minor things one would encounter
while trying to let mIRC do IPv6.
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.
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! :)