sometimes it’s easier to just work backwards and decompile a binary instead of trying to make sense of the code. IOCCC specializes in code that makes it impractical to understand the code. this is where reverse engineering comes into play, how about we just decompile the binary and work back to a high level understanding of its functionality :D
IOCCC - International Obfuscated C Code Contest
IOCCC is a computer programming contest for code written in C that is the most creatively obfuscated and held annually (when possible). Today, I will try and reverse engineer some of the entries from the past few years and rank them based on how hard it was to figure out what the application is meant to do.
I will be using Ghidra, an open source Software Reverse Engineering (SRE) created and maintained by the NSA. From the past winning entries I’ve picked out some programs to try and reverse engineer.
running the code through ghidra gives us the usual decompiled code with symbol tree.

skimming through the function definitions we find main function which gives us the following approximated code:
undefined8 main(void)
{
byte bVar1;
int *piVar2;
int iVar3;
char *extraout_RDX;
char *extraout_RDX_00;
int iVar4;
int iVar5;
int iVar6;
int iVar7;
int iVar8;
int iVar9;
bool bVar10;
int local_50;
int local_4c;
timespec local_48;
iVar8 = 5;
iVar7 = 0x22;
g();
gettimeofday((timeval *)&local_48,(__timezone_ptr_t)0x0);
v = (uint)(char)local_48.tv_sec;
k();
n();
iVar4 = d + -1;
iVar5 = c + 10;
while( true ) {
piVar2 = &local_50;
p(0,iVar7,0x23 - iVar7,&local_50,&local_4c);
iVar9 = (int)piVar2;
iVar6 = local_4c;
if (0 < local_4c) {
iVar6 = local_4c - (uint)(d + -1 <= iVar4);
}
if (((uint)(iVar7 * -0x55555555) < 0x55555556) && (iVar8 < 1)) {
iVar8 = 3;
iVar3 = E % 2;
iVar9 = iVar7 + 5;
E = E + 1;
t(iVar5,iVar4,iVar3 + 1);
}
iVar5 = iVar5 + local_50;
if (iVar5 < 0) {
iVar5 = 0;
}
iVar4 = iVar4 + iVar6;
if (iVar4 < 0) {
iVar4 = 0;
}
iVar3 = iVar5;
l(iVar4);
v = (v ^ 8) + 1;
bVar1 = (byte)((int)v >> 0x1f);
if ((byte)(((char)v + (bVar1 >> 5) & 7) - (bVar1 >> 5)) < 2) {
i();
m((void *)0x3,iVar3,extraout_RDX,iVar9,iVar6);
}
else {
write(1,&DAT_00103004,2);
write(1,O + 0x74,2);
write(1,&DAT_0010300b,1);
}
iVar7 = iVar7 + -1;
write(1,O + 0x80,3);
write(1,&DAT_00103004,2);
write(1,&DAT_0010300a,2);
local_48._0_11_ = SUB1611((undefined1 [16])0x0,0);
local_48.tv_nsec._3_1_ = 1;
local_48.tv_nsec._4_4_ = 0;
nanosleep(&local_48,(timespec *)0x0);
if (iVar7 == 9) break;
iVar8 = iVar8 + -1;
}
iVar7 = 9;
do {
iVar8 = (int)&local_4c;
piVar2 = &local_50;
p(0,iVar7,0x23 - iVar7);
iVar6 = (int)piVar2;
iVar9 = local_4c;
if (0 < local_4c) {
iVar9 = local_4c - (uint)(d + -1 <= iVar4);
}
t.constprop.1(iVar5,iVar4,iVar7);
iVar5 = iVar5 + local_50;
if (iVar5 < 0) {
iVar5 = 0;
}
iVar4 = iVar4 + iVar9;
if (iVar4 < 0) {
iVar4 = 0;
}
iVar9 = iVar5;
l(iVar4);
v = (v ^ 8) + 1;
bVar1 = (byte)((int)v >> 0x1f);
if ((byte)(((char)v + (bVar1 >> 5) & 7) - (bVar1 >> 5)) < 2) {
i();
m((void *)0x3,iVar9,extraout_RDX_00,iVar6,iVar8);
}
else {
write(1,&DAT_00103004,2);
write(1,O + 0x74,2);
write(1,&DAT_0010300b,1);
}
write(1,O + 0x80,3);
write(1,&DAT_00103004,2);
write(1,&DAT_0010300a,2);
local_48._0_11_ = SUB1611((undefined1 [16])0x0,0);
local_48.tv_nsec._3_1_ = 1;
local_48.tv_nsec._4_4_ = 0;
nanosleep(&local_48,(timespec *)0x0);
bVar10 = iVar7 != 0;
iVar7 = iVar7 + -1;
} while (bVar10);
l(b,0);
return 0;
}from the above snippet we can see that there’s some declarations in the code, skipping over to the main loop of the code we see a while loop that writes onto the screen and a do while loop which does the same. it looks like the code uses system time as a seed and randomizes a process which prints out stuff on the screen. “?1049” is an ansi escape sequence to request a new buffer and “2J” clears the buffer.
my guess is that this code uses datetime as a seed and generates visuals to display on the screen. let’s run the code to see how close the estimate was to the actual output.

