CTF Team at the University of British Columbia

[UTCTF 2022] IRC

13 Mar 2022 by - desp


Steal my irc password.

Note: the password is the flag, but it is not in utflag format (that would make it too easy). Do not guess; the correct solution finds the password with certainty. If you believe you have the correct password, but CTFd still marks it as wrong, DM me rather than using all your attempts.

I spent way too long solving this problem…


By Daniel Parks (@danielp on discord)

Initial Investigation and Setup

Memory dumps are one of the most important things to extract information out from during a forensic analysis, since it is littered with all sorts of sensitive information from whatever is running on the system at that moment. For this time, we have to figure out a way to extract an IRC password from it. There are many ways where an IRC password might show up in memory, but since IRC is a form of instant messaging, it is very likely that the system was running an IRC client at that moment, which would usually have the password residing somewhere in its memory space. With this info at hand, a snapshot of the IRC client would most likely be what we want to target - so the first thing we have to do is to do some investigation of client the system is running, if any. Time to fire up the trusty volatility framework!

Before we can do so, however, we need to make sure the file is parsable by volatility. Notice how the file name is memdump.lime.z - this tells us 2 things from a glance: it is likely that it is dumped using LiME, and then compressed using zlib deflation. A quick file memdump.lime.z tells us we are correct:

$ file memdump.lime.z
memdump.lime.z: zlib compressed data

So with the help of openssl zlib -d < memdump.lime.z > memdump.lime (chosen because openssl is in most systems by default unlike some other zlib tools), we obtained the raw memory dump in lime format, which is natively parsable by volatility. Another quick head -c 16 memdump.lime | xxd to check whether we are on the right track:

$ head -c 16 memdump.lime | xxd
00000000: 454d 694c 0100 0000 0010 0000 0000 0000  EMiL............

Yep! EMiL is the magic header for lime dumps, so we are all good now. Time to finally fire up volatility (Note: volatility 2 doesn’t work with this dump, which is unfortunate since volatility 2 has more plugins that could’ve eased operations, such as file searching and dumping, along with module dumping and physical offsets), and grab what is running:

D:\Downloads\utctf2022>vol -f memdump.lime linux.pslist
Volatility 3 Framework 2.0.0
Progress:  100.00               Stacking attempts finished
Unsatisfied requirement plugins.PsList.kernel: Linux kernel
Unable to validate the plugin requirements: ['plugins.PsList.kernel']

Hmm, what happened here? Turns out, we haven’t provided debug information to volatility about the kernel, so it doesn’t know how to read the dump. Time to get what version of kernel it is:

D:\Downloads\utctf2022>vol -f memdump.lime banners
Volatility 3 Framework 2.0.0
Progress:  100.00               PDB scanning finished
Offset  Banner

0x112e53c98     Linux version 5.10.0-11-amd64 (debian-kernel@lists.debian.org) (gcc-10 (Debian 10.2.1-6) 10.2.1 20210110, GNU ld (GNU Binutils for Debian) 2.35.2) #1 SMP Debian 5.10.92-1 (2022-01-18)

So the kernel version is 5.10.0-11-amd64, and we can obtain a copy of the kernel with debugging symbols by grabbing the linux-image-5.10.0-11-amd64-dbg_5.10.92-2_amd64.deb directly from the Debian update pool here, then unpacking using ar x linux-image-5.10.0-11-amd64-dbg_5.10.92-2_amd64.deb and tar -xvf data.tar.xz. But volatility only accepts json files as symbols - this is where the dwarf2json tool comes into play! After building the tool with go build (Note: newer versions seem to throw a lot of errors while building, so use Go 1.14.x to be safe), we can finally do dwarf2json.exe linux --elf vmlinux-5.10.0-11-amd64 and grab the resultant linux5.10.0-11.json file to the path listed by isfinfo. One more check before we try again:

D:\Downloads\utctf2022>vol -f memdump.lime isfinfo
Volatility 3 Framework 2.0.0
Progress:  100.00               PDB scanning finished
URI     Valid   Number of base_types    Number of types Number of symbols       Number of enums Windows info    Linux banner    Mac banner


file:///D:/Python/Python39/lib/site-packages/volatility3/framework/symbols/linux/linux5.10.0-11.json    Unknown 18
        8732    144172  1515    -       Linux version 5.10.0-11-amd64 (debian-kernel@lists.debian.org) (gcc-10 (Debian 10.2.1-6) 10.2.1 20210110, GNU ld (GNU Binutils for Debian) 2.35.2) #1 SMP Debian 5.10.92-1 (2022-01-18)

There we go! And finally we have everything we need to start working on the challenge.

Understanding hexchat

With a functioning pslist, we can see if there is any IRC apps running:

D:\Downloads\utctf2022>vol -f memdump.lime linux.pslist
Volatility 3 Framework 2.0.0
Progress:  100.00               Stacking attempts finished

959     779     hexchat
972     959     hexchat

Hey thats an IRC chat app! With some snooping around on the internet to find out how hexchat might store their passwords, we can see that hexchat stores plaintext passwords in their config files - which is likely also loaded into the memory for reading during the app’s lifetime. However, we have one problem: how can we identify the configuration file? An initial thought would be it might be memory mapped and shared between the 2 hexchat instances, even though usually config files are small enough that programs just load into heap instead - but checking vol -f memdump.lime linux.proc.Maps (output omitted for brevity since it is extremely long) does indeed show no path related to both versions of servlist.conf. So we have to take the alternative route and get the file using its internal format - and search it with tools such as strings and grep since we know its plaintext. However, the format is basically not documented anywhere, so we have to dig into the codes of hexchat to see how it might be formatted:

servlist_save (void)
		fprintf (fp, "N=%s\n", net->name);
		if (net->nick)
			fprintf (fp, "I=%s\n", net->nick);
		if (net->nick2)
			fprintf (fp, "i=%s\n", net->nick2);
		if (net->user)
			fprintf (fp, "U=%s\n", net->user);
		if (net->real)
			fprintf (fp, "R=%s\n", net->real);
		if (net->pass)
			fprintf (fp, "P=%s\n", net->pass);
		if (net->logintype)
			fprintf (fp, "L=%d\n", net->logintype);
		if (net->encoding)
			fprintf (fp, "E=%s\n", net->encoding);
			if (!servlist_check_encoding (net->encoding))
				buf = g_strdup_printf (_("Warning: \"%s\" character set is unknown. No conversion will be applied for network %s."),
							 net->encoding, net->name);
				fe_message (buf, FE_MSG_WARN);
				g_free (buf);

This tells us the files are essentially similar to INI files, with a single character prefix followed by a = and then the value, with P being the one that stores the password. Time to do a quick strings -a memdump.lime > strings.txt and go through the junk with your method of choice (I used vscode’s regex search for P=.*\n, but you can also use grep or similar tools) to find something that resembles a config file with consecutive lines that has config values:

E=UTF-8 (Unicode)

There it is! We can see the password is definitely related to IRC, with the password being for utctf.live - an indicator that we got to the right string. With that, we have obtained our flag: