Friday, April 4, 2014

50 more shades of green

This is a continuation of 50 shades of green. Read that first if you want to know what this is about.

So I finally reached a hundred day GitHub streak!

Since I already rambled about GitHub, commit streaks, time management, and a bunch of other things in the previous post, I'll just use this post to list what new projects I started working on in the latter 50 days:

  • Servo, Mozilla's new browser* in Rust. Rather fun project to work on, there is a lot of scope for making an impact on the project since a lot of the core features are yet unimplemented. Additionally, it has a tight-knit community.
  • SE-CitationHelper: A citation helper for Stack Exchange, based on this meta request. This was actually paid work, since I don't have the time to commit to a project like that.
  • ElectionPortal: A way to quickly hold elections and polls with LDAP authentication and filtering.
  • MathToTeX: My parser for converting typed math into LaTeX. This already existed, but was a sub-repo elsewhere. I plan to reorganize this repo and then start work on rewriting the algorithm to be more extensible. 
  • Blaze: Under the Charcoal group. This project is for monitoring new posts on a medium-activity Stack Exchange site.
  • I also forked 2048 to add the ability to save one's present state. This is on a fork and doesn't count in the activity punchcard.
In addition I continued work on most of the repos mentioned in the previous post.

I was less active than the first 50 days, I had academic commitments, extracurriculars, and more recently went to Kolkata and Kharagpur for a Mozilla event. Once the summer starts I expect to be pushing more dark greens :)

I also had to tweak the rules a bit from last time. Since I was working on Servo and rebasing code often, there were days when I did commit code but the commit was eventually moved around or left to rot in a fork. Neither of these count in the activity punchcard -- so for these days only, I've allowed pull requests / readme edits to count. I think there are two days like this.

Let's see how far I can take it from here!

* To be precise, it's a layout engine, not a browser. A layout engine (eg Gecko) handles most of the core magic that makes a browser work -- parsing and displaying HTML, and interacting with JavaScript. Browser features like tabbing and preferences and bookmarks are not a part of Servo, while it can be used (when it's stable enough) to browse the Internet, it's meant to be plugged into a browser if you want these features.

Wednesday, April 2, 2014

Introductory Firefox core development events : Setup issues

I'll be posting something about my overall experience at ProgramIIEST and MozSetup@IITKGP later this week, but I wanted to get this out there first. I may also post something about improving the good-first-bug system.

This post is partially meant as an extension to Deb's post on the same issue. Most of the contents in this post come from discussions with Deb, Sankha, Saurabh, and others: thanks, everyone!

So till now I've participated in two MozSetup-style events. One in IIT Bombay (where i was a participant), and one in IIT Kharagpur (where I was a volunteer/mentor). And one major issue that's there is setup. Basically, getting participants to come with a build system is rather nontrivial, and can be a turn-off in cases. Plus, some participants are on Windows (on the other end of the spectrum, some are on Arch), and it's harder to sort this out. Internet is not something to rely on either.

Besides that, build times are long, especially on systems like mine:
At the IITB event, I had spent quite a bit of time getting a build ready. Fortunately I was able to create a couple of patches without testing, but that's certainly not the ideal way to go. Firstly, getting started takes up a huge chunk of time, and it's a bit overwhelming to have the participants learn and understand the entire process. It's far better to get them involved in writing code and let them figure out the details of setting it up at their leisure.

At the Kharagpur event, I had planned on having some lab machines with a full Nightly build on them so that the students could test and make their patches on this system. This might have worked out, but we didn't have time (or lab access) the day before to initialize this. In the end, we had one machine with a full build on it, and another machine that was built later during the event. I had planned to rsync the built objdirs across systems, but somehow that didn't work even though I'd kept everything in a username-agnostic location (/opt). This is something I'll look into later.

But it turns out there's an easier way to do things than to run full builds on the spot. @Debloper had the interesting idea of using OpenStack for this, and after some discussions it was basically to have an OpenStack instance where we create a VM with a full build environment, and allow participants to fork this VM and do all their coding/testing there (via ssh -X). This requires some investment in maintaining an OpenStack instance, but overall it's a viable way to go. We can also allow participants to keep access to the instance for some time period to make transition to development on their own systems much easier.

As an alternative to this, I had the idea of using flash drives instead of VMs. One way to do this is to install a persistent Ubuntu system1 on a 16 GB flash drive, install the prerequisites, and build. This pen drive can then be booted into and used regardless of the user's system. It's persistent, too, so it can be used in the long term as well. It has the drawback of being a bit slower, though. Also, this drive can be quickly cloned via dd in preparation for an event. If a user wishes to install it baremetal, they can do so manually with dd and update-grub.

The other option is to make an Ubuntu live flash drive, but to customize it via squashfs and chroot and add the required packages along with a full build. Here, there won't be persistent storage, so anyone trying it out by booting into the flash drive will lose their work on reboot. However, this is easier to install baremetal since the standard installation process will work, and a baremetal install is faster, too. Again, the ISOs can be cloned.

If we want this to be scalable, we can eventually ask Mozilla to build these ISOs once every X days (or once every clobber) and put them up for download, much like Nightly builds. As far as I can tell, this won't create much extra strain on their resources. Then event organizers all over the world just have to burn the ISOs to some flash drives the night before, which is something very feasible.

The cherry on top of this method (Deb's awesome idea) is that these flash drives can double as swag. A mozilla-branded drive is something pretty cool, especially if it contains everything you need for contributing to Firefox from wherever you are. The details of this depend on budget and all, but ... it's an option :)

There will still be architecture issues and speed issues, but these can be solved with some work. Using an older Ubuntu version like Backtrack does is one way to make things faster, and we can always have a couple of AMD flash drives ready.

I hope we get to try this method out at a similar event (maybe the upcoming Kolkata one). There are a lot of avenues to explore here, and a lot of room for improvement, but overall it seems like a promising way to fix the setup issues at such events.

1. Or Fedora, but I haven't yet worked out the details for Fedora. I'll be trying this out when I have time.

Saturday, February 15, 2014

50 shades of green

Update: The streak has reached 100, read more about the additional projects I was working on here

Alright, four shades. Making 50 squares.

Yep, that's right, my GitHub commit streak reached 50 days!

What's GitHub? What's a GitHub commit streak?


