May 2012 Archives

We got a file called PPC300 and told to get 10000 points. File is a "Snake" video game written in Python and packet to exe using py2exe. Ofcourse you can play a game for fun and get a flag(or, oops - you can't, read ahead), but that's a good case for imporoving your Python bytecode reversing skills.
First, we extract bytecode from exe using py2exe extract.
Then try to disassemble it using dis, not so readable, but we can see the constants:
'Snake   Score:%s'    #caption template
100                   #points, incremented per eaten food
2000                  #speed tick increment
First idea was to patch a points incrementor constant and get a flag, it didn't work.
I decided to decompile bytecode to a human readable form, tried Uncompyle, then Decompyle with my patch, both didn't work, but decompilation errors were in different places.
I fixed the decompyle using some parser code pieces from Uncompyle and my patch (decompyle.patch) and got a readable source code (snake.p_).
As we can see - flag is generated from "fps" value, not from points. But the original source code has a mistake and you can't get a flag even if you get enough points: "f" is called with 1 parameter, but requires 2:
def f(fps, control):
    x = ((dumps(color) + dumps(control)) + dumps(fontsize))
    t = ''
    for i in x:
        t += hex((ord(i) ^ fps))[2:]

    return t

...
while mainloop:
...
    tickFPS = Clock.tick(fps)
    if (fps >= 101):
        pygame.display.set_caption(f(fps))
        screen.fill((0, 0, 0))
        tickFPS = Clock.tick(2000)

I made a script for flag generation and reported the mistake to authors.
from cPickle import dumps

color = (32, 32, 32)
fontsize = 35
fps = 1
control = 251

def f(fps, control):
    x = ((dumps(color) + dumps(control)) + dumps(fontsize))
    t = ''
    for i in x:
        t += hex((ord(i) ^ fps))[2:]

    return t


if __name__ == '__main__':
    while True:
        fps += 1
        control -= 1
        if (fps >= 101):
            print f(fps, control)
            break
Flag: 4d2c56576f2c56576f2c56576f1115546f4b2c5450546f4b2c56506f4b
IDA represents stack frame as a structure. So you need to convert frame offset to structure offset and call SetMemberName.

The following code snapshot demonstrates that.
# find next "mov xxx, eax" instruction
def get_next_eax_store(ea):
	temp_ea = ea
	max_steps = 10
	for step in range(0, max_steps):
		temp_ea = idc.NextHead(temp_ea) # get next instruction
		idaapi.decode_insn(temp_ea)     #decode it
		if idaapi.cmd.itype == idaapi.NN_mov and idaapi.cmd.Op2.type == idaapi.o_reg:
			return idaapi.cmd, temp_ea
			break
	return 0, temp_ea
...
cmd, addr = get_next_eax_store(ea)
if cmd != 0 and cmd.Op1.type == idaapi.o_phrase: # take only "mov [ebp-xxx],eax" instructions
	sid = idc.GetFrame(ea)
	if sid != None:
		offset = idc.GetFrameLvarSize(ea) - (~(int(cmd.Op1.addr) - 1) & 0xFFFFFFFF)
		idc.SetMemberName(sid, offset, "NewLocalName")

The salt is
offset = GetFrameLvarSize(ea) - (~(int(cmd.Op1.addr) - 1) & 0xFFFFFFFF)
The cmd.Op1.addr (from command "mov [ebp-0x14]") contains -0x14, or 0xFFFFFFEC. Minus 1, then invert converts complement code to true form, & 0xFFFFFFFF is python trick to get valid positive number. So it produces 0x14 for initial value -0x14. Then subtract it from size of local vars, and get a structure offset which can be use as SetMemberName argument.
It was real "structured programming" task. Code contains lots of function, but there no direct xrefs. Control is passed from function to function via pointers in structs.

Typical struct has the following format:
    +0: function pointer
    +4: function arguments
    ...
Each structure has a helper procedure that allow to "execute" struct, i.e. call function with specified parameters. For example,
int exec_struct6_runtime_if_2(struc_6 *pStruct)
{
    return pStruct->proc(pStruct->bWhatChar,
        pStruct->piResult,
        pStruct->chBrainfuckChar,
        pStruct->TrueProc,
        pStruct->FalseProc);
}
There was 6 different structs that are correspond to input sequence, brainfuck sequence, and control flow management.

File has simple anti-debug (ptrace) trick, and two brainfuck-like sequences (for debug and non-debug sessions). A program interprets brainfuck sequence and modifies only one memory cell: + increments it, > multiplies it by 2, < divides it by 2. Each non-control (+<>) symbol forces interpreter to check user input sequence: current symbol from user input is XOR'ed with values from memory cell. If the result is equal to 'a', then loop is continued and memory cell is cleared. Else, interpreter stops and reports error.

The following code snapshot decodes the flag:
int result = 0;
int counter = 0;

for (int i = 1; i < bf_len; i++)
{
    char c = bf[i];
    if ((c & 0x80) != 0)
        c = ~c;

    if (c == '+')
        result++;
    else if (c == '>')
        result >>= 1;
    else if (c == '<')
        result *= 2;
    else
    {
        pass[counter] = 'a' ^ result;
        result = 0;
        counter++;
    }
}
Without a debugger we get the following: ar3n't_funct10n_p01nt3rs_fun?

Under debugger output is different: d3bugg3rs_ar3_just_t00_us3ful
Obfuscated x64 binary. Very small amount of code is split into chunks that are linked together via jmp | push + jmp and "high-level" constructions like this:
mov rdx, offset InputData
push offset trim_password
mov rcx, offset _strlen
jmp @jmp_via_ecx
...
@jmp_via_ecx:
mov rdi, rdx
push ecx
jmp do_ret

@do_ret:
ret
Bring things together:
mov rdx, offset InputData
push offset trim_password ; retaddr
mov rcx, offset _strlen   ; destination
mov rdi, rdx
jmp ecx
Or, more clear
mov rdi, offset InputData
push offset trim_password ; retaddr
jmp qword ptr [_strlen]   ; destination
After de-obfuscation, code becomes more readable.

First, seed value is calculated: seed = 940903 * 659 * -3;

Then srand(seed) is called, and console is read using fgets(InputData, 80, STDIN). Each symbol from user input (InputData array) is compared with an one symbol of the Key sequence:
Rh|hx6VPJXwt7b%YUh3|ku9!Nnl#p-B*fjKUrA:r83Alg3KMjoiWA%P-8Xo5R%^:
Position is determined by rand() calling. In other words,
InputData[i] == Key[rand() % 64] ? continue : exit()
After full comparison, length of InputData is considered, and successful result is only possible if length of InputData is 64.

The following code snapshot allow to encode the flag:
char input[] = "Rh|hx6VPJXwt7b%YUh3|ku9!Nnl#p-B*fjKUrA:r83Alg3KMjoiWA%P-8Xo5R%^:";
char result[sizeof(input)];

int seed = 940903 * 659 * -3;
srand(seed);

for (int i = 0; i < sizeof(input) - 1; i++)
{
    result[i] = input[rand() % 64];
}
result[sizeof(input) - 1] = '\0';
printf("%s\n", result);
Output: Bg8Ph#xnr||l*YjV|9K#RRfh6XhnhK8*%f:h5AAUgg%t5K3%xRnR%Xh|iU#W6h3k

From the name of the task and the description we guess that it is the data from a card reader similar to the "Square".

The technical details about encoding are desribed here: http://stripesnoop.sourceforge.net/devel/phrack37.txt

In short: Magnetic stripe + Aiken Biphase encoding + ANSI BCD encoding = key

The game is very simple. 2 long strings are shown, you have to select which one is bigger. Short analysis of the strings didn't show any anomalities, so we just decided to collect all the string and remember their positions against each other. So we write an automated player. There are only 500 strings. A naive sorting approach will give us 500*499 comparisons, so we just left it as is for a couple of hours. You can definitely afford it during the 48hrs competition.

About this Archive

This page is an archive of entries from May 2012 listed from newest to oldest.

April 2012 is the previous archive.

December 2012 is the next archive.

Find recent content on the main index or look in the archives to find all content.