the code generates a bonsai tree in the terminal.
following the standard procedure we run the code through ghidra and skim through the approximated code.

undefined8 main(void)
{
byte bVar1;
uint uVar2;
uint uVar3;
undefined1 auVar4 [16];
int iVar5;
long lVar6;
uint uVar7;
uint uVar8;
long lVar9;
uint uVar10;
long lVar11;
long lVar12;
uint uVar13;
uint uVar14;
long lVar15;
uint uVar16;
uint uVar17;
uint uVar18;
uint uVar19;
int local_94;
uint local_88 [4];
undefined1 local_78 [16];
undefined1 local_68 [16];
undefined1 local_58 [16];
undefined1 local_48 [16];
lVar11 = 0;
uVar18 = 0x98badcfe;
uVar8 = 0x11111111;
uVar13 = 0x67452301;
uVar7 = 0xefcdab89;
uVar16 = 0x10325476;
local_78 = (undefined1 [16])0x0;
local_68 = (undefined1 [16])0x0;
local_58 = (undefined1 [16])0x0;
local_48 = (undefined1 [16])0x0;
local_94 = 0x76543210;
local_88[0] = 0x67452301;
local_88[1] = 0xefcdab89;
local_88[2] = 0x98badcfe;
local_88[3] = 0x10325476;
lVar15 = lVar11;
do {
lVar6 = 0x40;
LAB_00101111:
auVar4 = local_78;
if (lVar6 != 8) {
if (lVar6 == 0) goto LAB_00101188;
uVar14 = 0;
if (-1 < lVar15) goto LAB_00101147;
LAB_001010f8:
uVar8 = uVar8 >> 8 | uVar14;
lVar12 = lVar6 + -1;
lVar6 = lVar6 + 2;
if (-1 < lVar12) {
lVar6 = lVar12;
}
*(uint *)(local_78 + (lVar6 >> 2) * 4) = uVar8;
lVar6 = lVar12;
goto LAB_00101111;
}
local_78._4_4_ = (int)lVar11;
local_78._0_4_ = (int)((ulong)lVar11 >> 0x20);
local_78._8_8_ = auVar4._8_8_;
if (-1 < lVar15) {
local_94 = 1;
LAB_00101147:
iVar5 = getc(stdin);
lVar15 = (long)iVar5;
if (-1 < lVar15) {
lVar11 = lVar11 + 8;
}
uVar14 = 0x80000000;
if (-1 < lVar15) {
uVar14 = iVar5 << 0x18;
}
goto LAB_001010f8;
}
local_94 = 0;
LAB_00101188:
uVar14 = 0;
lVar12 = 0;
lVar6 = -0x10000000000;
uVar8 = 0;
do {
uVar17 = uVar18;
uVar19 = uVar16;
uVar18 = uVar7;
lVar9 = (lVar6 * 0x8f091 + lVar12 * 0xfb50 >> 0x15) + lVar12 * 0x8a514 + lVar6 * 0xd76aa;
lVar6 = lVar6 * 0x8a514 + (lVar6 * 0xfb50 + lVar12 * -0x8f091 >> 0x15) + lVar12 * -0xd76aa >>
0x14;
lVar12 = lVar9 >> 0x14;
uVar7 = ((uVar8 | 0xc) * 0x98) % 0x21f;
if (uVar14 == 3) {
uVar16 = ~uVar19 | uVar18;
}
else {
uVar16 = uVar19 ^ uVar18;
if (uVar14 != 2) {
if (uVar14 == 0) {
uVar16 = ~uVar18 & (uVar19 ^ uVar17);
}
else {
uVar16 = (uVar17 ^ uVar18) & uVar19;
}
}
}
uVar10 = uVar8 + 1;
uVar3 = uVar14 * 7;
uVar2 = uVar14 * 5;
uVar14 = uVar10 >> 4;
uVar13 = ((uint)(lVar9 >> 0x1c) ^ (uint)(lVar9 >> 0x3c)) +
local_88[0x13 - ((uVar3 >> 1 & 5) - ~(uVar2 & 6) * uVar8 & 0xf)] + (uVar16 ^ uVar17)
+ uVar13;
bVar1 = ((char)uVar7 + (char)(uVar7 / 0x52) * -0x52 & 3U) + 4 +
(char)((uVar8 & 3) * 0x2b >> 3) & 0x1f;
uVar7 = (uVar13 << bVar1 | uVar13 >> 0x20 - bVar1) + uVar18;
uVar16 = uVar17;
uVar8 = uVar10;
uVar13 = uVar19;
} while (uVar10 != 0x40);
uVar7 = uVar7 + local_88[1];
uVar8 = 0;
uVar16 = uVar17 + local_88[3];
uVar13 = uVar19 + local_88[0];
uVar18 = uVar18 + local_88[2];
local_88[1] = uVar7;
local_88[0] = uVar13;
local_88[3] = uVar16;
local_88[2] = uVar18;
if (local_94 == 0) {
uVar8 = 0;
do {
uVar7 = uVar8 + 1;
uVar13 = uVar13 >> ((byte)((uVar8 & 7) << 2) ^ 4) & 0xf;
putc((-(uint)(uVar13 < 10) & 0xffffffd9) + 0x57 + uVar13,stdout);
uVar13 = local_88[uVar7 >> 3];
uVar8 = uVar7;
} while (uVar7 != 0x20);
putc(10,stdout);
return 0;
}
} while( true );
}the given decompiled code has a few hints to go off of. on line 124 while (uVar10 != 0x40); it checks for block size of 512 bits which implies the code uses blocks of 512 bits. the final output loop runs for 0x20 iterations (32 iterations) giving out one character in each iteration which means the final output size is 128 bits in length. a fixed output length implies that the code could be for some hashing algorithm. the output length being 128 bits gives us the options of MD5 or some truncated modern algorithm. existence of 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 declared in lines 41-44 which are standard initial constants A, B, C, D for MD5 hashing algorithm points to the code being an md5 hash implementation.