For the uninitiated, GitHub is an online service that lets you efficiently manage repositories of code using the git protocol. Besides allowing for easy version control and collaboration on code (which are just features of the git protocol), it provides a bunch of useful collaboration tools like the issue tracker, and nifty features like pull requests. Most code hosted on GitHub is open source.

I keep most of my code on GitHub because

  • I can access my code from anywhere and make changes
  • I can use git without having to set up a bare repository on a remote server every time
  • It's open source, and I don't have to deal with the hassles of keeping it up to date elsewhere
  • It's pretty easy for others to report issues on it
  • It's easy for others to submit their own patches to the code via the pull request feature. I can also add collaborators with minimal hassle.
When using Git, a "commit" is basically a bundle of changes to the code, which can later be pulled/pushed between servers. On GitHub, if you've been committing code for a number of days in a row, it's called a "commit streak", and is showed on the profile. Days with relatively more commits are shown as a darker shade of green on the punchcard.

How I got started

Initially I didn't have any intention of maintaining a commit streak. Near the end of December, I was working on both Charcoal and HostelNoticeboard, and after a week and a half of constantly committing code, I noticed that I had a commit streak going. Naturally, I was pretty happy and wanted to extend this.

I first set up some ground rules, inspired by Ryan Seys:
  • Issues don't count
  • Edits to READMEs don't count
  • Edits to non-code files like GitHub Pages files do count.
  • No scripting commits; and push code the day you write it unless it's half written
  • No playing with local commit times
I also identified repositories and mini-projects that I needed to work on beforehand. This actually got some of my backburner'ed ideas out of the woodwork; some of which I actually implemented.

The journey

Initially I found it challenging to commit code every day. I had a lot of other commitments (ha!) in life and didn't want to impinge on my academics. Usually it takes a bit of time to get warmed up before coding; one has to evaluate the situation and figure out what needs to be done. This, along with debugging, takes up quite a bit of time.

However, as time passed, I got more and more efficient at this so that I could spend more time writing real code. At the same time, maintaining the commit streak became a habit. I used to always have a terminal tab open for my cloned repositories, and would be hacking away every now and then.

Sticking to an agenda becomes natural after a point

There were some days when I thought that I would be too busy to code, and would instead make some minor changes to fill in the punchcard for that day. Almost every time, I ended up unexpectedly making more substantial contributions the same day. There were also some days when I would open the site, in a panic that I forgot to code that day; and it would turn out that I had committed code, just forgotten about it. I guess that's the first sign of madness, but who cares?

Use GitHub for your academics, too!
As exam time neared, I had to switch strategies. I always had planned to put up my LaTeX documents (notes, presentations, assignments) on GitHub, making it easier for me to share them, keep them up to date, and incorporate improvements. Till then I had been using scripts to upload them to my university homepage. Which wasn't as efficient.

So I created CourseResources, and uploaded all the old documents I could find. Since I would be writing notes or assignments regularly, this provided a steady source of commits (also, a second motivation to study!) that helped me when I was too busy to write proper code. I still tried not to rely on this for the streak, though. The goal is to consolidate as many LaTeX notes as possible here; the repo is under an organization for easy collaboration. 

Where it's at now

So, in the past 50 days, the new projects I created are:

  • Kapi, a Metro note-taking app with fluid math support. Made it for a hackathon, plan to continue working on it.
  • IIT-Timetable, a webpage that lets one easily construct and share a printable semester timetable without having to worry too much about the complicated slot pattern. While there are plans to extend this, the app is complete in itself.
  • ChatExchange, a python wrapper for Stack Exchange Chat. Currently it has basic read/write functionality, but needs a lot of polishing. I also created multiple projects that use this as a submodule:
    • StackExchange-ChatBot: A python class that can be used to easily create a chatbot that can react to various commands. I created this today, and it doesn't do much yet but gives an idea of the basic structure.
    • SmokeDetector: A bot that monitors the Stack Exchange realtime feed and links to possible spam or otherwise low quality posts in a couple of chatrooms so that it can be dealt with quickly. This was intended to solve the issue of spam lying around on low-activity sites if the moderators aren't around at that moment. The bot is currently running, though I make tweaks to the algorithm every now and then.
    • ChatExchange-Scripts: A couple of random scripts created as proof-of-concepts.
  • CourseResources (both the CourseResources and Slides repos): As mentioned before, contains all my LaTeXed documents. Feel free to pull request and add your own.
  • daemonic-mach, a project to integrate inotify or watchman with Mozilla's mach build program to speed up build time. This is just a placeholder for now, I haven't yet gotten around to starting this. First commits don't count for a streak unless there are subsequent commits, so this didn't add to the streak.
  • ECMAScript6-tester, a script that loads dummy versions of proposed ES6 features into the document and reports compatibility of the document with these features. Intended to prevent naming collisions (like this one) where a prototype extension clashes with a new feature, breaking things. This repo is another placeholder.
Look at all this code I wrote!

In addition, I worked on the following preexisting projects (not necessarily my projects):
  • Charcoal, a webapp that lets one easily collect and flag noisy content (mainly comments) from Stack Exchange sites. I mainly dealt with the JS code in this.
  • HostelNoticeboard, the code (both Pi-side and server-side) for the Electronic Noticeboard project in IIT. I've written the Pi-side code and a portion of the server-side stuff. The code works and is currently deployed, on a single Pi with the online interface here. There are a bunch of improvements on the roadmap that I mean to get to in a few weeks.
  • waca: The Wikipedia Account Request System, running here. I usually do small bugfixes.
  • The Web & Coding club website, running here. I add posts and sometimes make changes to the Jekyll code.
  • Manish-Codes, random userscripts and things which I write.
All in all, plenty of code written, lots of work done :D

Where it's going to go

I really don't know how long I'll be able to keep this up. Academics do get in the way, and while I can do minor changes every day, that's not too productive. However, it's giving me a driving motivation to get all my backburner'ed projects finished, which is great! It's also taught me a lot about planning and I got a  good chance to hone my coding skills.

These 50 days have been really fun, though, and I hope I'll be able to keep it up as long as possible :)

Hope I get the time!

Octocats taken from the Octodex

Friday, February 7, 2014

Getting started with bug-squashing for Firefox

So over the past few months I've been trying to contribute a bit to Mozilla (mainly Firefox). Last August there was a MozBoot session at IIT Bombay which helped me get over the learning curve.

First off, a big thanks to @Debloper (and @hardfire) for showing me the basics. The process is intimidating, though once you've done it with help, it becomes pretty natural. These Mozilla reps got me past that intimidation point, so I'm really grateful for that.

This post is basically an tutorial on how to get started. It's basically an in-depth version of this tutorial, which I feel misses a few things.

Note that I am still a beginner at this, comments on how to improve this post/my workflow appreciated.

Ok, let's get started.

Step 1: Identifying a bug you want to fix

Firstly, make an account on You'll need it later. Browse the bug lists on the site, looking for bugs that seem fixable. Look for bugs marked as "good first bug", which have a status of "NEW".

Of course, this is a bit cumbersome to do and there are a lot of  bugs which are nontrivial or have a lot of discussion baggage which you may not want to go through. Fortunately, there are some tools out there that greatly help in searching for bugs.

Firstly, there's What Can I Do For Mozilla?. This is an interactive questionnaire that helps you find out which portions of Mozilla or Firefox you may be able to comfortably contribute to. Note that this is not just Firefox, though if you select the HTML or JS categories you will be presented with the Firefox subcategory which contains various entries.

This doesn't help find bugs as much as it helps you find the areas of the codebase that you might want to look at.

However, there is a different tool that is built specifically for this purpose; to look for easy bugs given one's preferences and capabilities. It's called Bugs Ahoy, and it lets you tick your preferences and programming languages to filter for bugs. It also has two insanely useful options, one that lets you filter out assigned bugs, and one that tells it to look for "good first bugs" ("simple bugs"). "Good first bug"s on Bugzilla are easy bugs which are kept aside for new users to try their hand at. There is a mentor for these bugs, who is a very active community member or employee. These mentors help you through the rest of the process, from where you need to look in the code to how to put up a patch. I've found that the mentors are very friendly and helpful, and the experience of being mentored on a bug is rather enjoyable.

Make sure the bug isn't assigned to anyone, and look through the comments and attachments for details on the status of the bug. Some bugs are still being discussed, and some bugs are half-written (it's not as easy to use these for your first bug). If you need help on choosing a bug, join #introduction on There are lots of helpful people out there who can give feedback on your chosen bug, and help you get started.

Step 2: Finding the relevant bits of code

If this is a mentored bug, you usually can ask the mentor in a comment on the bug for help. Be sure to get it assigned to you! If the mentor doesn't respond in a few days, use the needinfo box at the bottom of the page:

Type the username (usually preceded by a colon somewhere in the full name string), and a suggestion box should pop up with various users. Pick your mentor out from the list, and ask for help in the comment box.

If you want to look for the code yourself, Mozilla Cross-Reference is a great tool. For Firefox, you probably want the mozilla-central subtree. With MXR, you can easily search the codebase for text, variable names, and regexes.

For most UI changes, you can track the code down by first looking for nearby strings.  For example, if you want to look at the code for the where-do-I-save-downloads preference which is preceded with the text "Save files to", the search result leads to a dtd file, which defines the entity saveTo.label as the string. (Remember, all displayed strings will be in a localization file). Searching for saveTo.label turns up main.xul. Now that you've found this, you can dig deeper by looking at the event handling and figuring out where the relevant javascript is, or you can look around this same file and figure out how it works, depending on what you want to fix.

I've not really made any changes to the C++ yet, only the toolkit and UI javascript, so I can't comment on how one can find the C++ code relevant to a bug. But you can always ask around in IRC or ask your mentor (if any) for help.

Step 3: Downloading and building the code

Not all bugs need a build. Some are quite easy to do without having a full copy of the code or a build, and while you'll eventually want to have both of these, it is possible to hold this off for a while, depending on the bug. While it's easier to create patchfiles when the system is all set up, I will address patching without the full code in the next section.

Downloading can be done in two ways. Both require Mercurial to be installed (sudo apt-get mercurial works).

One way is to simply hg clone This will download the full repository. However, if you don't think your internet connection will be stable, download the mozilla-central bundle from here and follow the steps given there. Note that Mercurial is a bit different from Git, so you may wish to read up on the basics.

To build firefox , first do the OS-specific build environment setup linked to here. Once done, go to the root directory of the firefox code and run ./mach build. After your first build, you can run incremental builds (that only build the files you ask it to, and rebuilds any files depending on it) by using ./mach build <list of filepaths>, eg ./mach build browser/components/preferences/. You can specify both folders and files to the incremental build.

Note that for some javascript files, you have to build their containing directory — so if your changes aren't getting reflected in the incremental build, try building the directory they are in.

Step 4: Getting a patch

So by this point you will have figured out the fix and modified the code so that you have a partial (for a multifaceted bug) or full fix of the bug. At this point you can submit the patch for review. For this, you need to have a patch to submit first!

Creating patches with hg

If you have the full cloned repository, first add these lines to your ~/.hgrc to enable the mercurial queues extension with the proper settings.

username = Firstname Lastname 

qnew = -Ue

mq =

git = 1
unified = 8
showfunc = 1

Once done, navigate to the firefox source tree and run hg qqueue -c somequeuenamehere. This will create a named patch queue that you can work on.

Now, run hg qnew patchname.patch and then hg qpush patchname.patch. This creates a new patch by that name in the .hg/patches-queuename folder, and pushes it onto the curretly applied stack of patches. You can update its contents with the changes made to the code by  hg qrefresh or simply hg qref.  This patch is the one that you can submit in step 5.

When you run hg qnew, it will ask you to enter a commit message. Write the bug name and a short description of the patch ("Bug 12345 - Frob the baz button when foo happens"), and add a ";r=nameofreviewer". In case of mentored bugs, the uername of the mentor will be your reviewer. If not, you'll have to find a reviewer (more details on this later, for now you may leave this blank and edit it in the patch file later). Note that the default editor for this is usually vim, so you have to press Ins before typing text and then Esc followed by a :x and Enter to save.

Advanced usage

In case of complicated bugs or bugs which already have a patch, you can queue the patches up. Simply use hg qnew to create patches and  hg qpush or hg qpop to move up and down the patch queue (this will change the code to reflect the currently active patch, and hg qref will update that same patch)

If you want to work on a different bug in parallel, you just have to pop all current patches out, and create a new patch queue with hg qqueue -c. You can then switch between the queues with hg qqueue queuename.

Creating patches without hg

Since the full repository takes a really long time to download and unpack, it's useful to have a different way of making patches so that the download doesn't become a blocking step.

For preliminary patches, with just one file

This is if you want to submit a patch that can be reviewed fr feedback but not checked in as a final patch. I wouldn't recommend using this method, but I'll keep these instructions here just in case.