the above shows the code output which are hashes for the input strings.
note: upon furthering testing i noticed there’s some discrepancies when matching hashes with sites to calculate hash. that’s because of differences in utf or ascii inputs and other factors.
following the usual workflow of analyzing and decompiling the main function in ghidra we get the following decompiled code.
undefined8 main(void)
{
int iVar1;
o = o + 1;
while( true ) {
iVar1 = getchar();
n = iVar1 + 1;
if (n == 0) break;
if (((0xe < n == n < 10) || (iVar1 = 0, n == 0x21)) && (iVar1 = o, o == 0)) {
iVar1 = j + 1;
j = iVar1;
}
y = y + 1;
e = e + (n == 0xb);
o = iVar1;
}
j = (uint)(y != 0 && o == 0) + j;
printf("%8d%8d%8d\n",(ulong)e);
return 0;
}this one’s pretty straighforward when it comes to understanding the workflow of the code. it cycles through characters to get a line, word and character count of the input file. compared to the original obfuscated code we had at the beginning the decompiled code makes it a lot easier to understand the underlying functionality.
┌──(kali㉿kali)-[~/Downloads/2019_burton/2019/burton]
└─$ cat prog.c
e,n,j,o,y;main(){for(++o;n= ~getchar();e+=11==n,y++)o=n>0xe^012>n&&'`'^n^65?!n:!o?++j:o;printf("%8d%8d%8d\n",e^n,j+=!o&&y,y);}once again for another entry we analyze the binary and take a look at the decompiled code in ghidra.
uint main(void)
{
int iVar1;
bool bVar2;
char *pcVar3;
undefined *puVar4;
char cVar5;
int iVar6;
char *pcVar7;
uint *puVar8;
long lVar9;
uint *puVar10;
int iVar11;
int *piVar12;
uint uVar13;
byte *pbVar14;
byte bVar15;
int iVar16;
ulong uVar17;
int iVar18;
uint uVar19;
int iVar20;
int iVar21;
uint uVar22;
uint uVar23;
uint uVar24;
uint uVar25;
X = Y + -1;
for (; (int)i < 0x3f; i = i + 1) {
pcVar3 = G + 2;
r = G;
while (pcVar7 = pcVar3, G = pcVar7 + 1, *pcVar7 != '\0') {
pcVar3 = G;
if ('&' < (char)*G) {
if (*G == *r) {
pbVar14 = I + 1;
*I = ((char *)r)[1];
I = pbVar14;
puVar4 = I + 1;
*I = ((char *)r)[2];
I = puVar4;
pcVar3 = G;
}
else {
pbVar14 = I + 1;
*I = pcVar7[1];
I = pbVar14;
pcVar3 = G;
}
}
}
I = I + 1;
}
j = 0xc;
r = I;
while( true ) {
i = getc(stdin);
cVar5 = (char)i;
*I = cVar5;
if (cVar5 < '\0') break;
if (i == 10) {
H = H + 0x14;
if (W < (int)j) {
W = j;
}
j = 0xc;
}
else {
j = j + 0xc;
I = I + -(ulong)(0x5e < i - 0x20);
}
I = I + 1;
}
bVar15 = *r;
while (-1 < (char)bVar15) {
if (bVar15 == 10) {
h = h + 0x14;
bVar15 = r[1];
w = z;
}
else {
J();
w = w + 1;
bVar15 = r[1];
}
r = r + 1;
}
putc(0x47,stdout);
putc(0x49,stdout);
V(0x3846);
putc(0x39,stdout);
putc(0x61,stdout);
V(W);
V(H);
putc(0xf4,stdout);
i = z;
V();
uVar13 = i;
while ((int)uVar13 < 0x60) {
i = uVar13 + 1;
putc((((int)uVar13 / 3) * X) / 0x1f,stdout);
uVar13 = i;
}
i = uVar13;
putc(0x21,stdout);
putc(X,stdout);
putc(0xb,stdout);
G = "SJYXHFUJ735";
do {
cVar5 = *G;
G = G + 1;
putc(cVar5 + -5,stdout);
} while (*G != '\0');
putc(3,stdout);
V(1);
j = z;
V();
do {
if (0x3e < (int)j) {
putc(0x3b,stdout);
return z;
}
k = 0x101;
putc((int)(0xf921 % (long)Y),stdout);
putc((int)(0xf921 / (long)Y),stdout);
iVar6 = k * 4;
putc(iVar6 % Y,stdout);
putc(iVar6 / Y,stdout);
uVar13 = -(uint)((j & 0x1f) == 0) & 500;
putc((int)((long)(ulong)(uVar13 + 0xb) % (long)Y),stdout);
putc((int)((long)(ulong)(uVar13 + 0xb) / (long)Y),stdout);
uVar13 = z;
putc((int)z % Y,stdout);
putc((int)uVar13 / Y,stdout);
putc(0x2c,stdout);
uVar13 = z;
f = z;
i = z;
putc((int)z % Y,stdout);
putc((int)uVar13 / Y,stdout);
uVar13 = z;
putc((int)z % Y,stdout);
putc((int)uVar13 / Y,stdout);
iVar6 = W;
putc(W % Y,stdout);
putc(iVar6 / Y,stdout);
iVar6 = H;
putc(H % Y,stdout);
putc(iVar6 / Y,stdout);
putc((int)(0x800 % (long)Y),stdout);
putc((int)(0x800 / (long)Y),stdout);
uVar13 = i;
iVar20 = W;
iVar21 = H;
iVar6 = Y;
t = T;
iVar18 = W * H;
pbVar14 = I + iVar18;
G = pbVar14;
r = pbVar14;
if ((int)i < 0x200000) {
uVar19 = i;
if ((int)i < Y) {
iVar16 = 0x200000;
if (Y < 0x200001) {
iVar16 = Y;
}
bVar2 = iVar16 <= (int)i;
uVar17 = (ulong)(iVar16 - i);
uVar19 = (iVar16 - i) - 1;
uVar22 = i;
if (((int)i < iVar16) && (2 < uVar19)) {
if (bVar2) {
uVar17 = 1;
}
uVar23 = i + 1;
uVar24 = i + 2;
uVar25 = i + 3;
puVar8 = (uint *)(T + (long)(int)i * 4);
puVar10 = puVar8 + (uVar17 & 0xfffffffffffffffc);
do {
*puVar8 = uVar22;
puVar8[1] = uVar23;
puVar8[2] = uVar24;
puVar8[3] = uVar25;
puVar8 = puVar8 + 4;
uVar22 = uVar22 + 4;
uVar23 = uVar23 + 4;
uVar24 = uVar24 + 4;
uVar25 = uVar25 + 4;
} while (puVar8 != puVar10);
if ((uVar17 & 3) != 0) {
uVar22 = uVar13 + ((uint)uVar17 & 0xfffffffc);
goto LAB_001016ff;
}
}
else {
LAB_001016ff:
*(uint *)(T + (long)(int)uVar22 * 4) = uVar22;
iVar1 = uVar22 + 1;
if (iVar1 < iVar16) {
iVar11 = uVar22 + 2;
*(int *)(T + (long)iVar1 * 4) = iVar1;
if (iVar11 < iVar16) {
*(int *)(T + (long)iVar11 * 4) = iVar11;
}
}
}
if (bVar2) {
uVar19 = 0;
}
uVar19 = uVar13 + 1 + uVar19;
if (uVar19 == 0x200000) goto LAB_0010176c;
}
memset(T + (long)(int)uVar19 * 4,0xff,(ulong)(0x200000 - uVar19) * 4);
}
LAB_0010176c:
Z = k;
if (k == 0) {
i = 0;
if (0 < iVar18) goto LAB_0010186d;
}
else {
while( true ) {
while( true ) {
bVar15 = *pbVar14;
if (f == 0) {
bVar15 = 0;
}
*pbVar14 = bVar15 | (byte)(iVar6 % 2 << ((byte)f & 0x1f));
pq = 2;
if (6 < (int)f) break;
f = f + 1;
Z = Z / 2;
if (Z == 0) goto LAB_00101840;
iVar6 = iVar6 / 2;
pbVar14 = G;
}
f = z;
G = G + 1;
pq = 4;
Z = Z / 2;
if (Z == 0) break;
iVar6 = iVar6 / 2;
pbVar14 = G;
}
LAB_00101840:
i = 0;
pbVar14 = G;
iVar20 = W;
iVar21 = H;
if (W * H < 1) {
iVar6 = 0;
Z = k;
}
else {
LAB_0010186d:
i = 0;
Z = k;
iVar18 = 0;
do {
if (I[(int)i] == '\0') {
c = 0x1f - j;
if (0x1f < (int)j) {
c = j - 0x1f;
}
}
else {
c = ((char)I[(int)i] + -1) * 0x1f;
}
piVar12 = (int *)(t + (long)c * 4);
iVar6 = *piVar12;
if (iVar6 < (int)z) {
iVar6 = 0;
if (Z == 0) {
LAB_00101a37:
Z = iVar6 + 1;
k = Z;
*piVar12 = Z;
lVar9 = (long)c;
}
else {
while( true ) {
while( true ) {
bVar15 = *pbVar14;
if (f == 0) {
bVar15 = 0;
}
*pbVar14 = bVar15 | (byte)(iVar18 % 2 << ((byte)f & 0x1f));
pq = 2;
if (6 < (int)f) break;
f = f + 1;
Z = Z / 2;
if (Z == 0) goto LAB_001019f0;
iVar18 = iVar18 / 2;
pbVar14 = G;
}
f = z;
G = G + 1;
pq = 4;
Z = Z / 2;
if (Z == 0) break;
iVar18 = iVar18 / 2;
pbVar14 = G;
}
LAB_001019f0:
lVar9 = (long)c;
pbVar14 = G;
Z = k;
if (k < 0xffe) {
piVar12 = (int *)(t + lVar9 * 4);
iVar6 = k;
goto LAB_00101a37;
}
}
iVar6 = *(int *)(T + lVar9 * 4);
iVar20 = W;
iVar21 = H;
}
i = i + 1;
t = T + ((long)(Y * iVar6) + (long)Y) * 4;
iVar18 = iVar6;
} while ((int)i < iVar20 * iVar21);
}
if (Z != 0) {
while( true ) {
while( true ) {
bVar15 = *pbVar14;
if (f == 0) {
bVar15 = 0;
}
*pbVar14 = bVar15 | (byte)(iVar6 % 2 << ((byte)f & 0x1f));
pq = 2;
if (6 < (int)f) break;
f = f + 1;
Z = Z / 2;
if (Z == 0) goto LAB_00101c10;
iVar6 = iVar6 / 2;
pbVar14 = G;
}
f = z;
G = G + 1;
pq = 4;
Z = Z / 2;
if (Z == 0) break;
iVar6 = iVar6 / 2;
pbVar14 = G;
}
LAB_00101c10:
Z = k;
pbVar14 = G;
if (k != 0) {
iVar6 = 0x101;
while( true ) {
while( true ) {
bVar15 = *G;
if (f == 0) {
bVar15 = 0;
}
*G = bVar15 | (byte)(iVar6 % 2 << ((byte)f & 0x1f));
pq = 2;
if (6 < (int)f) break;
f = f + 1;
Z = Z / 2;
pbVar14 = G;
if (Z == 0) goto LAB_00101b40;
iVar6 = iVar6 / 2;
}
f = z;
G = G + 1;
pq = 4;
Z = Z / 2;
pbVar14 = G;
if (Z == 0) break;
iVar6 = iVar6 / 2;
}
}
}
}
LAB_00101b40:
G = pbVar14 + 1;
while( true ) {
lVar9 = (long)G - (long)r;
if ((long)X < (long)G - (long)r) {
lVar9 = (long)X;
}
k = (int)lVar9;
putc(k,stdout);
if (k == 0) break;
do {
k = k + -1;
bVar15 = *r;
r = r + 1;
putc((int)(char)bVar15,stdout);
} while (k != 0);
}
j = j + 1;
k = 0;
} while( true );
}the initial loop reads the input file and stores it in a buffer array. that’s followed by a series of putc() calls which write into the output file line 89-85. first few lines write “GIF89a” which is a standard GIF header signature. the last two lines are height and width of the GIF. line 109-114 looks like a simple palete implementation which is necessary for any gif encoding. line 121 pushes 0x3b which is GIF file terminator. the code looks like a simple GIF encoder which takes in an input file and generates a GIF.