If you're just editing one file, put the old version and the new version side by side, and run diff -u oldfile newfile >mypatch.patch in the same directory. Now, open the patch file and edit the paths to match the relative filepath of the edited file from the root firefox directory (eg if you edited main.xulold to main.xul, replace both names with browser/components/preferences/main.xul)

Proper patches

Put the files in a directory, and then run git init on the files. Now, git add *  and then git commit -m "commit message" to commit the files.

After this, make your changes to the files. Then, run git diff -U8 >output.patch . Edit the patch and change the a/filename and b/filename lines to be a/path/to/filename and b/path/to/filename. The paths here are relative with respect to the root directory.

Now, add the following to the top of the patch

# HG changeset patch
# Parent parenthash
# User Firstname Lastname>
Bug 12345 - Frob the baz button when foo happens; r=jaws
Set the commit message as described in the above section for creating patches with hg.

As for the parent hash, you can get it by going to the mozilla-central hg repository and copying the hash of the tip commit.

Step 5: Submitting the patch, and the review process

See also: Getting reviews

Now that you're at this stage, the rest is pretty smooth sailing. Find the "add attachment"  link on the bugzilla page:

Upload the attachment, give it a descriptive name ("Patch for barring the foo", though sometimes I just use "Patch 0.1"), and make sure the "patch" checkbox is ticked

Now, you also need to ask for review. Click the dropdown next to the review menu, and set it to "?" ("requesting review"). Put the username of your reviewer in the "Requestee" box (and use the autosuggest to get the email address). If you don't know who to ask for review:
  • If the bug is mentored, your mentor will be able to review your code. Usually the mentor name will turn up in the "suggested reviewers" dropdown box in bold, too.
  • If the bug isn't mentored, you still might be able to find reviewers in the suggested reviewers dropdown. The dropdown is available for bugs in most firefox and b2g components.
  • Otherwise, ask around in IRC or check out the hg logs of the file you modified (start here) to find out who would be an appropriate reviewer.
  • A list of module owners and peers for each module can be found here (the Firefox and Toolkit ones are usually the ones you want). These users are allowed to review code in that module, so make sure you pick from those. If you mistakenly pick someone else, they'll usually be helpful enough to redirect the review to the right person.

Usually, on the first bug, your review will be canceled ("r-"). This is nothing to be worried about, the mentors (and/or reviewers) are very helpful and will let you know exactly what can be improved in the process. This is one of the things I like about Mozilla; everyone's quite helpful!

Once you fix the nits and other changes requested from you, re-upload the attachment (mark the old one as obsolete).

At one point, the review will be granted, and the code will be checked in. Once that happens, the bug will get marked as resolved. And you're done with your first bug!

Sunday, January 26, 2014

Binary Pirates security competition/CTF

The other day a friend pointed out Binary Pirates to me. It was a CTF; i.e. a capture-the-flag competition, where one has to break into various systems to get the "flag", which is a piece of text. By collecting flags you get more points and may unlock more challenges.

This one was pretty easy. It certainly was not as complicated as the DEFCON CTF quals or SIGINT CTF last year, however it had some elements in common with the simpler challenges in these CTFs. As someone who is still new to the world of information security, I thought I'd give it a try; I might learn something. While some challenges were straightforward, many made me think for quite a while.

Without further ado, the challenges and my solutions:

1: Begin

Hint: You need to Alter E then A and think like Windows not Linux, no PHP knowledge required for this question.

Hint #2: Flag is inside a file, but which file?

Here, we were taken to this page

The PHP code is one that takes the text "Hello world" and converts it to "Hell!!". This was a red herring, just looking at the source code would reveal the answer
Basically the "This is where you are very close to the flag!" was pretty literal, since the link to the file containing the flag was right underneath the text. Opening the file drowssap.DWP, I found the flag to be slumdog_millionaire!.
There was an alternate way of solving this if you take into account the hints. Apache directory listing was not turned off, and one could simply open the containing directory in the browser and see the DWP file, plain as day.

Commentary on the design: I wasn't really fond of the exclamation mark in the flag. Most CTFs have text flags with underscores, or alphanumeric flags with some special characters. Punctuation is rarely part of the flag, because the punctuation could also be part of the sentence telling you what the flag is. This including of exclamation marks is pretty widespread in this CTF. This gets really confusing; and a CTF shouldn't be about having to try various portions of text after you have in essence completed the challenge. For this reason, many CTFs follow a standard format for their flags. For example, last year's SIGINT CTF said that all flags would start with something like SIGINT_ and would be from a fixed set of characters. This makes all those "is that the flag?" moments unnecessary, and you can concentrate on the actual challenge.

2: Nologin

Hint: You mite need to disable a feature in ur browser which prompts for the password.

This took us to a page which kept doing this:

after some inspection it was obvious that the javascript prompt was not really doing anything but block the page. There was no communication with the network, thus the flag must be client side and found in the source. Solution: I fetched the page via wget and looked at the source. I could have used other tricks to disable the prompt, however these require timing and Chrome has an annoying habit of temporarily disabling other things like the dev tools when there is a prompt.

On fetching the page, this is what I saw at the bottom:

var p = prompt('Enter Password: ','here');
 if (p == "yes_its_correct")
 alert('The flag is: and_you_thot_javascript_was_fun');

So that wraps up #2.

Commentary: This was a pretty good newbie problem since both the realization that the flag is in the source code and its extraction can be done in many ways.

3: Strong

Hint: Yes you are right, what you see is the real password, but are u copy pasting it?

In this one, there was a page with a text box that we had to enter a password into. 

Of course, the first thing I did was copy paste the text "very strong", "easy to crack" and "very strong but easy to crack!" into the box. None worked, however I noticed that the last one was truncated. This was due to a maxlength attribute on the text box, which was easily removed, giving the flag what_you_c_is_the_passwd.

Commentary: Similar as #1, the text  had an exclamation mark (though this was not the flag text). This was also pretty good newbie problem, not everyone knows about editing HTML on the spot and this is a good introduction.

4: 3 Users

Hint: Try downloading the source code and changing the names

This one had a login page with a dropdown of 3 items to select the user. The password was given, however there was a restriction on the username that was displayed when you try to log in.

The usernames in the dropdown were reshuffled names of the ones who were allowed by the server. Editing the HTML on the fly and fixing any one name gave the flag as the_thrii_musketeers

Commentary: Still pretty basic, by now I would expect some more challenging ones. At this point I'm pretty sure anyone who didn't know HTML manipulation would have learned it.

5: Stega

Hint: Download the pics in the same folder

We were taken to this page:

Opening the images in new tabs and scroll-switching between the two revealed the hidden message (animation), "LOL APE". Removing the space created the flag.

6: Reverseit

Hint: Read the tutorial :P

There was a tutorial on ollydbg and a link to an exe. Basically, we had to reverse engineer the application and find the correct password for it.

Unfortunately, I was on my Ubuntu boot, and being lazy I didn't want to reboot to Windows just to use ollydbg. I could run the exe with WINE, and for that matter ollydbg too, but I decided to try my hand at it with gdb, the in-built disassembler that I have very little experience with. Till now in CTFs I have not participated much in reversing, while I know which tools to use I am not too familiar with the x86 assembly, pretty clumsy with the tools, and apt to get lost in the code. So this was a pretty new experience for me. Fortunately I have learned the 8085 assembly and know a bit about modern assembly.

So, on running   gdb on the program and then disas main, I got the following disassembled code. Most of it wasn't really necessary to look at, what we're interested in are the jump statements. There are a bunch of jne 0x4014b6 <main+200>s in the middle, all going to the same location -- most probably these are nested ifs or something similar. The relevant bit is:
 0x00401496 <+168>: cmpb $0x41,-0x18(%ebp)
 0x0040149a <+172>: jne 0x4014b6 <main+200>
 0x0040149c <+174>: cmpb $0x62,-0x17(%ebp)
 0x004014a0 <+178>: jne 0x4014b6 <main+200>
 0x004014a2 <+180>: cmpb $0x68,-0x16(%ebp)
 0x004014a6 <+184>: jne 0x4014b6 <main+200>
 0x004014a8 <+186>: cmpb $0x69,-0x15(%ebp)
 0x004014ac <+190>: jne 0x4014b6 <main+200>
 0x004014ae <+192>: cmpb $0x6b,-0x14(%ebp)
 0x004014b2 <+196>: jne 0x4014b6 <main+200>

What we see here are some comparison statements, each followed by a "jump if not equal to" statement. A literal hex number is compared with a part of a register, and if the two are not equal, the program jumps out, ending itself. This seems to be a likely candidate for the string comparison. Collecting all the literals together we get the hex string 41:62:68:69:6b (colons separate letter), which converts to "Abhik", which turns out to be the key.

Commentary: I had fun here. I believe that we were expected to do it differently, using ollydbg's powerful toolset, but regardless mucking around in not-too-complicated assembly is enjoyable. Reversing is a rather useful and important skill, this exercise gives an introduction from which others can learn.

7: Ground Zero

Hint:  is 1 = 1 ? What does the doctor give you when you are ill (am not talking abt tablets)

The page was a simple login form:

Even if you didn't get it from the hint, the first thing one tries on a login form in a CTF is SQL injection. Most CTFs have very little injection (or the ones that they have are still hard), but it's always something you try out, just in case.

After some mucking around, I was able to get the input  with username Manishearth and password ' or '1'=1;# to work. For some reason the usual MySQL commenting style, --, wasn't working (I'm pretty sure the db was MySQL), but the pound symbol worked.

The flag was sunny_days_kill_the_db

Commentary: A pretty simple injection exercise, but very useful for those new to it.

8: Unauthorized

Hint: Im hungry, what should I eat?

This was a simple page that said "You are not authorized!". Looking at the cookies, there is an auth session cookie:

This can be edited (I used the Edit This Cookie Chrome Extension but there are many others) to yes, and you get the flag as c00kies_are_sweet.

Commentary: A good introduction to cookie manipulation. Some frameworks (I recall seeing this in  a Sinatra webapp in a CTF) have vulnerabilities where you can get enough details to forge session cookies. While one would never have such an easily forged "auth" cookie on the client side, the fact that cookies can be forged easily opens up a new window to things.

9: minorTweak

Hint: Its hidden!

Another login box , this time with a registration form.

Registering normally would create an account which one can log in to, but there was nothing special there.

This one was pretty easy, there was a hidden input element called level in the registration form. 

Setting it to one and registering got you to a page which listed all users and passwords. One of the passwords was a flag:

Commentary: This was essentially about finding HTML worth manipulating again. By now I was a bit tired of these, since for simple apps these stand out from a quick look at the source.

10: What Now?

Hint: You need to post it

Here we had a username and password, but no place to put it. Or so it seemed. Submitting a simple POST request to whatnow.php with the username and password parameters didn't work.

However, what did work was tacking on an extra key-value pair to the POST request, submit:submit. Submit buttons in HTML forms are full-fledged input elements (This is by design, one may want to use more than one submit button and have the choice get recorded), with names and values, and the value of a submit button is also displayed on the button. By default, most people use <input type=submit name=submit value=Submit>, so plugging in the extra key value pair led to the flag, "stop_me_if_uoy_can".

11: Strange Crack

Hint: Is it an exe file?

There was a simple page linking to an exe that doesn't work.

This was fairly straightforward, though it is a good exercise in understanding file types. Almost all binary files have some text in them. Part if it is supposed to be easily searchable text (like artist data for a sound file), part of it has to do with the format and protocol. Usually there is some string that uniquely identifies its correct format. Here, running strings flag.exe led to:

z L .

The first two strings uniquely identify it as a WAV file. Renaming to WAV and running it led to some squabbly gibberish, however on opening in Audacity and considerably slowing down the speed, the words "The flag is waves2009" could be heard.

Commentary: I really liked the file format bit. While not uncommon in CTFs (where it is just a first step to a more elaborate problem that is usually beyond my grasp), understanding that the extension is not all that can help you learn the file format is pretty important. However, the file was pretty garbled and, when slowed down to normal speed, the "waves" bit had been spoken pretty quickly and was mashed together, making it hard to understand even after tweaking the speed/tempo a bit. I guess some more enunciation would have been good.

12: Browser

Hint: You might need to install something

The page was an error page, which advised the user to use the csd_bpgc_v4.1 browser.

Without pausing to check if this CSD thing really was a browser (apparently it's not, it is the user agent of a bot), I simply used  User Agent Switcher to make Chrome pretend to be the browser. This worked, and the flag was found.

Commentary: User agent manipulation is again a form of client side manipulation, but it's a useful one to use while testing applications. Of course, it doesn't emulate the browser, however it makes the server think that the browser is different.

13: OBL Network Login

Hint: What do you do when you forget to have lunch/dinner and start feeling hungry, (assume that maggi is not available)

Here there was a login page with the username and password already out there.

Logging in worked, but there was nothing interesting once logged in.

There was a reference to an access log in the comments

Looking at access.log, there were three users: roran, eragon, and binary_pirate. Eragon was an admin.

Of course, I now have to log in as Eragon. SQL injection wasn't working, so I had a look at the cookies. There was a separate login_user cookie that contained a hex string. It looked like a hash, so I checked and it was indeed the md5 hash of the username. So all I had to do was edit the cookie to the md5 of "eragon", after which I got the flag, kage_bunshin_no_jutsu.

Commentary: While I managed this one pretty quickly, this probably was a matter of luck. This was a wonderful mix of "forgetfulness" and an insecure implementation of session cookies. Unlikely in practice but there are many vulnerabilities along these lines found today.

14: RARE crack

Hint: you need to FORCE it

We were given a rar file which was password protected with the first half of a Goan phone number.

This looked like something brute forceable (especially obvious if you look at the hint). We know that the number is a 5 digit one starting with a 9 (Unfortunately, as a Mumbaikar, I don't know the exact Goan cell phone number ranges). This could be easily brute forced using unrar and itertools, or alternatively a modified version of rarcrack. I kept the latter running in the background, while I tried to find out the phone number ranges for Goa and wrote the former. Never really got to use it though, I got the result as 96732, which gave me access to a text file containing the flag, GJQKMNAVQEXKRFPOISBWERF.

Conclusion: Brute forcing is pretty boring, since it can be done with various tools and just takes time. As an introduction to the topic this is pretty good, though. This could have been made slightly more challenging by having the password start with a zero, making the method of forlooping through the integers no longer work.

15: Encrypto

Hint: What is so special of the number 49?

Here we were taken to a page with an encrypted string which we had to decode:

This obviously wasn't a substitution cipher as there was a double space involved. After trying out various simple ciphers, I tried the Caesar Box cipher, which worked. This cipher involves writing the characters out in a square, and reading down the columns.

In this case, the box looks like:

F E M H C I _
R _ E E U S A
O S _ _ L _ _
_ B O I U N T
T L _ D S L E
H I T I _ Y P

Which gives the text "FROM THE SUBLIME TO THE RIDICULOUS IS ONLY A STEP", giving the flag "its_the_caesar_box"

Being too lazy to write out the box myself, I used the following JS code:


Commentary: I liked this one. While it dealt with an old and rarely used cipher, it was pretty fun.

16: Crack-o-mania

Hint: See the instructions after reversing

In this one we were given an exe to download, which we had to figure out the password to.

With the same techniques as #6, I got this disassembled code. While the code was more complicated, with a lot of printing and other stuff, the main bit was the same as last time:

   0x004015b9 <+459>: cmpb   $0x45,-0x38(%ebp)
   0x004015bd <+463>: jne    0x40171c <main+814>
   0x004015c3 <+469>: cmpb   $0x4e,-0x37(%ebp)
   0x004015c7 <+473>: jne    0x40171c <main+814>
   0x004015cd <+479>: cmpb   $0x54,-0x36(%ebp)
   0x004015d1 <+483>: jne    0x40171c <main+814>
   0x004015d7 <+489>: cmpb   $0x45,-0x35(%ebp)
   0x004015db <+493>: jne    0x40171c <main+814>
   0x004015e1 <+499>: cmpb   $0x52,-0x34(%ebp)
   0x004015e5 <+503>: jne    0x40171c <main+814>
   0x004015eb <+509>: cmpb   $0x2d,-0x33(%ebp)
   0x004015ef <+513>: jne    0x40171c <main+814>
   0x004015f5 <+519>: cmpb   $0x50,-0x32(%ebp)
   0x004015f9 <+523>: jne    0x40171c <main+814>
   0x004015ff <+529>: cmpb   $0x41,-0x31(%ebp)
   0x00401603 <+533>: jne    0x40171c <main+814>
   0x00401609 <+539>: cmpb   $0x53,-0x30(%ebp)
   0x0040160d <+543>: jne    0x40171c <main+814>
   0x00401613 <+549>: cmpb   $0x53,-0x2f(%ebp)
   0x00401617 <+553>: jne    0x40171c <main+814>
   0x0040161d <+559>: cmpb   $0x57,-0x2e(%ebp)
   0x00401621 <+563>: jne    0x40171c <main+814>
   0x00401627 <+569>: cmpb   $0x4f,-0x2d(%ebp)
   0x0040162b <+573>: jne    0x40171c <main+814>
   0x00401631 <+579>: cmpb   $0x52,-0x2c(%ebp)
   0x00401635 <+583>: jne    0x40171c <main+814>
   0x0040163b <+589>: cmpb   $0x44,-0x2b(%ebp)
   0x0040163f <+593>: jne    0x40171c <main+814>
   0x00401645 <+599>: cmpb   $0x3a,-0x2a(%ebp)
   0x00401649 <+603>: jne    0x40171c <main+814>
   0x0040164f <+609>: cmpb   $0x28,-0x29(%ebp)
   0x00401653 <+613>: jne    0x40171c <main+814>
   0x00401659 <+619>: cmpb   $0x0,-0x28(%ebp)
   0x0040165d <+623>: jne    0x40171c <main+814>

The  ascii codes for the literals in the cmpb statements translate to "ENTER-PASSWORD:(", which is the key.

Commentary: I believe this one would have been trickier via ollydbg, since the contestants had been trained to use the "find strings"  feature. In this case the "ENTER-PASSWORD:" would have seemed to be part of the output and not the input, so the question would have to be solved by stepping through. With gdb it was more or less the same problem, just with more assembly to sift through. Still had fun with this one.

17: Warn

Hint: Look above

(I have found a vulnerability, but not yet solved this one)

We were taken to a page with a login screen that provided credentials:

On login, a message appears "You need to warn the server with the message 'vulnerable'.". Interesting.

After mucking around a bit, I realized that the page was vulnerable to array-ification of the inputs. Editing the names of the inputs so that they become arrays,

one can log in with any username or password.

Why does this work? This is made evident from the warning messages shown when you try it:

PHPs strcasecmp() is being used here to check for the password, and it does not like array inputs. The way it works is that it returns zero if the strings are equal, and -1 if they are not. However, if an input is invalid, it also returns something which evaluates to a false<.code>; so passing an array to it can be used to bypass the string check.

However, I was unable to warn the server with that message even by making every vulnerable field (username, password, submit, all of the free request headers) read "vulnerable"

So I've not been able to complete the challenge, though I have found a vulnerability.

Commentary: Since this one has stumped me, I like it a lot :) Array injection

18: Patient's Nightmare

Hint: This is an old php server and allows simultaneous queries

Here, we are shown another login form

On submitting the page, some blending-in-with-background text appears with SQL code

rubs palms SQL injection time!

So this one is different from most simple SQL injection logins in that most of them can be bypassed by tacking on an or '1'=1 or anything else which evaluates to true because they usually are of the type where you SELECT  * FROM users WHERE username='$user' AND password='$pass'. But here, they are SELECTing the password and comparing it via PHP. So we have to make the SQL query return a fake password. This is doable with a UNION query.

The following query worked:
' union select 'def' as password;#

(with the password as "def")

The flag was the_nightmare_cms_true

Commentary: The UNION query is not usually known to newbies, making this a rather challenging problem. I found it easy, but fun.

Problem 19

Hint: You need to supply something, something which you should avoid doing with a fool

Here we were given another exe (disassembled code), and the text:

Download the file, try to crack it till you get the message, "correct!".
Concatenate the strings you entered and submit it to the scoring server. Do not leave any blank space in your answer.

In this case, looking at the assembly code (or the hint), we had to provide arguments. Specifically, two arguments, as was evident from the lines (compare with 2, and jump away if there aren't enough arguments)

0x00401439 <+75>:    cmpl   $0x2,0x8(%ebp)
0x0040143d <+79>:    jle    0x40165c <main+622>

This is roughly equivalent to a



Similar to the last few, there was a huge row of comparison statements

 0x00401443 <+85>:    mov    0xc(%ebp),%eax
   0x00401446 <+88>:    add    $0x4,%eax
   0x00401449 <+91>:    mov    (%eax),%eax
   0x0040144b <+93>:    cmpb   $0x31,(%eax)
   0x0040144e <+96>:    jne    0x4015e2 <main+500>
   0x00401454 <+102>:   mov    0xc(%ebp),%eax
   0x00401457 <+105>:   add    $0x4,%eax
   0x0040145a <+108>:   mov    (%eax),%eax
   0x0040145c <+110>:   inc    %eax
   0x0040145d <+111>:   cmpb   $0x33,(%eax)
   0x00401460 <+114>:   jne    0x4015e2 <main+500>
   0x00401466 <+120>:   mov    0xc(%ebp),%eax
   0x00401469 <+123>:   add    $0x4,%eax
   0x0040146c <+126>:   mov    (%eax),%eax
   0x0040146e <+128>:   add    $0x2,%eax
   0x00401471 <+131>:   cmpb   $0x30,(%eax)
   0x00401474 <+134>:   jne    0x4015e2 <main+500>
   0x0040147a <+140>:   mov    0xc(%ebp),%eax
   0x0040147d <+143>:   add    $0x4,%eax
   0x00401480 <+146>:   mov    (%eax),%eax
   0x00401482 <+148>:   add    $0x3,%eax
   0x00401485 <+151>:   cmpb   $0x51,(%eax)
   0x00401488 <+154>:   jne    0x4015e2 <main+500>
   0x0040148e <+160>:   mov    0xc(%ebp),%eax
   0x00401491 <+163>:   add    $0x4,%eax
   0x00401494 <+166>:   mov    (%eax),%eax
   0x00401496 <+168>:   add    $0x4,%eax
   0x00401499 <+171>:   cmpb   $0x63,(%eax)
   0x0040149c <+174>:   jne    0x4015e2 <main+500>
   0x004014a2 <+180>:   mov    0xc(%ebp),%eax
   0x004014a5 <+183>:   add    $0x4,%eax
   0x004014a8 <+186>:   mov    (%eax),%eax
   0x004014aa <+188>:   add    $0x5,%eax
   0x004014ad <+191>:   cmpb   $0x69,(%eax)
   0x004014b0 <+194>:   jne    0x4015e2 <main+500>
   0x004014b6 <+200>:   mov    0xc(%ebp),%eax
   0x004014b9 <+203>:   add    $0x8,%eax
   0x004014bc <+206>:   mov    (%eax),%eax
   0x004014be <+208>:   inc    %eax
   0x004014bf <+209>:   cmpb   $0x67,(%eax)
   0x004014c2 <+212>:   jne    0x4015e2 <main+500>
   0x004014c8 <+218>:   mov    0xc(%ebp),%eax
   0x004014cb <+221>:   add    $0x8,%eax
   0x004014ce <+224>:   mov    (%eax),%eax
   0x004014d0 <+226>:   add    $0x2,%eax
   0x004014d3 <+229>:   cmpb   $0x64,(%eax)
   0x004014d6 <+232>:   jne    0x4015e2 <main+500>
   0x004014dc <+238>:   mov    0xc(%ebp),%eax
   0x004014df <+241>:   add    $0x8,%eax
   0x004014e2 <+244>:   mov    (%eax),%eax
   0x004014e4 <+246>:   add    $0x3,%eax
   0x004014e7 <+249>:   cmpb   $0x64,(%eax)
   0x004014ea <+252>:   jne    0x4015e2 <main+500>
   0x004014f0 <+258>:   mov    0xc(%ebp),%eax
   0x004014f3 <+261>:   add    $0x8,%eax
   0x004014f6 <+264>:   mov    (%eax),%eax
   0x004014f8 <+266>:   add    $0x4,%eax
   0x004014fb <+269>:   cmpb   $0x6d,(%eax)
   0x004014fe <+272>:   jne    0x4015e2 <main+500>
   0x00401504 <+278>:   mov    0xc(%ebp),%eax
   0x00401507 <+281>:   add    $0x8,%eax
   0x0040150a <+284>:   mov    (%eax),%eax
   0x0040150c <+286>:   add    $0x5,%eax
   0x0040150f <+289>:   cmpb   $0x4d,(%eax)
   0x00401512 <+292>:   jne    0x4015e2 <main+500>
   0x00401518 <+298>:   mov    0xc(%ebp),%eax
   0x0040151b <+301>:   add    $0x8,%eax
   0x0040151e <+304>:   mov    (%eax),%eax
   0x00401520 <+306>:   cmpb   $0x48,(%eax)
   0x00401523 <+309>:   jne    0x4015e2 <main+500>

Each statement is of the approximate form of a move statement that brings a memory location into the register. Some number is added to it, and then the data at that location is compared. Basically, the inputted data was stored in an array, and it was being compared bit by bit
   0x004014f0 <+258>:   mov    0xc(%ebp),%eax ; move data to register
   0x004014f3 <+261>:   add    $0x8,%eax ; add offset of 8 (can be 4, depends on which of the two argv elements I am accessing)
   0x004014f6 <+264>:   mov    (%eax),%eax ; get memory at register contents
   0x004014f8 <+266>:   add    $0x4,%eax ; add array offset of 4
                                         ; can be anything from 2 upwards.
                                         ; to access a[1], use inc
                                         ; to access a[0], don't add anything
   0x004014fb <+269>:   cmpb   $0x6d,(%eax) ; compare with byte 6D
   0x004014fe <+272>:   jne    0x4015e2 <main+500> ; exit if not equals

So all I had to do was extract the ascii strings and place them as two arguments.

However, there was a little hitch. The statements from line 298 to 309 are actually for an earlier part of the string. Since they are comparing character-by-character, they can do it in any order. So they did the first letter (we know it is the first since there is no second add statement adding an array offset). Moving this character around, we get the string 130Qci HgddmM.

Commentary: This one was fun. You had to first realize that it needed two arguments, then figure out exactly what all those add/inc statements were doing. Once you realized that each one represented a shift in either the argv array or the string character array, the rest was a cakewalk though noticing that one of the letters was shifted was a bit non obvious.

Problem 20

Here, we were given the following ciphertext:
"gmsnmv rsfmvs bh rvacan cmis essk bh gmnnxan gbos ubc usmv gmngm emjs mu sws cb gxuum cvanc ubc cvsrbuxan omvi fsyy oscsyyan gxorsv ksgxan rvacan ybjsn cess ubc ceba emnc fvbuqsk gmxan yxqmvxan cesvs xn rac bus oxuk xu myy cesns osu muk xc xn rsuc mqmxunc gmsnmv"

This seemed like a substitution cipher. It turned out to not be in Modern English, so that made it a bit harder. I can't post details about the entire process here, but I eventually got the substitution array:


and the plaintext:

"caesar beware of brutus take heed of cassius come not near casca have an eye to cinna trust not trebonius mark well metellus cimber decius brutus loves thee not thou hast wronged caius ligarius there is but one mind in all these men and it is bent against caesar"

The flag turned out to be "Et tu Brute, then fall Caesar!"

Commentary: Always nice to have some crypto, however basic.


I had fun, though I found it a bit easy and was frustrated as the levels did not get much harder. Of course, it was meant for newbies to the realm of security, so I guess that's ok. It was a bit haphazardly constructed; like I mentioned before, there was no uniformity in the flags. There were also not as many "real world" type problems as I would have liked (the only ones that qualify are the reversing ones--which were all really the same, sql injection, directory index, server-side validation, and the strcasecmp() one). I guess these are more personal gripes as I may have not been in the target audience.

I enjoyed the variety though, and could recognize that these questions were great ways to induce comfort with the tools and the mental perspective required to do these things at a higher level. I've been thinking of conducting my own CTF for IITB, but I've always been intimidated by it. As far as I can tell, this one was designed by students too, so kudos to the team who made the CTF for pulling through with this!

Wednesday, January 22, 2014

Editing files from omni.ja in Firefox 20 onwards

This post focuses on Ubuntu, however when I get time I will update it with instructions for Windows.

So I've been working on a small side project recently and I found that editing the live code on Firefox is not as easy as it used to be. I had to do a fair amount of digging (and sifting out of outdated information) to get all the information on how to modify omni.ja files from live Firefox installs, so I'll just collect what I learned in this post.

Firefox is a browser written in C/C++, JavaScript, CSS, and XML. The C++ bits are compiled (by a very lengthy build process), but the Javascript is simply zipped up and loaded at runtime. There are binary versions of the JS files (which probably load faster), but these are not necessary.

Previously, the JS was uncompressed and just lying around in directories, and one could simply edit these files to change some functionality. While the core APIs are in C, most of the behavior of the Firefox UI is in JS/XML and thus a lot of tweaking can be done through these files. Of course, making an addon may be a viable option, but you may not always want to do that.

However to improve performance the files were gradually bundled in jars, finally resulting in the monstrosity known as omni.ja. This is a rather quirky jar file that contains the JS and JS "binaries".

Fortunately, the files in this jar can still be edited, with some more effort.

Firstly, let me note that on Ubuntu, there are two  omni.jas. The first in in /usr/lib/firefox, and the second is in /usr/lib/firefox/browser. These contain different code, so you may have to find out which one holds your JS.

Extracting the files from omni.ja is pretty simple. Copy omni.ja to a temporary directory, and run unzip omni.ja on it. (sudo apt-get install unzip may be necessary. Alternatively, use Ubuntu's Archive manager after renaming it to a .zip)

If you wish to modify a file from omni.ja, be sure that you delete its corresponding binary in the jsloader/resources/gre subtree or under the jssubloader tree. Then modify the javascript file as usual.

Try to keep a backup of the old omni.ja just in case, syntax errors may stop Firefox from loading.

To repack, you have to run zip -qr9XD omni.ja * in the same temporary directory. Be sure to delete the old omni.ja file before zipping, otherwise you may end up with a nested omni.ja. While I was playing with the file, after an initial smooth period where everything worked, I started getting segfaults even when simply unpacking, repacking, and loading omni.ja because I was neglecting to delete the old omni.ja, which created a jar that was nested in around 25 levels, which was too large for Firefox.

Now copy the new omni.ja from the temporary directory to where you got it from. Give everyone read permissions (chmod a+r /usr/lib/firefox)

This still does not ensure that the new jar file will be loaded. What you need to do to force this reload is open Firefox, disable or enable an addon (this only works if the addon is one that requires restart after being disabled or enabled — if this is the case you will be prompted to restart), and restart Firefox from the prompt. Other ways to do this (credit: Neil Rashbrook)

  • Use the --purgecaches command line parameter
  • Set the MOZ_PURGE_CACHES environment variable to 1
  • Use the .purgecaches file

Once you force reload omni.ja, Firefox should run on your new code.

Friday, July 12, 2013