Sunday, 4 January 2015

Hackvent 2014 CTF Writeup

Hi everyone! Here is my full writeup for the Hackvent CTF organized by Hacking-Lab crew that happened during December, 2014. It was pretty fun =)

Day 1

Recon challenge
@hackvent was given. This is a Twitter's nickname. We could search for it it would give us Santa Claus:
https://twitter.com/hackvent
There's a post saying:
Here we go for the first day: https://tr.im/a1188
Following the link take us to a tricked image: http://hackvent.hacking-lab.com/images/tricked.png
Taking a closer look at the path taken by the browser using a proxy tool like Burp, we see that the link makes the following jumps:
- https://t.co/PJ7N4VjWaN
- https://tr.im/a1188
- http://hackvent.hacking-lab.com/ch01.php
- http://bit.ly/1y04yV4
- http://hackvent.hacking-lab.com/images/tricked.png

Bitly, like most of the shortener sites, contains a feature that allows one to check which is the destination URL of a shortened link by using a '+' sign after the URL, like: http://bit.ly/1y04yV4+

This takes us to a bitly page where we can see the final destination URL of our link and we also can see which user created it.
In this case: hackvent

Clicking on the user takes us to the user gallery where we find our flag image.http://hackvent.hacking-lab.com/images/ChiefOfShortener.png

Day 2:

Back in time - knowledge

The string aHR0cDovL2hhY2t2ZW50Lm9yZy8= was given. this is obviously a base64 string. translates to http://hackvent.org/
accessing the site tell us that "try to build a time machine and watch the past".

So we need to use web.archive.org to see how the page looked like in the past.

Browsing to the page on 26th of November show us the same page content, except the image is not there anymore.
It has been replaced by another one: Ball_of-Santa.png

So we test to open the image on the current site and it gives us the flag image:
http://hackvent.org/Ball_of-Santa.png



Day 3:

Candle 1 - really old school

They give us the image candle with 8 sides

and the cipher: WAIYTELZEREMSOK3TEBZWETUE2EB2HIRMRYGBUGKSRASIIXGQ5HYSESYWCZDYCACBS43JIOHNRAA3DAURDES2DGO

The image of the multi-sided candle is an allusion to the Scytale cipher, which consisted on writing a message on a piece of paper wrapped around a multi sided wooden stick in a spiral.
The only problem is that they made it a bit harder here because to solve this cipher we must use 10 sides and not 8.

To solve it we just need to break the string in 10 byte words and read the plaintext in the vertical:

W A I Y T E L Z E R
E M S O K 3 T E B Z
W E T U E 2 E B 2 H
I R M R Y G B U G K
S R A S I I X G Q 5
H Y S E S Y W C Z D
Y C A C B S 4 3 J I
O H N R A A 3 D A
U R D E S 2 D G O

so by reading vertically, we get:

WE WISH YOU A MERRY CHRISTMAS AND YOUR SECRET KEY IS BASE32 GIYSA2LTEBXW43DZEBUGC3DGEB2GQZJAORZHK5DI

Decoding the Base32 string we get our flag: '21 is only half the truth'


Day 4:

Crypto 1 - or what else?

This one is quite easy. It's an image representing Breille language.
So decoding it we get: "in the land of the blind, the one-eyed man is king"

It's our flag


Day 5:

Math Basics
for everyone, even for your granny
The following image was given:
 
After googling some of the terms we see that these are hidden "easter eggs" present on Google.
However we need to do some calculations with them.
While researching, I've found an interesting chapter on wikipedia (http://en.wikipedia.org/wiki/List_of_Google_hoaxes_and_easter_eggs#Easter_eggs) where it says "The Calculator recognizes a number of strings as numbers. They can be entered by themselves or used in expressions. They must be entered without quotation marks. When used in an expression, the phrases must be entered in lowercase."

It means that we have to perform the complete calculation using Google calculator (which is just the search field)

Solution:
(half megasecond) squared squared * ((bakers dozen * donkeypower * once in a blue moon / the answer to the ultimate question of life, the universe, and everything) / (beard second squared * earth mass))

Google returns: 378 063 953

So our flag is 378063953


Day 6:

Do you speek 1337?
well, my native language is this

But be careful, this can lead to headache!

The following text was shown:

-[--->+<]>-.[---->+++++<]>-.---.+++++++++++++.-------------.--[--->+<]>-.[->+++<]>+.--[--->+<]>---.-------------.--[--->+<]>-.+++++[->+++<]>.-.--.-[--->+<]>.-[---->+<]>++.[-->+++<]>+.-.[--->++<]>.---[->++++<]>.+++++.---------.-----------.[--->+<]>----.+[---->+<]>+++.+++++[->+++<]>.---------.[--->+<]>--.[-->+++++++<]>.-----------.++++++++++.+.----.-------.--[--->+<]>-.-[--->++<]>-.+++++.-[->+++++<]>-.---[->++++<]>.------------.---.--[--->+<]>-.--[->++++<]>-.--------.+++.------.--------.[----->+++<]>--.[-->+<]>+++.>-[--->+<]>-.[---->+++++<]>-.+++++++.++++.++++[->+++<]>.--[--->+<]>-.--[->++++<]>-.+[->+++<]>.+++++++.[--->+<]>-----.---[->++++<]>+.-------.----------.+.+++++++++++++.+.+.+[->+++<]>++.+++++++++++++.----------.-[--->+<]>-.[->+++<]>++.+++++++.+++++.-------------.--[--->+<]>---.+++++++.-[-->+++++<]>.------------.[->+++<]>+.+++++++++++++.----------.-[--->+<]>-.---[->++++<]>.------------.+++++++.++++.++++[->+++<]>.--[--->+<]>-.--[->++++<]>-.+[->+++<]>.+++++++.[--->+<]>-----.+[->+++<]>+.+++++++++++.-.[++>---<]>++.[->+++<]>-.

The hints were "language" and "headache". That's referring to the programming language called brainfuck.

Searching in Google for "wierd programming languages" or "bizzare programming languages" would lead to pages describing Brainfuck.
With this information, we could simply decode the string using an online tool like http://gc.de/gc/brainfuck/

The string translate to "There are only 10 types of people in the world: Those who understand binary, and those who don't" which is our flag.


Day 7:

Can you c?
but to see is not to scan


 
The image can be easily identified as a stereogram. The issue was to extract the hidden image within.Using the crossview technique we could understand that the hidden image was a QR code (also by the filename "3DQR.png").

To see the hidden stereo image, one could use the following steps:
- open the image on a image editor (a decent one... i used Fireworks)
- duplicate the layer
- reduce the opacity a bit
- set the blending mode to Difference (XOR works even better)
- move the duplicated layer left or right until we see the QR code

Once the QR code is cleary visible we can optimize it for software scanning:
- Merge down the layer
- increase the contrast to the max
- increase the brightness a bit (+30)
- reduce saturation to the minimum


This should give us a clear black QR code over a noisy white background that's easy to scan by any software.


Day 8:

A Pearl White Candle

                                  (
                     (       (
                           (         (
                (    (         (            (
                                       (
             (      (       (    (          (    (
                             ''
                )      )      )))   )    )     )   )
           )                  )))))
                  )          =~('(?{'           .
                       (    '`'|'%').   (    (
                (           '['))^'-'
                             ).('`'  |   '!')
                      .    (    (     '`')|','
                                )  .('"\\$').(
                             '`'|'!').'=<>;'.
                        ('`'|'#').('`'|"\(").(
                    '`'|'/').('`'|'-').(('[')^
                    '+').('{'^'[').'\\$'.('`'|
                    '!').';\\$'.('`'|'!').'='
                    .'~'.('['^'/').('['^')').
                    '/'.('`'^'!').'-'.('{'^'!'
                   ).('{'^'[').('`'|'!').'-'.(
                  '['^'!').'/\\"-;'.('`'^'.').
                  '-'.('{'^'!').('`'^('!')).
                  '-'.('`'^"\-").'/;((\\$'.(
                     '`'|'!').('{'^'[').('`'
                     |'%').('['^'*').(('{')^
                     '[')."'".('^'^('`'|'.')
                     ).('`'^'"').('{'^'!').(
                     '`'^'-').('`'^'.').('`'
                     ^'$').('{'^'(').(('`')^
                     '&').('{'^'!').('`'^'.'
                     ).('{'^'*').('`'^'/').(
                     '`'^'"').('`'^'.').('`'
   ^                 '$').('`'^'/').('`'^'&'
).(('`')^            "'").('{'^'(').('`'^'.'        )     .
  ('^'^('`'|'/'      )).('{'^'(').('`'^'&').  (    '{'^"\!").
   '!'."\'".')&&('.( '['^'+').('['^')').('`'   |')').('`'|'.').(
   ((   '['))^'/').('{'^'[').'\\"'.('['^')')  .('`'|')').('`'|"'")
         .(  '`'|'(').('['^'/').'\\\\'.('`'|'.').'\\"));'.('!'^
               '+')   .'"})');$:='.'^'~';$~ =(   (     (     (
                (         '@')))))|'('
                      ;$^=')'^'[';$/
                  ='`'|'.';$,=('(')^
                '}';$\='`'|'!';#;#
                  ;#;  #;#   ;#;

It's easy to guess that this is a Perl script because they say "Pearl".
To deobfuscate it we could do:

$ perl -MO=Deparse original.pl | perltidy > deobfuscated.pl

doing a cat on the deobfuscated file return this:

'' =~
m[(?{eval"\$a=<>;chomp \$a;\$a=~tr/A-Z a-z/\"-;N-ZA-M/;((\$a eq '0BZMNDSFZNQOBNDOFGSN1SFZ!')&&(print \"right\\n\"));\n"})];
$: = 'P';
$~ = 'h';
$^ = 'r';
$/ = 'n';
$, = 'U';
$\ = 'a';

so the program is loading a file and each line gets a modification that seems like Rot13 but not quite:
$a=~tr/A-Z a-z/\"-;N-ZA-M/;

Then the modified line is compared to the string '0BZMNDSFZNQOBNDOFGSN1SFZ!'. If it matches it returns 'right'.

So we have to build a script that reverts the operation by brute-forcing the whole ASCII range.

Here's the decoding perl script:

@cipher = (split //, '0BZMNDSFZNQOBNDOFGSN1SFZ!');
@res;

for($x=0; $x<scalar @cipher; $x++) {
    print "Testing char $cipher[$x]...  ";
    for $y (32..122){
        $asciichar = chr($y);
        next if($asciichar=~/\d+/); # --> prevent bugs in the decoding
        $test = $asciichar;
        $test =~ tr/A-Z a-z/"-;N-ZA-M/;   
        if($cipher[$x] eq $test ){
            print "Got char for $cipher[$x] - $asciichar \n";
            $res[$x]=$asciichar;
            last;
        }
    }
}

print "CIPHER: 0BZMNDSFZNQOBNDOFGSN1SFZ!\n";
print "PLAIN:  ".join("", @res)."\n";


print "CIPHER: 0BZMNDSFZNQOBNDOFGSN1SFZ!\n";
print "PLAIN: ".join("", @res)."\n";

This produces the following output:

root@henshin:/tmp# perl new.pl
Testing char 0...  Got char for 0 - O
Testing char B...  Got char for B - n
Testing char Z...  Got char for Z - l
Testing char M...  Got char for M - y
Testing char N...  Got char for N -  
Testing char D...  Got char for D - p
Testing char S...  Got char for S - e
Testing char F...  Got char for F - r
Testing char Z...  Got char for Z - l
Testing char N...  Got char for N -  
Testing char Q...  Got char for Q - c
Testing char O...  Got char for O - a
Testing char B...  Got char for B - n
Testing char N...  Got char for N -  
Testing char D...  Got char for D - p
Testing char O...  Got char for O - a
Testing char F...  Got char for F - r
Testing char G...  Got char for G - s
Testing char S...  Got char for S - e
Testing char N...  Got char for N -  
Testing char 1...  Got char for 1 - P
Testing char S...  Got char for S - e
Testing char F...  Got char for F - r
Testing char Z...  Got char for Z - l
Testing char !...  Got char for ! - !
CIPHER: 0BZMNDSFZNQOBNDOFGSN1SFZ!
PLAIN:  Only perl can parse Perl!

And we get our flag to the QR code!

After a bit more analysis, it's easy to understand that since it's a reversible cipher, we could just inverse the translation parameters:

root@henshin:~# echo '0BZMNDSFZNQOBNDOFGSN1SFZ!' | tr '"-;N-ZA-M' 'A-Z a-z'
Only perl can parse Perl!


Day 9:

iPhone forensics
reveal the message

Get your daily work: http://hackvent.hacking-lab.com/iPh0n3

We start by analyzing the file type:

root@henshin:/tmp/crypto# file iPh0n3
iPh0n3: SQLite 3.x database

Its an iPhone SMS.db file.

Opening the file in a SQLite browser, we find an SMS message '==Nn0EUp68lYbS2LeMKMhEaYbS2Leyzoa1PouWzYw9JoiRQJFS3qT10IIuxY3Szq' with a handleID +4179666ROT13

So the string looks like a reversed base64 because it starts with ==. Reversing it and decoding the base64 doesn't show anything useful.
So we try to apply the ROT13 decoding to the string without "=="
Returns: Aa0RHc68yLoF2YrZXZuRnLoF2Yrlmbn1CbhJmLj9WbvEDWSF3dG10VVhkL3Fmd

If we try base64 it still doesn't show anything useful, but reversing it again we get:
dmF3LkhVV01Gd3FSWDEvbW9jLmJhbC1nbmlrY2FoLnRuZXZrY2FoLy86cHR0aA

Decoding this in base64 shows plaintext, so we decode the whole string with the == in the final like this: dmF3LkhVV01Gd3FSWDEvbW9jLmJhbC1nbmlrY2FoLnRuZXZrY2FoLy86cHR0aA== and we get:

vaw.HUWMFwqRX1/moc.bal-gnikcah.tnevkcah//:ptth

its easy to see that this is an http address reversed. We get the normal text:
http://hackvent.hacking-lab.com/1XRqwFMWUH.wav

Downloading this file shows that it's an audio wave with DMTF dial tones in it.

We could use an online tool like http://dialabc.com/sound/detect/index.html to get the dials pressed. The site returns that the dials were:
66#97#122#33#110#103#97

Interesting. All the decimal values fit in the ASCII printable range so we decode the chars and we get:
Baz!nga

This is our flag.


Day 10:

SQL Hero

the oracle says: just another language

Based on this table (hlscore):
RANK POINTS TEAM        NICK        AVATAR
==== ====== =========== =========== ======
1    2619   Ukraine     solarwind   Crack
2    2610   Switzerland HaRdLoCk    Crack
3    2400   Switzerland M.          Chief
4    2270   Switzerland PS          Chief
5    2178   Spain       tunelko     Chief
6    2030   Switzerland DanMcFly    Chief
7    1054   Austria     Mister004   Chief
8    1028   Austria     woody       Chief
9    941    Italy       scegliau    Geek
10   848    Austria     sebl314     Geek
11   837    Austria     TomCat435   Geek
12   814    Austria     nufan       Geek
13   808    Austria     WANeKO3     Geek
14   720    Austria     Manio       Geek
15   712    Switzerland Leskat      Geek
16   706    Ukraine     LeoStep     Geek
17   702    Austria     chris.pcguy Geek
18   691    Switzerland bias        Geek
19   662    Germany     nks         Geek
20   627    Switzerland d0h         Geek

Run this to get the daily code:
select ( select translate('&1','abcdefghijklmnopqrstuvwxyz','MEZ4VPG5NS6RYH7CT1QW2FO3AI') from dual ) || '-' || ( select case when points > 1000 then points + 1000 else points + 2500 end from hlscore where utl_raw.cast_to_raw(dbms_obfuscation_toolkit.md5(input_string => NICK)) = 'C6DEE681E936AF6577387507603A7539') || '-' || ( select substr(sum(level*level),3,4) from dual connect by level <= 1337 ) || '-' || ( select substr(rawtohex(utl_raw.cast_to_raw(dbms_obfuscation_toolkit.md5(input_string => '&1'))), -14, 4) from dual) || '-' || ( select sum(y+cnt*cnt) from ( select count(*) cnt, team, mod(trunc(x),987) y from ( select team, avg(points) over(partition by team) x from hlscore ) group by team,x having count(*) > 2)) || '-' || ( select sum(points) from hlscore start with rank=15 connect by rank= prior rank-3 ) solution_code from dual  

So this challenge is all about reversing this SQL Query.

We can format it a little better like this:

select
(select translate('&1','abcdefghijklmnopqrstuvwxyz','MEZ4VPG5NS6RYH7CT1QW2FO3AI') from dual ) ||
'-' || ( select case
when points > 1000 then
points + 1000
else
points + 2500
end
from hlscore where utl_raw.cast_to_raw(dbms_obfuscation_toolkit.md5(input_string => NICK)) = 'C6DEE681E936AF6577387507603A7539') ||
'-' || ( select substr(sum(level*level),3,4) from dual connect by level <= 1337 ) ||
'-' || ( select substr(rawtohex(utl_raw.cast_to_raw(dbms_obfuscation_toolkit.md5(input_string => '&1'))), -14, 4) from dual) ||
'-' || ( select sum(y+cnt*cnt) from ( select count(*) cnt, team, mod(trunc(x),987) y from (
select team, avg(points) over(partition by team) x from hlscore ) group by team,x having count(*) > 2)) ||
'-' || ( select sum(points) from hlscore start with rank=15 connect by rank= prior rank-3 ) solution_code from dual  

Each part of the query returns a part of the TOKEN.

PART 1:
(select translate('&1','abcdefghijklmnopqrstuvwxyz','MEZ4VPG5NS6RYH7CT1QW2FO3AI') from dual )

The query is using &1 which is used in Oracle to represent user input and it's using a translate to transform the string in something else.
We know that the final token always starts with HV14- so we can reverse this translate query to give us the input:

(select translate('HV14','MEZ4VPG5NS6RYH7CT1QW2FO3AI','abcdefghijklmnopqrstuvwxyz') from dual )

The result is "nerd", so we have our input.

PART 2:

select case
when points > 1000 then
points + 1000
else
points + 2500
end
from hlscore where utl_raw.cast_to_raw(dbms_obfuscation_toolkit.md5(input_string => NICK)) = 'C6DEE681E936AF6577387507603A7539'

Result: 3212

PART 2:
Can be retrieved by simply running the query as is:
select substr(sum(level*level),3,4) from dual connect by level <= 1337

Result: 7553

PART 3:
select substr(rawtohex(utl_raw.cast_to_raw(dbms_obfuscation_toolkit.md5(input_string => '&1'))), -14, 4) from dual
It uses &1 which implies that this is user input.
We see that it's using the input and hashing it in MD5 and getting the 4 digit value from the 14 last chars of the MD5

After breaking PART1 we know that the input is 'nerd', so we can run the query with that instead of &1 and it should give us the wanted token part

select substr(rawtohex(utl_raw.cast_to_raw(dbms_obfuscation_toolkit.md5(input_string => 'nerd'))), -14, 4) from dual

Result: BCF8

PART 4:
Can be retrieved by simply running the query as is:
select sum(y+cnt*cnt) from ( select count(*) cnt, team, mod(trunc(x),987) y from (
select team, avg(points) over(partition by team) x from hlscore ) group by team,x having count(*) > 2)

Result: 1597

PART 5:
Can be retrieved by simply running the query as is:
select sum(points) from hlscore start with rank=15 connect by rank= prior rank-3

Result: 6897


So, joining all the parts, we can get our token: HV14-3212-7553-BCF8-1597-6897


Day 11:

Good Old times
back in time, once again

http://hackvent.hacking-lab.com/GoodOldTimes.exe

Analyzing the file, we see that's a Windows PE file. Looking inside with an hex editor we find the message: '!Keep on trying till you run out of cake!'
Attempting to run the file just shows the following message


Using the hints "old times" and search in Google for "run executable old times". It will show some results related to DOSBox, an application that emulates the old DOS operative system.

After downloading and installing DOSBox, we mount the C drive and execute the file:


And we got our flag "I'm Still Alive"

Searching the 'Keep on trying till you run out of cake' phrase on Google it will show that its part of the lyrics for a Portal song called 'Still Alive'



Day 12:

Another oracle says

wrap it up!


John Scriptkiddy has created an awesome crypttool to store his passwords very securely on the database.
Unfortunately, a few days later, he recognized that his script is really awesome: He isn't capable to decrypt his own password!

Can you help him to reveal his original password from this stored cipher:
617B7E0A0870637F710E42B44A3B0647433442441B4E4F1D4B471F29475C5D62


He remembers only that it is an easy-to-remember phrase, well apparently not that easy ...

His awesome script is also available here: http://hackvent.hacking-lab.com/AwesomeCryptTool.pls

We start by checking the pls script. Downloading and opening the file shows this:

CREATE OR REPLACE PACKAGE HACKvent wrapped
a000000
b2
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
9
9e aa
WvN7G0Z97FJZxrcs6FN4mZS8BegwgwL/f54VaS9GOPauf2NhYSanELc0bMJC2BOPRH472079
5mfaCjcsInUbe5dndzKa0MGZwNrhc4rs3619e5RBRwVcR7f+NkctWhGNU27zR628a2pk0WgN
owpf8LWz0sIQk30xpCJbEj5a

/
CREATE OR REPLACE PACKAGE BODY HACKvent wrapped
a000000
b2
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
b
372 270
BwGVYsJ7/qYMVlBQVrtLCEIPNvowg41eLiAFfHRVPfmUNJX8eu+9Swzwy8hsbG/gDDciws6O
jlQ30tMfTDw4Z6rkWtoux1Rt/990+PfPBoxBPHHYzgk1AQFbKvl6VaRKDhDsG20RolA8qWUV
8o3eA0hT3zV5HREd/bmi11VuN16oReqp5ftkjfyHS37fkGVTvDf6Gnbg3Dr+4AN41rp8LTuJ
2Yt+NkUMyiZ3Cf2KAAjlzGapA7OFWSs7mq1IGnltsiBR5oPPIgF0MjZtbpkXusj3eEOqp5+c
Y1QM7C0FBKtWkofnuWrRVJIcWH4N44e4q9UGYZMpaaCb1dffQJAo3BNsaM/WzVzGaSjM0dgd
Lh1PlOmMR2V3nNqvDi2f8N76fN9xunfRhocRkDpUqIYBn+JOiAtPKtbBwTj/GuqIrch04REL
yBQuEWGWZcWkn7oewvMu+WNKhVT53OHcQMwTSVxJcnCIYgxiX9HV+7+B5G5iFj36rOZk9kMi
iq+rMk+vr1ld7AMMQHy+Crn+MMG4aJq1RgcFxu/kKaqv+TMpy0oA5H1rJC+b53O7HYkPtBrP
PWcUjB8I6fLUycyh7Boa1nx7o/0C9E/54UwY6yM=

/

The hint "wrap" makes sense now. These are wrapped Oracle procedures.

Using this site http://www.codecrete.net/UnwrapIt/ we get the unwrapped version of each procedure:

PACKAGE HACKvent AS
  FUNCTION ENCODE(INPLAINTEXT  IN VARCHAR2) RETURN VARCHAR2;
  FUNCTION DECODE(INCIPHERTEXT IN VARCHAR2) RETURN VARCHAR2;
END HACKVENT;

PACKAGE BODY HACKvent IS
    FUNCTION ENCODE(INPLAINTEXT IN VARCHAR2) RETURN VARCHAR2 IS
    KEY VARCHAR(100) := '';
    RES VARCHAR(100) := '';
    RES1 VARCHAR(100) := '';
    X NUMBER(2);
    Y NUMBER(2);
BEGIN
SELECT ROUND(DBMS_RANDOM.VALUE(10,20)) INTO X FROM DUAL;
SELECT ROUND(DBMS_RANDOM.VALUE(10,20)) INTO Y FROM DUAL;
KEY := UTL_RAW.CAST_TO_RAW(DBMS_OBFUSCATION_TOOLKIT.MD5(INPUT_STRING => X*Y));
FOR I IN 1..LENGTH(INPLAINTEXT) LOOP
RES1 := CHR(ASCII(SUBSTR(INPLAINTEXT,I,1))+I) || RES1;
END LOOP;
RES :=
   UTL_RAW.BIT_XOR(
    UTL_RAW.CAST_TO_RAW(RES1),
    UTL_RAW.CAST_TO_RAW(KEY)
);
RETURN RES;
END ENCODE;

FUNCTION DECODE(INCIPHERTEXT IN VARCHAR2) RETURN VARCHAR2 IS
    HINT VARCHAR2(100);
  • BEGIN
HINT := 'Hohoho, this part you have to do on your own ;-)';
RETURN HINT;
  • END DECODE;
END HACKVENT;


So the decoder is what we need to do.
Spending some time analyzing the code, we see that the encoding program generates 2 random numbers from 10 to 20, multiplies them and use that as input to a MD5 hash algorithm.
The hash is then XORed with the INPLAINTEXT parameter after a letter scrambling operation.

The algorithm is easy to understand, we just need to find the right key (the random numbers X and Y).
So I've scripted a Perl program to reverse those steps bruteforcing the two random numbers:

#!/usr/bin/perl

use Digest::MD5 qw(md5 md5_hex md5_base64);
print "\nHenshin's Hackvent D12 Cracker!!\n\n";

my $target = '617B7E0A0870637F710E42B44A3B0647433442441B4E4F1D4B471F29475C5D62';

#decode brute force
for my $x (10..20){
        for my $y (10..20){
                my $key = uc(md5_hex($x*$y));
                my $cipher = pack('H*',$target) ^ $key;
                $cipher =~ s/\x00//g;
                $res="";
                for (my $i=0; $i<length($cipher); $i++){
                        my $char = substr($cipher,$i,1);
                        $res = chr(ord($char)-(length($cipher)-$i)) . $res;
                }
                next if ($res =~ /[\x00-\x1F\x7F-\xFF]+/); # only report when the answer is printable ascii
                print "Found possible value!!\nX=$x;Y=$y\nCIPHER: ".unpack('H*',$cipher)."\n";
                print "PLAINTEXT: '$res'\n\n";
        }
}

And run it:

root@henshin:~/hackvent# ./plsql-crypto.pl

Henshin's Hackvent D12 Cracker!!

KEY: 4645313331443746354136423338423233434339363733313643313344414532
Found possible value!!
X=13;Y=17
CIPHER: 514d4e4b4c49514b49377b807d7f327673707a7d2c7a78297b70266a76686a55
PLAINTEXT: 'There is no place like 127.0.0.1'

Found possible value!!
X=17;Y=13
CIPHER: 514d4e4b4c49514b49377b807d7f327673707a7d2c7a78297b70266a76686a55
PLAINTEXT: 'There is no place like 127.0.0.1'

And we got our flag 'There is no place like 127.0.0.1'


Day 13:

Contact

from outer space
We have received a disturbing message from space. Can you understand what they are telling us?

The image has a noticeable last row with artifacts.
We cut the last row of pixels and analyze it in detail.
First option seemed to be a bar code, so I've created a new image with a higher height that looked like this:

However attempting to read it failed, so the next thing was to see if it was a binary pattern.
Using a script in python to check the color components of each pixel, we can read it in binary:

Script:

#!/usr/bin/python

import Image
import sys

imagefile=sys.argv[1]

print "Loading image..."
im = Image.open(imagefile)
pix = im.load()

print "Image OK! Size: " + `im.size[0]` + "x"+`im.size[1]`+"\nProcessing...\n"

dump = ""
height = 1
for width in range(im.size[0]):
    comps = pix[width,height]
    if(comps[0]==0):
        sys.stdout.write('1')
    else:
        sys.stdout.write('0')


print "\n"

Running:

root@kali-ts:~/hackvent/day13# ./hackvent_d13.py barcode.png
Loading image...
Image OK! Size: 368x300
Processing...

01101000011101000111010001110000001110100010111100101111011010000110000101100011011010110111011001100101011011100111010000101110011010000110000101100011011010110110100101101110011001110010110101101100011000010110001000101110011000110110111101101101001011110011010101110110010100000100101101000111010110010011000001110100011001000101100000101110011011010111000000110011


which translates to:

http://hackvent.hacking-lab.com/5vPKGY0tdX.mp3

After downloading the file, I've opened it on WavePad and checked what was the sound.
At first it seemed to be Morse code but attempting to read it didn't produced good results.
So maybe it was binary again? I started to write down the codes by hand while listening to the sound, but after a while I always started to mix digits up and had to restart.
So I've found a much nicer way of extracting the information. By converting the sampling rate to 6000 Hz, the tones became very easy to distinguish visually:


So now we have it.. the bigger bars are zeros and the smaller bars are ones.

This resulted in the following bitstring:

011000010110110001101001011001010110111001101000011000010110001101101011011000010111010001110100011000010110001101101011

which when converting to base 256 translates to 'allhackattack', our flag



Day 14:

Lunch time

today: crackers


Get your lunch here:  http://hackvent.hacking-lab.com/CrackBall.zip

The file is a zip password protected file. Dictionary attacks didn't work against it.

The image of the cracker had an Exif comment which displayed "5 x lowerleet". This is a pretty good hint, meaning that the password is probably 5 chars long and is composed of lower case letters and numbers (l33tsp34k style).

So using any cracking software configured with these settings, it was possible to retrieve the password quickly using brute force.
The password is "u2z1p"


With the password we can now extract the Ball.png image and scan it:

root@henshin:~/hackvent# 7z e -pu2z1p CrackBall.zip

7-Zip [64] 9.20  Copyright (c) 1999-2010 Igor Pavlov  2010-11-18
p7zip Version 9.20 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,1 CPU)

Processing archive: CrackBall.zip

Extracting  Ball.png

Everything is Ok

Size:       64345
Compressed: 64420

root@henshin:~/hackvent# zbarimg Ball.png
QR-Code:HV14-ctc2-jiPs-Y321-Pp9J-9Rxr
scanned 1 barcode symbols from 1 images in 0.07 seconds

And we got our token


Day 15:

Execute the inevitable

QzgwODAwMDBDNjQ1Rjg3N0IwMTMzNDdFODg0NUY5NkE2MTU5OEQ0NUY4ODNDMDAyODgwOEVCMDEzRjJCQzA2QTM5OEIwNDI0ODNDNDA0MDNDMDQwODNFMTAxODNDOTAxOEQ3REZCRjNBQTgzRTAwMTgzQzg1NTgzRjAzMzhENERGQzUxNUE4ODAyRUIwMTc1RTgwMDAwMDAwMDVCOEE1QkZBOEFDMzg2Qzg4ODRERkQ5MENDMzNDMDA1RDEwMDAwMDA4M0U4NjM1MDgzQzQwNDhBNUMyNEZDODg1REZFRkU0NUY4QzlDMw==

We know that this is a BASE64 string so we decode it and it gives us an ASCII hex string:

root@kali-ts:~/hackvent/day15# cat initial_base64.txt | base64 -d && echo ""
C8080000C645F877B013347E8845F96A61598D45F883C0028808EB013F2BC06A398B042483C40403C04083E10183C9018D7DFBF3AA83E00183C85583F0338D4DFC515A8802EB0175E8000000005B8A5BFA8AC386C8884DFD90CC33C005D100000083E8635083C4048A5C24FC885DFEFE45F8C9C3

Using an hex editor we put this ASCII hex inside and check what is it

root@kali-ts:~/hackvent/day15# file binary
binary: Applesoft BASIC program data

It could be, but this is actually a false positive. After spending some more time figuring out what this ASCII hex string really mean, the options reduced to just one... assembly code

So lets try to inject these hex bytes on the execution of any program and see what gives.

On immunity debugger, I opened the executable from day 11 (GoodOldTimes.exe), let it run until we reach the program entry point, and then replace the binary data:



By the time the program reaches the Leave instruction, I've checked all registers and ESP pointed to an address in memory which contained an interesting string:



ESP points to an address that contains the ascii 'xmasfun'

That's it! it's our flag


Day 16:

Christmas Present Safe  

don't unwrap presents early!

Get your present here: http://hackvent.hacking-lab.com/ChristmasPresentSafe.pyc

Our file is a compiled python script. So the first to do is to find a tool to decompile it. I used uncompyle2 (https://github.com/wibiti/uncompyle2) for this.

After having our source python, it was time to analyze the code:

#Embedded file name: ChristmasPresentSafe.py
import hashlib
from Crypto.Cipher import AES
from base64 import b64decode, b64encode

def fail():
    print 'Hey, wait for christmas.'
    exit(1)


print 'This present has a high-security lock to prevent any unauthorized unwrapping before christmas!'
try:
    pins = [ int(pin) for pin in raw_input('Enter your PIN codes: ').split(' ') ]
except:
    fail()

if len(pins) != 4:
    fail()
for pin in pins:
    if pin < 100000 or pin > 999999:
        fail()

if (pins[0] ^ pins[1]) + pins[2] != pins[3]:
    fail()
print "checkpoint 1!"
if pins[0] & 57005 | pins[1] >> 10 != 54399:
    fail()
print "checkpoint 2!"
if pins[3] + pins[2] - pins[0] ^ pins[1] != 702564:
    fail()
print "checkpoint 3!"
if (pins[1] * 23 - 1234567 ^ pins[3]) - 1199399 != 42:
    fail()
print "checkpoint 4!"
if pins[2] & pins[1] & pins[3] != 8448:
    fail()
print "checkpoint 5!"
if [ pin >> 16 for pin in pins ] != [3,
 1,
 6,
 8]:
    fail()
print "checkpoint 6!"
if pins[1] & 543210 != 18752:
    fail()
if (pins[0] - 23 ^ pins[3]) % pins[2] >> 8 != 1275:
    fail()
if pins[2] / 10000 != 42 or pins[2] % 100 != 42:
    fail()
if pins[3] | pins[0] != 783695:
    fail()
if [ pin & 3 for pin in pins ] != [2,
 1,
 2,
 1]:
    fail()
if pins[3] % pins[1] & pins[2] != 10544:
    fail()
if pins[0] ^ 12648430 | pins[1] != 12845045:
    fail()
if 507707 != (pins[1] | pins[2]) ^ pins[0] - 234242:
    fail()
if pins[1] & pins[2] != 30992:
    fail()
wrappedPresent = 'ru40F6WwRYUwa/uuwXmSoWV2jJottzDQUtlGcxmhY+TW4yKCRYiw/4hsMCdgFi4jFkuFoq6MsN424F6wlBbvbN1AHxTdFeM08+xWxgp/cw+pz+Pc1ZYmvuOyXNVtcHeuy9n+3lRMASCn2oozuMCqjqVqX4yoIUOCF5LjAlo1ugsvhLcnU7iFlbXgcsj9gZOydiPBfBWR9eB/tDmoh0hup9VBtCentvYBTnF1FE6VsXV1W/bh5Rx/lGUezAK4UI/v5UYtWH5Fq3a6bYDcP1m/KfcrFzwWrIlV/q+Kj12dwCDWSg9VYHfM9dqGIQCFx6CPoLXphwNYFGy+mtlVRUITcIeaDl6a8VsaBflN6/2onqsRJz7rJTbiTaG7jiJ4VBjSIX+FZrS50iO2DCqnwjCj4itovhMeO/s8xS/9Ka5BxXXZBYITLieIG0xrdQpPmIeSj+BGW7moUNXEQQXVbqqcj9SarYzyExUG/nTdPwa5HQLByoUwA3gmCALkE+JZnH8nBwtn8UkbUyJt0G+UOu7kqX1viPBbw7q8AD3dQBb0xUEW3nwon5RpYPfc5hUmBcsA3tMR9AFfwVL/O9Xkn+zhK6jkm4yFUeqjl75YB+m3J7RQjXCcHg0hOsQvozAZaXI7aPLSlL5aoxEP82ZYqUV5l+R8YUQd19XHzfSPyJggPSXQOt6psYwwbxQ1/XHgbq3iLAm+BRi0gVuPM7kZYdezZzw8sPWIRvZRoMG1qBeqkiJ5nIzjHI0k/OtHi+XHqvTXa7QVnEZ9A9SAt9dfzeCSHyD+eSvUa6I7FaKnIG8pNIjeC3eLkvSuyMEa3acpCncxUUSMXHSCgZDQ9cY6IHC7fBI0+Hfl0XXP3M0BsDtbRW4smmNEjXM/dmYVdbraRHOryn3GmeoGbm/h6E514dl/qoFqll01PiBgKJGVX77qSUIjkZBrR0712syRGSZjtIMqacBUloUuCDzRa9/CzTAS/WUM+7dk5EHe0sxZpkM7PKnjbdfZZ2v3++T3YNKsOuCF83XmTjs+ftG5rRj5tSuf3AUDdYE9mUHNSDKqenR0P5rbtZ9UrIB5vKi4yA1vAi/6UUF31urp6uAIBa5kVsT5OJl0UhCp6dZwj5nhLFm8sR8uVn2W+XiklRPtDlHlhJLMMPy+P5nqDx8EN3BD5XM5xNEQo4xBpUtbd5iboAuY0ZkJD1R4E3aOnZzKyg42x3cGBNC44/q2pHaVigkFUPyKSuGFOiCHY3K6B1ku96/BmSohqxbBWe0mS46TMrl8OSKLzTM57GKZJaWxv7HqAGIyYGBqwP0sf2qinwnrfBP6+axa7nL0ZGIvS4R+ofSho/BqnA6psc/xshUyL19sHTDGs7v6RF+MgZAlgyIYehLLDRPyfF9ct0Xi9Z1f36h+uPm2hVrVCZg5s6nUlwKtBkEI/e72dklFZRqGrGR8ZM5jCHtWje6M45sjzeYSNWp3xmQD+ztUN81qInz6YsBPVn7AAe0DsfQUiitSOveRjfS1QbXCR0vrf7uPgxiCAjXAvIcNgNWlJnNcAq7Zu+RRLMEpkj2gcAnCe2VyWrZQPw6hAFbVSuH7R4PA77iTYGL35pbA0jc7iP50qei4F14RAipmgJ+6fptYsQhno+HKV/MmYHrywlWS7LxHHH0o6Fppx5RT8NCbNxUvgJjFbuUJE8oIqZa8gTsO0uv+F5fZrSOE92k8YrZPuEpR8LKK1leRBV7jz0TSJ/1/H42ZPC9X8EFMsFwVAJ2a1rNIwl4VJfDBDbOt/FUB15xWZa8Mz3dyPqUgWzMoynb1+gUfgdEeGtcR+U4tsmo2ya1PsaFPoS8CDapyC0KIxyaLTw/207YL3CWczfYrGQ7eS1gQEMkVRqfknT1uqXuZxY8tYE67Niw3KxOZ7kjXAtOVXnZ4rFwpjzJNvRUlhWuFu8U3Je2TjwqBljZey2Nhs2yynQKb9eaY8j91qVfzQ86nMpHq231/wCwcErx+iVJIZVtyhvVBBBpOhigLACxN4UpJ3gtpEUyL+c6lOSgtvKCNQaQjQ51ov+yri6l6F83cfUvZv7V7X9IwS7qRhE7HqynVQI8j6id1lZxTaQKJ0aZ0xmwu9q+MEsuAXbm8pORc/mwOCnB1IAnvP8qbSwBSfycoW3dKojoS3gqbQbMEKf87BIANWXi5/tqUaqJM1nikRGAMB/rOHgTT1GvGaTKCfwKpIhLfH1Jz5XfcS302+z78bxBhJQqV5diFRJ7yBZQUgbULl59QZ0JBCtJo50XNwJwRTLzGD/oHS0zEioPLdaM4OMjdpcb9WMhdSqymSRFR6yuTWz3beTnAB0E2lmOyu1sw0WXPAarfJ5MqbCP7eh4m3igyKD4F2znuEInseH49EhDt1ONyuKyhaG2fb/Vu3+SyVDTCp9Pzy+SwDmBRBQ/iZaml68FZ+2WwPzLm20GnihO/LluTZOMUjtHJEs6ZVy2IW6RvNCvYXYtU16NG7AUkMxE57eKGBM+3ZEd4cUndO5/5l6f4+q8YS4MzoOQ4U/IyNN58XdqFq9kn+uOxBsyahy7gwJLoKCyFZkwDV7cBWveCbkJ4Hqnynjx1IRpECT5eq/HGIZkRZ3A6MWa7reHMlB510KbutqKm054GwiJHBII0ldrAw+FMm/VsKGrr1byGQYDqq9eMfQtPKPpXgVc5iOBXjN70QM3V5k5VOy1wLb/Yj7lyie1fnpdSeVkB1OU='
cipher = AES.new(hashlib.sha256('M'.join([ str(pin) for pin in pins ])).digest(), AES.MODE_CFB, 'AnIVofHighDegree')
open('present.gif', 'w').write(cipher.decrypt(b64decode(wrappedPresent)))
print "Your present was unwrapped to 'present.gif'. Go get it!"


Looks complex but essentially we know that the program receives 4 pins which are numbers between 100000 and 999999 and then it goes through all kinds of checks to see if they are valid. The checks are almost all done by checking two or more pins against various conditions at the same time.
We can use this to our advantage because it means that if we find one pin, we can get the others.

Bruteforcing all pins is totally out of the question unless you have a quantic computer :) the process is very slow because basically we are checking 999999^4 = 999996000005999996000001 possibilities

So the idea here is to find a pin which has checks that aren't related to any other pins. This will give us a way of filtering out bad pins.
Here are those atomic conditions:

if pins[1] & 543210 != 18752:
if pins[2] / 10000 != 42 or pins[2] % 100 != 42:

so for these pins we can weed out the bad pins with something like this:

pin2vals = []
for pin2 in range(100000,999999):
        if(pin2 & 543210 == 18752):
                pin2vals.append(pin2);

and then we use only the valid range of pins or subsequent checks and we do this until we got very few combinations.
For this I've wrote a script in python:

import hashlib
from Crypto.Cipher import AES
from base64 import b64decode, b64encode

print "\n === Henshin's PIN bruteforcer === \n";
print "Filtering PIN ranges..."

pin2vals = []
for pin2 in range(100000,999999):
        if(pin2 & 543210 == 18752):
                #print "got it: ",pin2
                pin2vals.append(pin2);

print "pin2 all done! ", len(pin2vals)

pin3vals = []
for pin3 in range(100000,999999):
        if(pin3 / 10000 == 42 and pin3 % 100 == 42):
                pin3vals.append(pin3)
print "pin3 all done! ", len(pin3vals)

p2vals = []
p3vals = []
for pin2 in pin2vals:
        if(pin2 & 543210 == 18752):
                for pin3 in pin3vals:
                        if(pin2 & pin3 == 30992):
                                if(pin3 / 10000 == 42 and pin3 % 100 == 42):
                                        #print "GOT pin2:",pin2," and PIN3:",pin3
                                        p2vals.append(pin2)
                                        p3vals.append(pin3)

#remove dups
p2vals = sorted(set(p2vals))
p3vals = sorted(set(p3vals))
print "number of pins for pin2:",len(p2vals)
print "number of pins for pin3:",len(p3vals)


pin1vals = []
for pin1 in range(100000,999999):
        for pin2 in p2vals:
                if pin1 & 57005 | pin2 >> 10 == 54399 and pin1 ^ 12648430 | pin2 == 12845045:
                        pin1vals.append(pin1)

pin1vals = sorted(set(pin1vals))
print "number of pins for pin1:",len(pin1vals)

pin2temp = p2vals;
p2vals = []
p4vals = []
for pin2 in pin2temp:
        for pin4 in range(100000,999999):
                if (pin2 * 23 - 1234567 ^ pin4) - 1199399 == 42:
                        p2vals.append(pin2)
                        p4vals.append(pin4)

p2vals = sorted(set(p2vals))
p4vals = sorted(set(p4vals))

print "number of pins for pin2:",len(p2vals)
print "number of pins for pin4:",len(p4vals)

p4temp = p4vals
p4vals = []
p1vals = []

for pin1 in pin1vals:
        #print "testing pin1: ",pin1
        for pin4 in p4temp:
                if pin4 | pin1 == 783695:
                        p1vals.append(pin1)
                        p4vals.append(pin4)

p1vals = sorted(set(p1vals))
p4vals = sorted(set(p4vals))
print "Number of pins for pin1:",len(p1vals);
print "Number of pins for pin4:",len(p4vals);

for pin1 in p1vals:
        for pin2 in p2vals:
                for pin3 in p3vals:
                        for pin4 in p4vals:
                                if ((pin1 ^ pin2) + pin3 == pin4 and pin1 & 57005 | pin2 >> 10 == 54399 and pin4 + pin3 - pin1 ^ pin2 == 702564 and (pin2 * 23 - 1234567 ^ pin4) - 1199399 == 42 and pin3 & pin2 & pin4 == 8448):
                                        print "Got it!"
                                        print "Pin1: ",pin1,"Pin2: ",pin2," Pin3: ",pin3," Pin4: ",pin4
                                        print "If you just wanna copy paste: ",pin1,pin2,pin3,pin4
                                        break

And here's the execution (it takes less than 5 seconds):

root@henshin:~/hackvent/day16# python pin_bruteforcer.py

 === Henshin's PIN bruteforcer ===

Filtering PIN ranges...
pin2 all done!  1664
pin3 all done!  100
number of pins for pin2: 16
number of pins for pin3: 3
number of pins for pin1: 128
number of pins for pin2: 16
number of pins for pin4: 16
Number of pins for pin1: 32
Number of pins for pin4: 1
Got it!
Pin1:  251214 Pin2:  130389  Pin3:  424242  Pin4:  565581
If you just wanna copy paste:  251214 130389 424242 565581

That's it we got the pin combination. Now we can run the original pyc and give it as input:

root@henshin:~/hackvent/day16# python ChristmasPresentSafe.pyc
This present has a high-security lock to prevent any unauthorized unwrapping before christmas!
Enter your PIN codes: 251214 130389 424242 565581
Your present was unwrapped to 'present.gif'. Go get it!

And to get our token:
root@henshin:~/hackvent/day16# zbarimg present.gif
QR-Code:HV14-kwSD-Fo0f-nzOV-9fZY-flYt


Day 17:

Public Key Encryption 
better dont try this manually

PublicKey(n=4157341, e=5)
Encrypted: 4100698
PublicKey(n=1435807, e=5)
Encrypted: 1386406
PublicKey(n=7550821, e=5)
Encrypted: 2712996
PublicKey(n=2030989, e=3)
Encrypted: 32768
PublicKey(n=153707, e=11)
Encrypted: 38178
PublicKey(n=16068707, e=5)
Encrypted: 10576615
PublicKey(n=243379, e=3)
Encrypted: 141339
PublicKey(n=5210171, e=7)
Encrypted: 5044903
PublicKey(n=15773077, e=5)
Encrypted: 15642259
PublicKey(n=65693, e=5)
Encrypted: 51002
PublicKey(n=16097573, e=11)
Encrypted: 5176063
PublicKey(n=1347251, e=5)
Encrypted: 581783
PublicKey(n=16480523, e=11)
Encrypted: 7239458
PublicKey(n=2378093, e=5)
Encrypted: 46434
PublicKey(n=1163809, e=5)
Encrypted: 155553
PublicKey(n=711803, e=7)
Encrypted: 655655
PublicKey(n=184423, e=3)
Encrypted: 32768
PublicKey(n=9051551, e=5)
Encrypted: 3818256
PublicKey(n=33221, e=5)
Encrypted: 2194
PublicKey(n=1019327, e=5)
Encrypted: 839131
PublicKey(n=40723, e=11)
Encrypted: 7013
PublicKey(n=12713699, e=5)
Encrypted: 12637275
PublicKey(n=3294419, e=5)
Encrypted: 2922785
PublicKey(n=2963329, e=3)
Encrypted: 1481544
PublicKey(n=1046411, e=5)
Encrypted: 623917
PublicKey(n=235063, e=7)
Encrypted: 164442
PublicKey(n=721681, e=3)
Encrypted: 32768
PublicKey(n=253807, e=3)
Encrypted: 142397
PublicKey(n=619481, e=5)
Encrypted: 452443
PublicKey(n=260993, e=5)
Encrypted: 147328
PublicKey(n=1256093, e=5)
Encrypted: 1010759
PublicKey(n=13881313, e=17)
Encrypted: 9682507
PublicKey(n=13846489, e=5)
Encrypted: 1059267
PublicKey(n=57403, e=3)
Encrypted: 32768
PublicKey(n=4054081, e=3)
Encrypted: 1000000
PublicKey(n=2151067, e=7)
Encrypted: 471061
PublicKey(n=88157, e=5)
Encrypted: 48375
PublicKey(n=73153, e=3)
Encrypted: 57815
PublicKey(n=52993, e=3)
Encrypted: 32768
PublicKey(n=1168991, e=5)
Encrypted: 569556
PublicKey(n=104617, e=3)
Encrypted: 88748
PublicKey(n=393233, e=7)
Encrypted: 75575
PublicKey(n=57403, e=3)
Encrypted: 51628
PublicKey(n=7247473, e=5)
Encrypted: 874532
PublicKey(n=46003, e=3)
Encrypted: 2776
PublicKey(n=14779717, e=7)
Encrypted: 4900718
PublicKey(n=4007287, e=3)
Encrypted: 32768
PublicKey(n=421999, e=3)
Encrypted: 280866
PublicKey(n=665699, e=5)
Encrypted: 44689
PublicKey(n=3951523, e=3)
Encrypted: 32768
PublicKey(n=947213, e=7)
Encrypted: 352625
PublicKey(n=14728673, e=7)
Encrypted: 3519604
PublicKey(n=1013963, e=7)
Encrypted: 56336
PublicKey(n=96673, e=5)
Encrypted: 54911
PublicKey(n=957613, e=7)
Encrypted: 275714
PublicKey(n=1118981, e=5)
Encrypted: 143206
PublicKey(n=4229177, e=5)
Encrypted: 3950193
PublicKey(n=52993, e=3)
Encrypted: 12011
PublicKey(n=420443, e=7)
Encrypted: 341246
PublicKey(n=4535387, e=7)
Encrypted: 3149476
PublicKey(n=2657813, e=11)
Encrypted: 2134408
PublicKey(n=1293671, e=5)
Encrypted: 1212657
PublicKey(n=47897, e=11)
Encrypted: 36859
PublicKey(n=150947, e=7)
Encrypted: 9810
PublicKey(n=1113839, e=7)
Encrypted: 32896
PublicKey(n=12509621, e=5)
Encrypted: 11915340
PublicKey(n=11938697, e=7)
Encrypted: 714605
PublicKey(n=2045063, e=19)
Encrypted: 954136
PublicKey(n=14980579, e=3)
Encrypted: 1030301
PublicKey(n=63523, e=5)
Encrypted: 14288
PublicKey(n=412681, e=5)
Encrypted: 243019
PublicKey(n=6363719, e=7)
Encrypted: 4355263
PublicKey(n=319369, e=3)
Encrypted: 32768
PublicKey(n=442331, e=11)
Encrypted: 149643
PublicKey(n=1183979, e=5)
Encrypted: 192423
PublicKey(n=13841567, e=11)
Encrypted: 3962742
PublicKey(n=131003, e=5)
Encrypted: 107254
PublicKey(n=296897, e=13)
Encrypted: 223213
PublicKey(n=249761, e=5)
Encrypted: 15042
PublicKey(n=54619, e=5)
Encrypted: 48514
PublicKey(n=441239, e=5)
Encrypted: 427053
PublicKey(n=15239431, e=5)
Encrypted: 2469284
PublicKey(n=4011253, e=3)
Encrypted: 1030301
PublicKey(n=3872987, e=5)
Encrypted: 2570536
PublicKey(n=13178899, e=5)
Encrypted: 9729409
PublicKey(n=4938793, e=7)
Encrypted: 2131050
PublicKey(n=247483, e=3)
Encrypted: 75998
PublicKey(n=114857, e=7)
Encrypted: 12323
PublicKey(n=3448261, e=7)
Encrypted: 1265764
PublicKey(n=1170623, e=5)
Encrypted: 98710
PublicKey(n=60187, e=5)
Encrypted: 27909
PublicKey(n=279493, e=5)
Encrypted: 45729
PublicKey(n=4023709, e=5)
Encrypted: 1364760
PublicKey(n=43039, e=5)
Encrypted: 22307
PublicKey(n=5551913, e=7)
Encrypted: 524547
PublicKey(n=4566389, e=5)
Encrypted: 2839412
PublicKey(n=1109863, e=5)
Encrypted: 1081412
PublicKey(n=63797, e=7)
Encrypted: 1826
PublicKey(n=12322049, e=7)
Encrypted: 347668
PublicKey(n=10874027, e=11)
Encrypted: 9875135
PublicKey(n=254563, e=5)
Encrypted: 40119
PublicKey(n=3749747, e=7)
Encrypted: 806607
PublicKey(n=115747, e=5)
Encrypted: 67271
PublicKey(n=88409, e=13)
Encrypted: 67048
PublicKey(n=2018329, e=3)
Encrypted: 1481544
PublicKey(n=799133, e=7)
Encrypted: 462950
PublicKey(n=436789, e=5)
Encrypted: 277834
PublicKey(n=13707853, e=5)
Encrypted: 3630214
PublicKey(n=15112649, e=7)
Encrypted: 10190759
PublicKey(n=58033, e=3)
Encrypted: 39303
PublicKey(n=841627, e=3)
Encrypted: 32768
PublicKey(n=4079183, e=13)
Encrypted: 3167478
PublicKey(n=491831, e=5)
Encrypted: 185744
PublicKey(n=458267, e=7)
Encrypted: 253509
PublicKey(n=1450223, e=5)
Encrypted: 117134
PublicKey(n=2962969, e=3)
Encrypted: 1030301
PublicKey(n=993791, e=5)
Encrypted: 783009
PublicKey(n=112169, e=5)
Encrypted: 467
PublicKey(n=10361957, e=11)
Encrypted: 744843
PublicKey(n=76987, e=3)
Encrypted: 32768
PublicKey(n=268967, e=7)
Encrypted: 102709
PublicKey(n=1069447, e=5)
Encrypted: 816857
PublicKey(n=957067, e=3)
Encrypted: 447861
PublicKey(n=51143, e=5)
Encrypted: 50462
PublicKey(n=457297, e=3)
Encrypted: 453037
PublicKey(n=15916909, e=5)
Encrypted: 10515808
PublicKey(n=777823, e=3)
Encrypted: 252478
PublicKey(n=16183799, e=5)
Encrypted: 14596017
PublicKey(n=65369, e=7)
Encrypted: 27005
PublicKey(n=568861, e=5)
Encrypted: 499595
PublicKey(n=5021689, e=7)
Encrypted: 3288642
PublicKey(n=82333, e=3)
Encrypted: 32768
PublicKey(n=78937, e=5)
Encrypted: 13192
PublicKey(n=506041, e=5)
Encrypted: 428333
PublicKey(n=100097, e=5)
Encrypted: 87486
PublicKey(n=4244843, e=5)
Encrypted: 556035
PublicKey(n=2943383, e=7)
Encrypted: 1628609
PublicKey(n=222083, e=5)
Encrypted: 149781
PublicKey(n=3282707, e=7)
Encrypted: 130402
PublicKey(n=62107, e=3)
Encrypted: 32768
PublicKey(n=1992989, e=7)
Encrypted: 1009727
PublicKey(n=1327499, e=5)
Encrypted: 290918
PublicKey(n=218011, e=3)
Encrypted: 201923
PublicKey(n=204703, e=5)
Encrypted: 130309
PublicKey(n=744011, e=5)
Encrypted: 73937
PublicKey(n=12652117, e=3)
Encrypted: 1685159
PublicKey(n=953429, e=5)
Encrypted: 774984
PublicKey(n=1789321, e=5)
Encrypted: 1216706
PublicKey(n=182527, e=5)
Encrypted: 151991
PublicKey(n=34933, e=7)
Encrypted: 30860
PublicKey(n=1710503, e=5)
Encrypted: 615197
PublicKey(n=82933, e=3)
Encrypted: 67305
PublicKey(n=74491, e=5)
Encrypted: 16329
PublicKey(n=55417, e=7)
Encrypted: 34611
PublicKey(n=177139, e=5)
Encrypted: 79075
PublicKey(n=301187, e=5)
Encrypted: 180136
PublicKey(n=88307, e=5)
Encrypted: 86079
PublicKey(n=16320497, e=7)
Encrypted: 2622417
PublicKey(n=62957, e=7)
Encrypted: 60060
PublicKey(n=2406319, e=7)
Encrypted: 978415
PublicKey(n=247561, e=3)
Encrypted: 38634
PublicKey(n=196891, e=3)
Encrypted: 32768
PublicKey(n=33127, e=11)
Encrypted: 20351
PublicKey(n=285547, e=5)
Encrypted: 167534
PublicKey(n=1163863, e=3)
Encrypted: 397033
PublicKey(n=53461, e=5)
Encrypted: 34385
PublicKey(n=67799, e=11)
Encrypted: 42141
PublicKey(n=4286449, e=3)
Encrypted: 1728000
PublicKey(n=59911, e=7)
Encrypted: 10933
PublicKey(n=1710127, e=7)
Encrypted: 1576811
PublicKey(n=8513203, e=3)
Encrypted: 1061208
PublicKey(n=3970381, e=3)
Encrypted: 1157625
PublicKey(n=157537, e=3)
Encrypted: 70704
PublicKey(n=7586899, e=3)
Encrypted: 1092727
PublicKey(n=3592189, e=5)
Encrypted: 2947676
PublicKey(n=10618541, e=5)
Encrypted: 2730991
PublicKey(n=1484851, e=3)
Encrypted: 36024
PublicKey(n=423569, e=5)
Encrypted: 14376

What a long list. Anyway, this one is about public/private key encryption.

What we know about private key encryption is that we need the following elements:

prime1 and prime2 - two (big) prime numbers
n = multiplication of the two primes (prime1 * prime2) - this is actually the public key
phi - calculation done with the primes which is: (prime1-1)*(prime2-1)
e - public exponent (given in the challenge)
d - inverse module of e


So this exercise is quite simple because normally, the primes are very big and create an even bigger n (public key) which is very very hard to crack (calculate the factorization)
This is the principle of Private/public key encryption

So, in this case since the n are pretty small, a normal CPU will have no problems finding out which are the prime factors.

I've developed a Perl script which uses BigInt library to have access to optimized functions for big numbers and the Math::Prime::Util::GMP to use the super optimized factor function to calculate the factors of each N.

The only caveat here is that we are decrypting numbers, so we have to decrypt them using the formula m[i] = c[i]^d (mod N) instead of any RSA functions.

#!/usr/bin/perl

use Data::Dumper;
use Math::BigInt;
use Math::Prime::Util::GMP qw(factor);

print "\nHackvent Day 17 - Public Key encriptation!\n";
#Format: Pubkey, e, encripted
@pubkeys=(
[4157341,5,4100698],
[1435807,5,1386406],
[7550821,5,2712996],
[2030989,3,32768],
[153707,11,38178],
[16068707,5,10576615],
[243379,3,141339],
[5210171,7,5044903],
[15773077,5,15642259],
[65693,5,51002],
[16097573,11,5176063],
[1347251,5,581783],
[16480523,11,7239458],
[2378093,5,46434],
[1163809,5,155553],
[711803,7,655655],
[184423,3,32768],
[9051551,5,3818256],
[33221,5,2194],
[1019327,5,839131],
[40723,11,7013],
[12713699,5,12637275],
[3294419,5,2922785],
[2963329,3,1481544],
[1046411,5,623917],
[235063,7,164442],
[721681,3,32768],
[253807,3,142397],
[619481,5,452443],
[260993,5,147328],
[1256093,5,1010759],
[13881313,17,9682507],
[13846489,5,1059267],
[57403,3,32768],
[4054081,3,1000000],
[2151067,7,471061],
[88157,5,48375],
[73153,3,57815],
[52993,3,32768],
[1168991,5,569556],
[104617,3,88748],
[393233,7,75575],
[57403,3,51628],
[7247473,5,874532],
[46003,3,2776],
[14779717,7,4900718],
[4007287,3,32768],
[421999,3,280866],
[665699,5,44689],
[3951523,3,32768],
[947213,7,352625],
[14728673,7,3519604],
[1013963,7,56336],
[96673,5,54911],
[957613,7,275714],
[1118981,5,143206],
[4229177,5,3950193],
[52993,3,12011],
[420443,7,341246],
[4535387,7,3149476],
[2657813,11,2134408],
[1293671,5,1212657],
[47897,11,36859],
[150947,7,9810],
[1113839,7,32896],
[12509621,5,11915340],
[11938697,7,714605],
[2045063,19,954136],
[14980579,3,1030301],
[63523,5,14288],
[412681,5,243019],
[6363719,7,4355263],
[319369,3,32768],
[442331,11,149643],
[1183979,5,192423],
[13841567,11,3962742],
[131003,5,107254],
[296897,13,223213],
[249761,5,15042],
[54619,5,48514],
[441239,5,427053],
[15239431,5,2469284],
[4011253,3,1030301],
[3872987,5,2570536],
[13178899,5,9729409],
[4938793,7,2131050],
[247483,3,75998],
[114857,7,12323],
[3448261,7,1265764],
[1170623,5,98710],
[60187,5,27909],
[279493,5,45729],
[4023709,5,1364760],
[43039,5,22307],
[5551913,7,524547],
[4566389,5,2839412],
[1109863,5,1081412],
[63797,7,1826],
[12322049,7,347668],
[10874027,11,9875135],
[254563,5,40119],
[3749747,7,806607],
[115747,5,67271],
[88409,13,67048],
[2018329,3,1481544],
[799133,7,462950],
[436789,5,277834],
[13707853,5,3630214],
[15112649,7,10190759],
[58033,3,39303],
[841627,3,32768],
[4079183,13,3167478],
[491831,5,185744],
[458267,7,253509],
[1450223,5,117134],
[2962969,3,1030301],
[993791,5,783009],
[112169,5,467],
[10361957,11,744843],
[76987,3,32768],
[268967,7,102709],
[1069447,5,816857],
[957067,3,447861],
[51143,5,50462],
[457297,3,453037],
[15916909,5,10515808],
[777823,3,252478],
[16183799,5,14596017],
[65369,7,27005],
[568861,5,499595],
[5021689,7,3288642],
[82333,3,32768],
[78937,5,13192],
[506041,5,428333],
[100097,5,87486],
[4244843,5,556035],
[2943383,7,1628609],
[222083,5,149781],
[3282707,7,130402],
[62107,3,32768],
[1992989,7,1009727],
[1327499,5,290918],
[218011,3,201923],
[204703,5,130309],
[744011,5,73937],
[12652117,3,1685159],
[953429,5,774984],
[1789321,5,1216706],
[182527,5,151991],
[34933,7,30860],
[1710503,5,615197],
[82933,3,67305],
[74491,5,16329],
[55417,7,34611],
[177139,5,79075],
[301187,5,180136],
[88307,5,86079],
[16320497,7,2622417],
[62957,7,60060],
[2406319,7,978415],
[247561,3,38634],
[196891,3,32768],
[33127,11,20351],
[285547,5,167534],
[1163863,3,397033],
[53461,5,34385],
[67799,11,42141],
[4286449,3,1728000],
[59911,7,10933],
[1710127,7,1576811],
[8513203,3,1061208],
[3970381,3,1157625],
[157537,3,70704],
[7586899,3,1092727],
[3592189,5,2947676],
[10618541,5,2730991],
[1484851,3,36024],
[423569,5,14376]);

my $str = "";
for(my $i=0; $i<scalar @pubkeys; $i++){
    my @ki = @{$pubkeys[$i]};
    my @factors = factor($ki[0]);
    my $n = $ki[0];
    my $crypt = $ki[2];
    $prime1 = $factors[0];
    $prime2 = $factors[1];
    my $phi = ($prime1-1)*($prime2-1);
    my $e = $ki[1];
    my $msg = $ki[2];
    my $x = Math::BigInt->new($e);
    my $d = $x->bmodinv($phi);
    print "Pub: $n Prime1: $prime1 Prime2: $prime2 Phi: $phi E: $e D: $d Crypt: $crypt\n";
    #we need this to decrypt. As CrypTool states => Decryption into plaintext m[i] = c[i]^d (mod N)
    $x = Math::BigInt->new($crypt);
    $y = $x->bmodpow($d,$n);
    $str.=chr($y);
}

print "Solution: $str\n";

And this produces the following output:

root@kali-ts:~/hackvent/day17# ./pubkey_breaker.pl

Hackvent Day 17 - Public Key encriptation!
Pub: 4157341 Prime1: 1069 Prime2: 3889 Phi: 4152384 E: 5 D: 830477 Crypt: 4100698
Pub: 1435807 Prime1: 1009 Prime2: 1423 Phi: 1433376 E: 5 D: 1146701 Crypt: 1386406
Pub: 7550821 Prime1: 2557 Prime2: 2953 Phi: 7545312 E: 5 D: 3018125 Crypt: 2712996
Pub: 2030989 Prime1: 1283 Prime2: 1583 Phi: 2028124 E: 3 D: 1352083 Crypt: 32768
Pub: 153707 Prime1: 281 Prime2: 547 Phi: 152880 E: 11 D: 69491 Crypt: 38178
Pub: 16068707 Prime1: 2063 Prime2: 7789 Phi: 16058856 E: 5 D: 12847085 Crypt: 10576615
Pub: 243379 Prime1: 257 Prime2: 947 Phi: 242176 E: 3 D: 161451 Crypt: 141339
Pub: 5210171 Prime1: 2161 Prime2: 2411 Phi: 5205600 E: 7 D: 4461943 Crypt: 5044903
Pub: 15773077 Prime1: 2383 Prime2: 6619 Phi: 15764076 E: 5 D: 12611261 Crypt: 15642259
Pub: 65693 Prime1: 179 Prime2: 367 Phi: 65148 E: 5 D: 39089 Crypt: 51002
Pub: 16097573 Prime1: 2053 Prime2: 7841 Phi: 16087680 E: 11 D: 11700131 Crypt: 5176063
Pub: 1347251 Prime1: 823 Prime2: 1637 Phi: 1344792 E: 5 D: 537917 Crypt: 581783
Pub: 16480523 Prime1: 3923 Prime2: 4201 Phi: 16472400 E: 11 D: 1497491 Crypt: 7239458
Pub: 2378093 Prime1: 1117 Prime2: 2129 Phi: 2374848 E: 5 D: 1424909 Crypt: 46434
Pub: 1163809 Prime1: 577 Prime2: 2017 Phi: 1161216 E: 5 D: 928973 Crypt: 155553
Pub: 711803 Prime1: 523 Prime2: 1361 Phi: 709920 E: 7 D: 608503 Crypt: 655655
Pub: 184423 Prime1: 311 Prime2: 593 Phi: 183520 E: 3 D: 122347 Crypt: 32768
Pub: 9051551 Prime1: 2719 Prime2: 3329 Phi: 9045504 E: 5 D: 1809101 Crypt: 3818256
Pub: 33221 Prime1: 139 Prime2: 239 Phi: 32844 E: 5 D: 6569 Crypt: 2194
Pub: 1019327 Prime1: 523 Prime2: 1949 Phi: 1016856 E: 5 D: 813485 Crypt: 839131
Pub: 40723 Prime1: 193 Prime2: 211 Phi: 40320 E: 11 D: 7331 Crypt: 7013
Pub: 12713699 Prime1: 2347 Prime2: 5417 Phi: 12705936 E: 5 D: 10164749 Crypt: 12637275
Pub: 3294419 Prime1: 1217 Prime2: 2707 Phi: 3290496 E: 5 D: 2632397 Crypt: 2922785
Pub: 2963329 Prime1: 1223 Prime2: 2423 Phi: 2959684 E: 3 D: 1973123 Crypt: 1481544
Pub: 1046411 Prime1: 547 Prime2: 1913 Phi: 1043952 E: 5 D: 417581 Crypt: 623917
Pub: 235063 Prime1: 313 Prime2: 751 Phi: 234000 E: 7 D: 167143 Crypt: 164442
Pub: 721681 Prime1: 593 Prime2: 1217 Phi: 719872 E: 3 D: 479915 Crypt: 32768
Pub: 253807 Prime1: 353 Prime2: 719 Phi: 252736 E: 3 D: 168491 Crypt: 142397
Pub: 619481 Prime1: 683 Prime2: 907 Phi: 617892 E: 5 D: 247157 Crypt: 452443
Pub: 260993 Prime1: 359 Prime2: 727 Phi: 259908 E: 5 D: 155945 Crypt: 147328
Pub: 1256093 Prime1: 719 Prime2: 1747 Phi: 1253628 E: 5 D: 752177 Crypt: 1010759
Pub: 13881313 Prime1: 3631 Prime2: 3823 Phi: 13873860 E: 17 D: 9793313 Crypt: 9682507
Pub: 13846489 Prime1: 2113 Prime2: 6553 Phi: 13837824 E: 5 D: 2767565 Crypt: 1059267
Pub: 57403 Prime1: 137 Prime2: 419 Phi: 56848 E: 3 D: 37899 Crypt: 32768
Pub: 4054081 Prime1: 1061 Prime2: 3821 Phi: 4049200 E: 3 D: 2699467 Crypt: 1000000
Pub: 2151067 Prime1: 1327 Prime2: 1621 Phi: 2148120 E: 7 D: 920623 Crypt: 471061
Pub: 88157 Prime1: 199 Prime2: 443 Phi: 87516 E: 5 D: 70013 Crypt: 48375
Pub: 73153 Prime1: 191 Prime2: 383 Phi: 72580 E: 3 D: 48387 Crypt: 57815
Pub: 52993 Prime1: 197 Prime2: 269 Phi: 52528 E: 3 D: 35019 Crypt: 32768
Pub: 1168991 Prime1: 613 Prime2: 1907 Phi: 1166472 E: 5 D: 466589 Crypt: 569556
Pub: 104617 Prime1: 233 Prime2: 449 Phi: 103936 E: 3 D: 69291 Crypt: 88748
Pub: 393233 Prime1: 461 Prime2: 853 Phi: 391920 E: 7 D: 279943 Crypt: 75575
Pub: 57403 Prime1: 137 Prime2: 419 Phi: 56848 E: 3 D: 37899 Crypt: 51628
Pub: 7247473 Prime1: 2377 Prime2: 3049 Phi: 7242048 E: 5 D: 4345229 Crypt: 874532
Pub: 46003 Prime1: 179 Prime2: 257 Phi: 45568 E: 3 D: 30379 Crypt: 2776
Pub: 14779717 Prime1: 2677 Prime2: 5521 Phi: 14771520 E: 7 D: 12661303 Crypt: 4900718
Pub: 4007287 Prime1: 1193 Prime2: 3359 Phi: 4002736 E: 3 D: 2668491 Crypt: 32768
Pub: 421999 Prime1: 479 Prime2: 881 Phi: 420640 E: 3 D: 280427 Crypt: 280866
Pub: 665699 Prime1: 547 Prime2: 1217 Phi: 663936 E: 5 D: 531149 Crypt: 44689
Pub: 3951523 Prime1: 1187 Prime2: 3329 Phi: 3947008 E: 3 D: 2631339 Crypt: 32768
Pub: 947213 Prime1: 661 Prime2: 1433 Phi: 945120 E: 7 D: 810103 Crypt: 352625
Pub: 14728673 Prime1: 2153 Prime2: 6841 Phi: 14719680 E: 7 D: 4205623 Crypt: 3519604
Pub: 1013963 Prime1: 563 Prime2: 1801 Phi: 1011600 E: 7 D: 433543 Crypt: 56336
Pub: 96673 Prime1: 277 Prime2: 349 Phi: 96048 E: 5 D: 57629 Crypt: 54911
Pub: 957613 Prime1: 523 Prime2: 1831 Phi: 955260 E: 7 D: 545863 Crypt: 275714
Pub: 1118981 Prime1: 1009 Prime2: 1109 Phi: 1116864 E: 5 D: 223373 Crypt: 143206
Pub: 4229177 Prime1: 1399 Prime2: 3023 Phi: 4224756 E: 5 D: 3379805 Crypt: 3950193
Pub: 52993 Prime1: 197 Prime2: 269 Phi: 52528 E: 3 D: 35019 Crypt: 12011
Pub: 420443 Prime1: 433 Prime2: 971 Phi: 419040 E: 7 D: 59863 Crypt: 341246
Pub: 4535387 Prime1: 1831 Prime2: 2477 Phi: 4531080 E: 7 D: 3883783 Crypt: 3149476
Pub: 2657813 Prime1: 1201 Prime2: 2213 Phi: 2654400 E: 11 D: 2413091 Crypt: 2134408
Pub: 1293671 Prime1: 1063 Prime2: 1217 Phi: 1291392 E: 5 D: 516557 Crypt: 1212657
Pub: 47897 Prime1: 211 Prime2: 227 Phi: 47460 E: 11 D: 38831 Crypt: 36859
Pub: 150947 Prime1: 271 Prime2: 557 Phi: 150120 E: 7 D: 85783 Crypt: 9810
Pub: 1113839 Prime1: 709 Prime2: 1571 Phi: 1111560 E: 7 D: 476383 Crypt: 32896
Pub: 12509621 Prime1: 2677 Prime2: 4673 Phi: 12502272 E: 5 D: 5000909 Crypt: 11915340
Pub: 11938697 Prime1: 2081 Prime2: 5737 Phi: 11930880 E: 7 D: 3408823 Crypt: 714605
Pub: 2045063 Prime1: 1021 Prime2: 2003 Phi: 2042040 E: 19 D: 537379 Crypt: 954136
Pub: 14980579 Prime1: 3407 Prime2: 4397 Phi: 14972776 E: 3 D: 9981851 Crypt: 1030301
Pub: 63523 Prime1: 139 Prime2: 457 Phi: 62928 E: 5 D: 37757 Crypt: 14288
Pub: 412681 Prime1: 409 Prime2: 1009 Phi: 411264 E: 5 D: 82253 Crypt: 243019
Pub: 6363719 Prime1: 2039 Prime2: 3121 Phi: 6358560 E: 7 D: 3633463 Crypt: 4355263
Pub: 319369 Prime1: 389 Prime2: 821 Phi: 318160 E: 3 D: 212107 Crypt: 32768
Pub: 442331 Prime1: 631 Prime2: 701 Phi: 441000 E: 11 D: 40091 Crypt: 149643
Pub: 1183979 Prime1: 587 Prime2: 2017 Phi: 1181376 E: 5 D: 945101 Crypt: 192423
Pub: 13841567 Prime1: 3011 Prime2: 4597 Phi: 13833960 E: 11 D: 5030531 Crypt: 3962742
Pub: 131003 Prime1: 269 Prime2: 487 Phi: 130248 E: 5 D: 78149 Crypt: 107254
Pub: 296897 Prime1: 337 Prime2: 881 Phi: 295680 E: 13 D: 181957 Crypt: 223213
Pub: 249761 Prime1: 379 Prime2: 659 Phi: 248724 E: 5 D: 49745 Crypt: 15042
Pub: 54619 Prime1: 193 Prime2: 283 Phi: 54144 E: 5 D: 10829 Crypt: 48514
Pub: 441239 Prime1: 463 Prime2: 953 Phi: 439824 E: 5 D: 87965 Crypt: 427053
Pub: 15239431 Prime1: 2389 Prime2: 6379 Phi: 15230664 E: 5 D: 3046133 Crypt: 2469284
Pub: 4011253 Prime1: 1109 Prime2: 3617 Phi: 4006528 E: 3 D: 2671019 Crypt: 1030301
Pub: 3872987 Prime1: 1069 Prime2: 3623 Phi: 3868296 E: 5 D: 3094637 Crypt: 2570536
Pub: 13178899 Prime1: 3067 Prime2: 4297 Phi: 13171536 E: 5 D: 10537229 Crypt: 9729409
Pub: 4938793 Prime1: 2083 Prime2: 2371 Phi: 4934340 E: 7 D: 2819623 Crypt: 2131050
Pub: 247483 Prime1: 263 Prime2: 941 Phi: 246280 E: 3 D: 164187 Crypt: 75998
Pub: 114857 Prime1: 331 Prime2: 347 Phi: 114180 E: 7 D: 32623 Crypt: 12323
Pub: 3448261 Prime1: 1291 Prime2: 2671 Phi: 3444300 E: 7 D: 492043 Crypt: 1265764
Pub: 1170623 Prime1: 809 Prime2: 1447 Phi: 1168368 E: 5 D: 701021 Crypt: 98710
Pub: 60187 Prime1: 139 Prime2: 433 Phi: 59616 E: 5 D: 47693 Crypt: 27909
Pub: 279493 Prime1: 277 Prime2: 1009 Phi: 278208 E: 5 D: 166925 Crypt: 45729
Pub: 4023709 Prime1: 1123 Prime2: 3583 Phi: 4019004 E: 5 D: 803801 Crypt: 1364760
Pub: 43039 Prime1: 193 Prime2: 223 Phi: 42624 E: 5 D: 8525 Crypt: 22307
Pub: 5551913 Prime1: 1453 Prime2: 3821 Phi: 5546640 E: 7 D: 4754263 Crypt: 524547
Pub: 4566389 Prime1: 1187 Prime2: 3847 Phi: 4561356 E: 5 D: 3649085 Crypt: 2839412
Pub: 1109863 Prime1: 547 Prime2: 2029 Phi: 1107288 E: 5 D: 664373 Crypt: 1081412
Pub: 63797 Prime1: 131 Prime2: 487 Phi: 63180 E: 7 D: 36103 Crypt: 1826
Pub: 12322049 Prime1: 2459 Prime2: 5011 Phi: 12314580 E: 7 D: 7036903 Crypt: 347668
Pub: 10874027 Prime1: 2381 Prime2: 4567 Phi: 10867080 E: 11 D: 7903331 Crypt: 9875135
Pub: 254563 Prime1: 277 Prime2: 919 Phi: 253368 E: 5 D: 152021 Crypt: 40119
Pub: 3749747 Prime1: 1031 Prime2: 3637 Phi: 3745080 E: 7 D: 1070023 Crypt: 806607
Pub: 115747 Prime1: 283 Prime2: 409 Phi: 115056 E: 5 D: 92045 Crypt: 67271
Pub: 88409 Prime1: 211 Prime2: 419 Phi: 87780 E: 13 D: 20257 Crypt: 67048
Pub: 2018329 Prime1: 1181 Prime2: 1709 Phi: 2015440 E: 3 D: 1343627 Crypt: 1481544
Pub: 799133 Prime1: 823 Prime2: 971 Phi: 797340 E: 7 D: 455623 Crypt: 462950
Pub: 436789 Prime1: 577 Prime2: 757 Phi: 435456 E: 5 D: 348365 Crypt: 277834
Pub: 13707853 Prime1: 3637 Prime2: 3769 Phi: 13700448 E: 5 D: 8220269 Crypt: 3630214
Pub: 15112649 Prime1: 2111 Prime2: 7159 Phi: 15103380 E: 7 D: 8630503 Crypt: 10190759
Pub: 58033 Prime1: 131 Prime2: 443 Phi: 57460 E: 3 D: 38307 Crypt: 39303
Pub: 841627 Prime1: 557 Prime2: 1511 Phi: 839560 E: 3 D: 559707 Crypt: 32768
Pub: 4079183 Prime1: 1373 Prime2: 2971 Phi: 4074840 E: 13 D: 1253797 Crypt: 3167478
Pub: 491831 Prime1: 557 Prime2: 883 Phi: 490392 E: 5 D: 196157 Crypt: 185744
Pub: 458267 Prime1: 487 Prime2: 941 Phi: 456840 E: 7 D: 65263 Crypt: 253509
Pub: 1450223 Prime1: 719 Prime2: 2017 Phi: 1447488 E: 5 D: 868493 Crypt: 117134
Pub: 2962969 Prime1: 1307 Prime2: 2267 Phi: 2959396 E: 3 D: 1972931 Crypt: 1030301
Pub: 993791 Prime1: 587 Prime2: 1693 Phi: 991512 E: 5 D: 396605 Crypt: 783009
Pub: 112169 Prime1: 223 Prime2: 503 Phi: 111444 E: 5 D: 22289 Crypt: 467
Pub: 10361957 Prime1: 3217 Prime2: 3221 Phi: 10355520 E: 11 D: 941411 Crypt: 744843
Pub: 76987 Prime1: 167 Prime2: 461 Phi: 76360 E: 3 D: 50907 Crypt: 32768
Pub: 268967 Prime1: 277 Prime2: 971 Phi: 267720 E: 7 D: 152983 Crypt: 102709
Pub: 1069447 Prime1: 733 Prime2: 1459 Phi: 1067256 E: 5 D: 853805 Crypt: 816857
Pub: 957067 Prime1: 863 Prime2: 1109 Phi: 955096 E: 3 D: 636731 Crypt: 447861
Pub: 51143 Prime1: 199 Prime2: 257 Phi: 50688 E: 5 D: 30413 Crypt: 50462
Pub: 457297 Prime1: 557 Prime2: 821 Phi: 455920 E: 3 D: 303947 Crypt: 453037
Pub: 15916909 Prime1: 2053 Prime2: 7753 Phi: 15907104 E: 5 D: 3181421 Crypt: 10515808
Pub: 777823 Prime1: 569 Prime2: 1367 Phi: 775888 E: 3 D: 517259 Crypt: 252478
Pub: 16183799 Prime1: 2053 Prime2: 7883 Phi: 16173864 E: 5 D: 3234773 Crypt: 14596017
Pub: 65369 Prime1: 131 Prime2: 499 Phi: 64740 E: 7 D: 46243 Crypt: 27005
Pub: 568861 Prime1: 619 Prime2: 919 Phi: 567324 E: 5 D: 113465 Crypt: 499595
Pub: 5021689 Prime1: 1609 Prime2: 3121 Phi: 5016960 E: 7 D: 3583543 Crypt: 3288642
Pub: 82333 Prime1: 281 Prime2: 293 Phi: 81760 E: 3 D: 54507 Crypt: 32768
Pub: 78937 Prime1: 193 Prime2: 409 Phi: 78336 E: 5 D: 62669 Crypt: 13192
Pub: 506041 Prime1: 643 Prime2: 787 Phi: 504612 E: 5 D: 201845 Crypt: 428333
Pub: 100097 Prime1: 199 Prime2: 503 Phi: 99396 E: 5 D: 79517 Crypt: 87486
Pub: 4244843 Prime1: 1627 Prime2: 2609 Phi: 4240608 E: 5 D: 2544365 Crypt: 556035
Pub: 2943383 Prime1: 1123 Prime2: 2621 Phi: 2939640 E: 7 D: 2099743 Crypt: 1628609
Pub: 222083 Prime1: 337 Prime2: 659 Phi: 221088 E: 5 D: 132653 Crypt: 149781
Pub: 3282707 Prime1: 1297 Prime2: 2531 Phi: 3278880 E: 7 D: 936823 Crypt: 130402
Pub: 62107 Prime1: 173 Prime2: 359 Phi: 61576 E: 3 D: 41051 Crypt: 32768
Pub: 1992989 Prime1: 1231 Prime2: 1619 Phi: 1990140 E: 7 D: 1137223 Crypt: 1009727
Pub: 1327499 Prime1: 823 Prime2: 1613 Phi: 1325064 E: 5 D: 265013 Crypt: 290918
Pub: 218011 Prime1: 311 Prime2: 701 Phi: 217000 E: 3 D: 144667 Crypt: 201923
Pub: 204703 Prime1: 277 Prime2: 739 Phi: 203688 E: 5 D: 122213 Crypt: 130309
Pub: 744011 Prime1: 659 Prime2: 1129 Phi: 742224 E: 5 D: 148445 Crypt: 73937
Pub: 12652117 Prime1: 2801 Prime2: 4517 Phi: 12644800 E: 3 D: 8429867 Crypt: 1685159
Pub: 953429 Prime1: 523 Prime2: 1823 Phi: 951084 E: 5 D: 190217 Crypt: 774984
Pub: 1789321 Prime1: 1279 Prime2: 1399 Phi: 1786644 E: 5 D: 357329 Crypt: 1216706
Pub: 182527 Prime1: 349 Prime2: 523 Phi: 181656 E: 5 D: 145325 Crypt: 151991
Pub: 34933 Prime1: 181 Prime2: 193 Phi: 34560 E: 7 D: 29623 Crypt: 30860
Pub: 1710503 Prime1: 1289 Prime2: 1327 Phi: 1707888 E: 5 D: 1024733 Crypt: 615197
Pub: 82933 Prime1: 239 Prime2: 347 Phi: 82348 E: 3 D: 54899 Crypt: 67305
Pub: 74491 Prime1: 163 Prime2: 457 Phi: 73872 E: 5 D: 29549 Crypt: 16329
Pub: 55417 Prime1: 151 Prime2: 367 Phi: 54900 E: 7 D: 7843 Crypt: 34611
Pub: 177139 Prime1: 307 Prime2: 577 Phi: 176256 E: 5 D: 141005 Crypt: 79075
Pub: 301187 Prime1: 349 Prime2: 863 Phi: 299976 E: 5 D: 239981 Crypt: 180136
Pub: 88307 Prime1: 233 Prime2: 379 Phi: 87696 E: 5 D: 70157 Crypt: 86079
Pub: 16320497 Prime1: 3457 Prime2: 4721 Phi: 16312320 E: 7 D: 4660663 Crypt: 2622417
Pub: 62957 Prime1: 157 Prime2: 401 Phi: 62400 E: 7 D: 26743 Crypt: 60060
Pub: 2406319 Prime1: 1069 Prime2: 2251 Phi: 2403000 E: 7 D: 1373143 Crypt: 978415
Pub: 247561 Prime1: 281 Prime2: 881 Phi: 246400 E: 3 D: 164267 Crypt: 38634
Pub: 196891 Prime1: 401 Prime2: 491 Phi: 196000 E: 3 D: 130667 Crypt: 32768
Pub: 33127 Prime1: 157 Prime2: 211 Phi: 32760 E: 11 D: 14891 Crypt: 20351
Pub: 285547 Prime1: 283 Prime2: 1009 Phi: 284256 E: 5 D: 227405 Crypt: 167534
Pub: 1163863 Prime1: 947 Prime2: 1229 Phi: 1161688 E: 3 D: 774459 Crypt: 397033
Pub: 53461 Prime1: 193 Prime2: 277 Phi: 52992 E: 5 D: 21197 Crypt: 34385
Pub: 67799 Prime1: 151 Prime2: 449 Phi: 67200 E: 11 D: 61091 Crypt: 42141
Pub: 4286449 Prime1: 1193 Prime2: 3593 Phi: 4281664 E: 3 D: 2854443 Crypt: 1728000
Pub: 59911 Prime1: 181 Prime2: 331 Phi: 59400 E: 7 D: 33943 Crypt: 10933
Pub: 1710127 Prime1: 1117 Prime2: 1531 Phi: 1707480 E: 7 D: 975703 Crypt: 1576811
Pub: 8513203 Prime1: 2879 Prime2: 2957 Phi: 8507368 E: 3 D: 5671579 Crypt: 1061208
Pub: 3970381 Prime1: 1031 Prime2: 3851 Phi: 3965500 E: 3 D: 2643667 Crypt: 1157625
Pub: 157537 Prime1: 263 Prime2: 599 Phi: 156676 E: 3 D: 104451 Crypt: 70704
Pub: 7586899 Prime1: 1931 Prime2: 3929 Phi: 7581040 E: 3 D: 5054027 Crypt: 1092727
Pub: 3592189 Prime1: 1327 Prime2: 2707 Phi: 3588156 E: 5 D: 2870525 Crypt: 2947676
Pub: 10618541 Prime1: 2287 Prime2: 4643 Phi: 10611612 E: 5 D: 4244645 Crypt: 2730991
Pub: 1484851 Prime1: 1091 Prime2: 1361 Phi: 1482400 E: 3 D: 988267 Crypt: 36024
Pub: 423569 Prime1: 467 Prime2: 907 Phi: 422196 E: 5 D: 337757 Crypt: 14376
Solution: God could create the world in six days because he didn't have to make it compatible with the previous version. If we're supposed to work in Hex, why have we only got 0xA fingers?
root@kali-ts:~/hackvent/day17#

And we got our flag that generates our token in the Ball-o-Matic :)


Day 18:

not x86/x64 this time
Get your daily binary here:  http://hackvent.hacking-lab.com/notX86

The file provided is a Mach-o executable. To make things worse, it's also compiled for ARM, which makes it impossible to test it using a MacOSX virtual machine.

However we can still open it on IDA and understand that the logic of the executable is pretty simple.
It reads a string from the user, makes some calculations and then compares the calculated value with one pre-established.
If it matches, it shows "Congrats, your key is..." otherwise it shows "no luck this time".
We can see that in the address 0x000022D4 there is a BNE which is the equivalent to JNE instruction in x86, right after a CMP op code. This is clearly an IF clause.
If we could run the program we could just switch this BNE to a BE and input any string would make it jump to the success phrase.


Since we cannot run it, we need to understand what are the calculations about. For this we can use an IDA plugin called ARM Decompiler.
This plugin is capable of translating very well the op-codes back to C code, which is awesome. By selecting the option "Produce file->Create C file", the plugin gives us a very nice C file will all the logic inside.


We can see that the logic is pretty simple and the algorithm is very easily revertable. We just need to calculate what are the v3 and v4 variables. We can use the provided C file to create a new one just to give us this:

#include <stdio.h>

int main(void)
{
  signed int v0;
  int v3;
  int v4;

  v0 = -559038242;
  v4 = (v0 ^ 0xBABEF00D) >> 1;
  v3 = v4;
  v4 = (v4 >> 2) & 0xFFFF;
  v3 = (v3 >> 3) & 0xFFFF;
  printf("Here's your token on a silver plater: XMAS-%x-%x-FUN\n", v4, v3);

  return 0;
}

Then we compile it and run it:

root@kali-ts:~/hackvent/day-ios# gcc -o decompiled_main decompiled_main.c
root@kali-ts:~/hackvent/day-ios# ./decompiled_main
Here's your token on a silver plater: XMAS-661a-330d-FUN

And we got our ball-o-matic token


Day 19:

Basic Steganography

putting the bits together



Steganography... So we know that our token is hidden somewhere in the image

the formula !r & (g ^ b) gives us what kind of manipulation we need to do to our image with the componentes r(ed), g(reen) and b(lue)

The name of the image is StegBool.png - this is also a hint

Basically if we pick each pixel and apply the formula, the result will be either True or False since we are using logical operators.

So lets create a script that for each pixel does this calculation and only print out the pixels that return True.
To make it more clear, we will output white for True and black for False


#!/usr/bin/python

import Image
import sys
import re

if(len(sys.argv)<2):
        print "Syntax is " + sys.argv[0] + " <image_file_to_analyze> <output_file>\n"
        print "\timagefile - Image file to analyze"
        print "\toutfile - Image with the result transformation"
        exit()

imagefile=sys.argv[1]
outfile=sys.argv[2]

print >> sys.stderr, "Loading image..."
im = Image.open(imagefile) #Can be many different formats.
pix = im.load()
print >> sys.stderr, "Image OK! Size: " + `im.size[0]` + "x"+`im.size[1]`+"\nProcessing...\n"
imgout = Image.new("RGB", (im.size[0], im.size[1]))
for width in range(im.size[0]):
        for height in range(im.size[1]):
                comps = pix[width,height]
                formula =  ~comps[0] & (comps[1]^comps[2])
                formula = formula & 1; #get the LSB of formula value
                #because formula will be 0 or 1, when it's 1, we will change it to the max value so that it's visible
                if(formula==0):
                        imgout.putpixel((width,height),(0,0,0))
                else:
                        imgout.putpixel((width,height),(255,255,255))

imgout.save(outfile)
print "Image ",outfile,"saved!"


And lets run it:
root@kali-ts:~/hackvent/day19# ./image_lsb_extractor.py stegbool.png result.png
Loading image...
Image OK! Size: 640x452
Processing...

Image  result.png saved!

And the result is this:


Here it is... Our QR code =)

day 20:

http://riddler.php
Completely automated riddling to tell Computers and Humans apart 

Follow this link and do some math:  http://hackvent.hacking-lab.com/1JnjqflWseX_29Ow-YXH/

Following the link takes us to a page that only shows an image with some math where we need to find the results. Each time we refresh the page, the image is different.



If we catch all HTTP traffic with an intercepting proxy tool like Burp, we can see that there are some hints in the headers in the response:

X-Riddler: INFO
X-Riddler-Howto: I will reward you with a nice christmas ball if you solve 30 of my riddles in just a minute. Just send me back my cookies and POST your answer as 'result'. However, if one of your answers is wrong, you'll have to start over.

So we need two things... do the OCR scanning of the image and then develop a script that will handle the POST submits, error handling, etc

For OCR, I tried to use Tesseract which is the de-facto tool in linux, but it failed to recognize the characters without any "training" first.
So I changed to GOCR, which is really awesome... It can recognize the text very accurately with no special configurations whatsoever.

So I combined everything in the following Python script:

import requests
from PIL import Image
from StringIO import StringIO
import subprocess
import sys
import os
import re

tempfilename="_temp.gif"
chunk_size=200
ans=0
phpsessid=""
cookies=""
params=""
for i in range(1,100):
        r=None
        if(ans==0):
                r = requests.get('http://hackvent.hacking-lab.com/1JnjqflWseX_29Ow-YXH/');
                phpsessid = r.cookies['PHPSESSID']
                print "SESSIONID: ",phpsessid
        else:
                r = requests.post('http://hackvent.hacking-lab.com/1JnjqflWseX_29Ow-YXH/', data=params, cookies=cookies)
                print "server returned content type: " + r.headers['content-type']

                if(r.headers['content-type'].find("gif")>0):
                        print "Result is a GIF!"
                elif(re.match("Wrong",r.content)):
                        print "Got wrong answer from server... Restarting..."
                        ans=0
                        continue
                else:
                        print "Got unknown response from server. Maybe the answer? Dumping it..."
                        with open("finalresult.png", 'wb') as fd:
                                fd.write(r.content)
                        exit()


        with open(tempfilename, 'wb') as fd:
                for chunk in r.iter_content(chunk_size):
                        fd.write(chunk)


        image = Image.open(tempfilename)
        if len(image.split()) == 4:
                # In case we have 4 channels, lets discard the Alpha.
                # Kind of a hack, should fix in the future some time.
                r, g, b, a = image.split()
                image = Image.merge("RGB", (r, g, b))
        nx, ny = image.size
        image.save("temp.png")
        parsed = subprocess.check_output(["gocr", "temp.png"])
        parsed = parsed.replace("=","").replace("?","").replace("x","*") # fine tunning the OCR
        print "Evaluating... ",parsed

        result = eval(parsed)
        print "Result: ",result

        print "Sending post:"
        params = {"result": result}
        cookies = dict(PHPSESSID=phpsessid)
        ans=1


And running it:

root@henshin:~/hackvent/day20# python captcha_decoder.py
SESSIONID:  q1l38oe2d17e197ciurrnmrnu0
Evaluating...  55218 * 47297 - 51114 * 45138 

Result:  304462014
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  21350 * 47540 + 11795 * 13007 

Result:  1168396565
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  6381 + 53172 - 17012 - 2571 

Result:  39970
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  37691 * 13322 - 21070 + 36534 

Result:  502134966
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  28575 * 30088 + 33410 + 39135 

Result:  859837145
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  49747 + 23534 * 30159 * 4566 

Result:  3240772912543
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  9408 + 32569 + 32937 * 43224 

Result:  1423710865
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  29752 + 51751 * 15153 * 27371 

Result:  21463870267765
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  42666 + 818 - 43603 - 55545 

Result:  -55664
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  24602 - 45586 * 40838 + 729 

Result:  -1861615737
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  50371 * 19539 + 56701 + 12660 

Result:  984268330
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  4238 * 27893 * 20258 - 14229 

Result:  2394708983543
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  38882 + 31711 * 5466 * 34748 

Result:  6022951702730
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  23792 + 27796 + 1942 - 25810 

Result:  27720
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  44184 - 59047 + 13758 + 59831 

Result:  58726
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  16358 - 17099 + 24168 + 59672 

Result:  83099
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  24241 - 23242 - 57149 * 10836 

Result:  -619265565
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  49634 - 32117 - 30678 * 59865 

Result:  -1836520953
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  55366 - 58828 * 10471 - 35436 

Result:  -615968058
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  34181 * 24249 + 24125 + 13289 

Result:  828892483
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  21133 * 9930 - 43839 - 19758 

Result:  209787093
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  26279 * 14817 - 44948 - 44323 

Result:  389286672
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  41032 * 38989 + 12437 + 58629 

Result:  1599867714
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  15143 - 58320 * 38403 - 11766 

Result:  -2239659583
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  5016 - 54588 * 46202 * 59105 

Result:  -149067229630464
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  53378 - 55711 * 43421 * 1665 

Result:  -4027680452737
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  40708 * 23600 + 31586 * 7586 

Result:  1200320196
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  42933 + 9277 + 521 - 7384 

Result:  45347
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  30630 * 12074 - 33491 * 35328 

Result:  -813343428
Sending post:
server returned content type: image/gif
Result is a GIF!
Evaluating...  58015 * 57608 - 52914 - 29484 

Result:  3342045722
Sending post:
server returned content type: image/png
Got unknown response from server. Maybe the answer? Dumping it...

The script generates a file called finalresult.png which is our QR code image, so lets scan it:

root@henshin:~/hackvent/day20# zbarimg finalresult.png
QR-Code:HV14-hyUZ-Km4a-jE6u-G1K0-RPT1
scanned 1 barcode symbols from 1 images in 0.08 seconds

And there it is our token.

Day 21:

Fair gambling 

break the bank 
Follow the link and play that game:  http://hackvent.hacking-lab.com/iKlrDE49dCjFi17oDYzs/

Following the link takes us to a page where it's written:

Fair Gambling Christmas Casino
Let's play a game of number guessing. I'll think of a number from 0 to 99 (inclusive). You start out with $100. To buy some awesome christmas presents for your frinds, you'll need $10000. Each correct guess triples your bet. Good Luck!
This game is guaranteed to be fair. Your random numbers are generated by a Linear Congruental Generator using the following parameters

• modulus = 2^31
• multiplier = 1664525
• increment = 1013904223

Your personal seed will be disclosed when you stop playing, so you can verify we didn't cheat you.
Let's get started!


Clicking on "Lets get started" take us to a betting page.



So we have to break the Linear Congruental Generator. Actually Linear Congruental is pretty hard to break if the seed is very high.
Since the initial seed must be always less than the modulus, we know that the max value for the seed is 2^31 = 2147483648

CPU wise, this is not a very tough number to crack.

I started by making a series of games and click the "Stop playing" button to see what were the seeds generated. The idea was to try to check which was the lower bound for seeds.

Here's the list of the seeds I've got from several runs:

693185091
562459729
443921726
400792555
372742788
273381562
269822229
141631621
2094758365
1989613124
1867004465
1828102337
1814917411
1752974585
1683911525
1667166901
1633827251
1619166369
1419363237
1354214219
1334636276
1260328231
1247849209
1129460945
1092758660
1009961900

The lowest number was 141631621, so i assumed that a decent starting value was 100000000

This gives us 2047483648 possible seeds. It's a large number but modern CPUs will crunch this quickly, so I've developed a brute force script in Python to take care of it.

The idea was to create a list of a good amount of rolls during bets and set the script to find which was the seed that generated them.

So I started playing the game betting only $1 in each round and the rolls were 85,28,99,70,41,72,15,90,29,64,55,70,85,32,7,2,25,28.
At this point we should have enough information to feed the script with.

#!/usr/bin/python

modulus = 2**31 # = 2147483648
multiplier = 1664525
increment = 1013904223

# Xn = (aXn-1 + c) mod m
#where
# m = modulus
# a = multiplier
# c = increment

#target roll
rolls = (85,28,99,70,41,72,15,90,29,64,55,70,85,32,7,2,25,28)

def jsw_lcg(prevseed):
        seed = (multiplier * prevseed + increment) % modulus
        return seed

print "\nCracking LCG using bruteforce based on specified rolls...\n"

initialseed = 100000000

i=initialseed

seed = 0
step = 0
while (i < modulus):
        seed = i
        reseed = seed
        #print "seed: ", seed
        for roll in range(0,len(rolls)):
                #print "checking seed",seed,"on roll",roll,"which is",rolls[roll]
                value = jsw_lcg(reseed)
                #print "got value:",value,"(", value % 100,")"
                if(rolls[roll]==value % 100):
                        step+=1
                        reseed = value
                else:
                        step=0
                        #print "Failed after",roll,"tries"
                        break

        if(step==len(rolls)):
                print "Yeah!! Got full sequence with seed: ", seed
                break
        i+=1

print "Predicting first 50 values of the sequence... "
print "Sequence should be:"
for i in range(1,50):
        #print "Seed: ", seed
        seed = jsw_lcg(seed)
        print str(seed%100)+",",

print "\n\nDone. Now it's time to profit $$$$!\n"

And then I've ran the script... It took around 10-15 min to get the seed :)

root@kali-ts:~/hackvent/day21# ./congruential_generator.py

Cracking LCG using bruteforce based on specified rolls...

Yeah!! Got full sequence with seed:  979875738
Predicting first 50 values of the sequence...
Sequence should be:
85, 28, 99, 70, 41, 72, 15, 90, 29, 64, 55, 70, 85, 32, 7, 2, 25, 28, 43, 62, 17, 36, 51, 78, 53, 56, 55, 50, 37, 24, 95, 38, 45, 52, 67, 18, 81, 20, 51, 18, 77, 56, 43, 58, 33, 48, 19, 50, 53,

Done. Now it's time to profit $$$$!

The script dumps the complete sequence for the first 50 rolls. At this point we could bet with confidence on which are the next numbers to come out until we get the 10000$. At that point we can just stop the game and it would show us the following message:


Awesome! You did it, you really won this $10k! Are you going to buy <QR code> now?


And we got our QR code


Day 22:

xmas labyrinth 
find the hidden message 
Get your daily job here:  http://hackvent.hacking-lab.com/YkZg9Fn5I29coGRi2e8q

Now we've reached the very hard challenges =)

I started by analyzing the file:

root@henshin:~/hackvent/day22# file YkZg9Fn5I29coGRi2e8q
YkZg9Fn5I29coGRi2e8q: x86 boot sector; partition 2: ID=0x6f, starthead 13, startsector 1818845510, 224752245 sectors, MS or PC-DOS bootloader IO.SYS+MSDOS.SYS, code offset 0x34, OEM-ID "ckRfZm9y", sectors/cluster 2, root entries 240, sectors 5760 (volumes <=32 MB) , sectors/FAT 9, dos < 4.0 BootSector (0x0)

So it seems to be a DOS partition. So lets try to mount it:

root@henshin:~/hackvent/day22# mkdir dos
root@henshin:~/hackvent/day22# mount -o loop -t msdos YkZg9Fn5I29coGRi2e8q dos/
root@henshin:~/hackvent/day22# cd dos/
root@henshin:~/hackvent/day22/dos# ll
total 1471
drwxr-xr-x 2 root root    7680 Jan  1  1970 .
drwxr-xr-x 3 root root    4096 Dec 21 23:06 ..
-rwxr-xr-x 1 root root    4107 Feb  2  1988 autoexec.bat
-rwxr-xr-x 1 root root   25308 Feb  2  1988 command.com
-rwxr-xr-x 1 root root     284 Dec 14 10:05 config.sys
-rwxr-xr-x 1 root root  151758 Feb  2  1988 dos.exe
-rwxr-xr-x 1 root root 1179426 Feb  2  1988 explorer.exe
-rwxr-xr-x 1 root root   55029 Aug  8  1988 fdisk.com
-rwxr-xr-x 1 root root   11968 Jul 13  1988 format.com
-r-xr-xr-x 1 root root   25613 Jul 18  1988 io.sys
-r-xr-xr-x 1 root root   30128 Jul 24  1987 msdos.sys
-rwxr-xr-x 1 root root    4921 Jul 13  1988 sys.com
root@henshin:~/hackvent/day22/dos#


Nice!
So lets check each one of these files:

root@henshin:~/hackvent/day22/dos# for f in `ls *.*`; do file $f; done
autoexec.bat: data
command.com: DOS executable (COM)
config.sys: Zip archive data, at least v2.0 to extract
dos.exe: PC bitmap, Windows 3.x format, 171 x 294 x 24
explorer.exe: JPEG image data, JFIF standard 1.01, comment: "CREATOR: gd-jpeg v1.0 (using IJG JPEG v62), quality = 80"
fdisk.com: DOS executable (COM)
format.com: DOS executable (COM)
io.sys: data
msdos.sys: DOS executable (COM)
sys.com: DOS executable (COM)
root@henshin:~/hackvent/day22/dos#

We got some interesting results:
- Config.sys is actually a zip file
- explorer.exe is actually a JPEG image
- dos.exe is actually a BMP image
- autoexec.bat is suspicious because it should be plaintext (batch script) and it is marked as data

Trying to unzip the config.sys asks for a password:

root@henshin:~/hackvent/day22/dos# 7z e config.sys  

7-Zip [64] 9.20  Copyright (c) 1999-2010 Igor Pavlov  2010-11-18
p7zip Version 9.20 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,1 CPU)

Processing archive: config.sys

Extracting  howto.txt
Enter password (will not be echoed) :
     Data Error in encrypted file. Wrong password?

Sub items Errors: 1

So this is actually what we need to find.

Let's check for hidden information on the JPEG file using stegdetect:
root@henshin:~/hackvent/day22/dos# stegdetect explorer.jpg
explorer.jpg : appended(8)<[random][ASCII text, with no line terminators][X1oxcA==]>

Interesting. It detected some extra chars at the end of the file that look like Base64. Lets decode it:

root@kali-ts:~/hackvent/day22/dos# echo -n X1oxcA== | base64 -d && echo ""
_Z1p

So it seems that this is a piece of the password. We need to find the others.

Looking back to our first 'file' command shows something interesting:

YkZg9Fn5I29coGRi2e8q: x86 boot sector; partition 2: ID=0x6f, starthead 13, startsector 1818845510, 224752245 sectors, MS or PC-DOS bootloader IO.SYS+MSDOS.SYS, code offset 0x34, OEM-ID "ckRfZm9y", sectors/cluster 2, root entries 240, sectors 5760 (volumes <=32 MB) , sectors/FAT 9, dos < 4.0 BootSector (0x0)

OEM-ID "ckRfZm9y"? interesting... It looks just like BASE64 as well:

root@henshin:~/hackvent/day22/dos# echo -n ckRfZm9y | base64 -d && echo ""
rD_for

Good :) so we have two pieces
rD_for and _Z1p

If we glue them together we get: rD_for_Z1p
So we can predict that the first part is "Passwo" in leet speak... We could try to brute force it now, but the first part must be here somewhere.

Since we are looking for Base64 strings, I've developed a script in Perl that checks for base64 strings, decodes them and see if they are valid printable characteres.


#!/usr/bin/perl

use MIME::Base64;

my $program;
($program = $0) =~ s#.*/##;
print "\n--== Henshin's BASE64 scrapper ==--\n\n";
if(scalar(@ARGV)<1){
        print "Syntax: $0 <file_to_scan>\n\n";
        exit -1;
}

$file = $ARGV[0];

if(! -f $file){
        print "Can't access file $file\n\n";
        exit -1;
}

if(-z $file || !open(FILE,'<', $file)){
        die("Error: Can't open the file '$file'! ($!)\n\n");
}
$count=0;
@found = undef;
while( my $line = <FILE>){
        @matches = $line=~/([\x2b\x2f-\x39\x3d\x41-\x5a\x61-\x7a]{4,})/g;
        for my $match (@matches){
                $match =~s/(\w+=+).+/\1/;
                next if (length($match) % 4 != 0); #size must be multiple of 4
                next if ($match ~~ @found); #ignore if we already found it
                $decoded = decode_base64($match);
                $decoded=~s/\r|\n//g;
                next if (length($decoded)<5);
                next if ($decoded!~/^[\x20-\x7E]+$/); # result should be printable ascii
                printf("[+] %-15s ==> '%s'\n",$match,$decoded);
                push(@found,$match);
                $count++;
        }
}
close (FILE);
print "\nFound $count possible Base64 strings!\n\n";


So lets use it to search the whole file.

root@henshin:~/scripts# ./base64_scrapper.pl ../hackvent/day22/YkZg9Fn5I29coGRi2e8q

--== Henshin's BASE64 scrapper ==--

[+] ckRfZm9y        ==> 'rD_for'
[+] UDRzc3cw        ==> 'P4ssw0'

Found 2 possible Base64 strings!

Here it is. Investigating a bit further shows that the last piece is the name assigned to the partition when it is mounted on Windows. We could use a software like OSFMount to confirm this

So now we have the whole password to unzip the config.sys


Before unzipping I checked what's going on with the autoexec.bat.
Looking at file in hex, shows that it starts with 'GIF66' which is a clear indication of being an GIF after all.
However most image editors dont seem to be able to open the image, probably because some information is missing or corrupted.
I used Adobe Fireworks and it loaded fine.


The image is a bar code. So lets convert this to a valid format image like PNG and decode it with zbarimg utility.

root@henshin:~/hackvent/day22/dos# zbarimg autoexec.png
CODE-128:UNIXisuserfriendly
scanned 1 barcode symbols from 1 images in 0.02 seconds

Cool it seems to be one more password for something... We'll check it out later.

For now we unzip the config.sys file:

root@henshin:~/hackvent/day22# 7z e -oFinalstep -pP4ssw0rD_for_Z1p dos/config.sys

7-Zip [64] 9.20  Copyright (c) 1999-2010 Igor Pavlov  2010-11-18
p7zip Version 9.20 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,1 CPU)

Processing archive: dos/config.sys

Extracting  howto.txt

Everything is Ok

Size:       138
Compressed: 284

Lets check inside:

root@henshin:~/hackvent/day22# cd Finalstep/
root@henshin:~/hackvent/day22/Finalstep# ll
total 12
drwx------ 2 root root 4096 Dec 28 23:20 .
drwxr-xr-x 4 root root 4096 Dec 28 23:20 ..
-rw-r--r-- 1 root root  138 Dec 14 10:03 howto.txt
root@henshin:~/hackvent/day22/Finalstep# cat howto.txt
ElGamal

Public Key:

p=CFF3829FE2BC008D
g=2367CA6FE33CF1A9
y=42F357F7636AA02F

Message:

a=7D3BDC843CE75CD3
b=275E625204563FAC


ElGamal is a public key method that is used in both encryption and digital signing. It is used in many applications and uses discrete logarithms. At the root is the generation of P which is a prime number and G (which is a value between 1 and P-1) 

The formula for generating the private key is:
Y = G^x mod P

in which:
P - prime number 
G - number between 1 and P-1
x - Random number -> private key
Y - public key

The variables P, G and Y are public [P,G,Y]. The private key is x

So mathematically is pretty complex to get x having Y,P and G
However this is the discreet logarithm problem and there are very optmized ways of calculating this number.

After searching online a bit I've found this site called MAGMA (http://magma.maths.usyd.edu.au/calc/), a similar tool to Wolfram Alpha and with some very nice features regarding prime numbers and big numbers handling.

So we convert our numbers to integers and feed the calculator with our data:

p := 14984464008407154829;
K := GF(p);
K;
g := K ! 2551230295831277993;
y := K ! 4824296345880404015;
x := Log(g, y);
x;

And it returns the K and x variables instantly:

K = Finite field of size 14984464008407154829
x = 3888521305394705767


Thats it, we got our x so we possess the private key now. This means that we can now encrypt and decrypt messages.

For this I created a small python script using the Elgamal library:


#!/usr/bin/python

from Crypto.PublicKey import ElGamal

p=int('CFF3829FE2BC008D',16)
g=int('2367CA6FE33CF1A9',16)
y=int('42F357F7636AA02F',16)
a=int('7D3BDC843CE75CD3',16)
b=int('275E625204563FAC',16)
print "P:",p
print "G:",g
print "Y:",y
print "A:",a
print "B:",b

#X and K calculated from MAGMA
x=3888521305394705767
k=14984464008407154829

gamal = ElGamal.construct((p,g,y,x))


print "X:",x
print "K:",k

print "Decrypting..."
res = gamal.decrypt((0x7D3BDC843CE75CD3,0x275E625204563FAC))
print "Result:",res,"-->",hex(res)

and runnning it:

root@kali-ts:~/hackvent/day22/Finalstep# ./crypto_breaker.py
P: 14984464008407154829
G: 2551230295831277993
Y: 4824296345880404015
A: 9024048738882510035
B: 2836812919689592748
X: 3888521305394705767
K: 14984464008407154829
Decrypting...
Result: 6013542897370025543 --> 0x537465676f504e47L

Great

So our hex string '537465676f504e47' looks like it has ascii printable chars, lets decode it:

537465676f504e47 -> StegoPNG

StegoPNG? Searching in Google returns a software with that name

I download it and open it.
Now we have some things we didn't used yet... Our BMP image, and our password 'UNIXisuserfriendly'

So lets try to extract information from the BMP with this password using this software.



Good, no errors...

Lets check the output. It's not an image at all, it's a text file that says:

'Don't code today what you can't debug tomorrow'


That's our flag for day 22!


Day 23:

Today, debugging and crypto fun 
Get your daily job here:  http://hackvent.hacking-lab.com/VFucbZz83LIYuRuuGErL.exe

I fired up Immunity debugger and started my analysis.

First thing i checked was that placing breakpoints in certain addresses of the program caused an immediate termination of the process. It means that there are some anti-debugging protections in place.
If the process is terminating it means that there must be some part of the code that tells it to Exit.
So we will need to locate ExitProcess calls that happen after some comparison.

We can easily find them looking at immunity


As we can see, at 0x00402506 and 0x0040252E there is a comparison and then the code jumps if the condition is true, otherwise it prepares the ExitProcess calls.

So to bypass the anti-debugging mechanisms we could simply change the JE to JMP on 0x0040250C and 0x00402534, making the code jump inconditionally, thus avoiding the ExitProcess calls completely

To make things easier, we can produce a new executable and continue the debugging process through it.

Without the anti-debugger mechanism, we can now check closely what's going on with this executable.


This part is interesting because it is here that the program receives the user input and even more interesting is the marked CMP instruction on 0x00402467. It's checking wether the size is 1D=29 chars or not. This means that it is expecting a TOKEN of the type HV14-XXXX-XXXX-XXXX-XXXX-XXXX

So lets give it a random one and see what's going on... After validating the string size, it checks if the dashes in the Token are in the right places by comparing the string positions with 2D, the hifen character '-'


If the dashes are in the right places the code jumps to the part where the "ciphered" string is and it begins the decoding...


We can see the decoded phrase starting to appear in the ESP registry.
Once the decoding is finished we can read "thats it haxx0r. now enter the code to get your points".

We're not so interested in this decoding part because it doesn't help us getting our flag, but once the string is decoded, the real interesting part starts.



This is where all the magic happens.
According to the online docs, the REPE means string instruction repetition.

"When specifying the repeat prefix before a string instruction, the string instruction repeats cx times. Without the repeat prefix, the instruction operates only on a single byte, word, or double word."

So there is a string operation done CX times. Our CX has 1D which is 29 in decimal... So we are on the right track, we are checking if each byte of the flag is correct.
Furthermore we can give a closer look to what the REPE is comparing...


We see that the REPE is being applied between ESI and EDI registers. It means that one of them is our input and the other is the FLAG =)
On Immunity we can check the values of each one by right-clicking the address in the window below the ASM and choose "Follow address in Dump"
However the tokens are ciphered as we can see in the image above. The string starts with 9AFC2E.... which is not plain ASCII.

Now we can make some assumptions:
 - the tokens are ciphered and have the same size as the original plaintext
 - both the target ciphered token and my inputed token start with the same 4 chars (9A FC 2E 3C FF)

This information is enough to assume that there is a XOR operation applied to the tokens.
Assuming this and seeing that we can produce a ciphered text from a plaintext that we know, we can revert the XOR operation and obtain the plaintext of the target token.

CIPHER = KEY xor PLAIN

Having the PLAIN and the CIPHER we can extract the key:
KEY = CIPHER xor PLAIN

Having the KEY we can cipher/decipher everything that we want:
TOKEN = KEY xor CIPHERTOKEN


So lets recap, we can extract the ciphertoken from the memory:

CIPHERTOKEN: 9AFC2E3CFF82985BB1DD5815DEC0D39EF284318DC1708740A57479A2C6

And we have this as input:
PLAIN: HV14-ctc2-jiPs-Y321-Pp9J-9Rxr
Which generates the following ciphertext:
CIPHER: 9AFC2E3CFF90DC7EB5DD452DEF84D3F49BC2358DC639D865A57F7A8AD9

With a small Perl script to help us, we can get the original Token in plaintext instantly:


#!/usr/bin/perl

$plain = unpack("H*",'HV14-ctc2-jiPs-Y321-Pp9J-9Rxr');
print "PLAIN: $plain\n";
$cipher = '9AFC2E3CFF90DC7EB5DD452DEF84D3F49BC2358DC639D865A57F7A8AD9';
print "CIPHER: $cipher\n";

$key = unpack("H*",pack("H*",$plain) ^ pack("H*",$cipher));

print "KEY: $key\n";

$target = '9AFC2E3CFF82985BB1DD5815DEC0D39EF284318DC1708740A57479A2C6';
print "TARGET: $target\n\n";

print "RESULT: ",pack("H*",$target) ^ pack("H*",$key) ."\n\n";

And so when we run it:

root@kali-ts:~/hackvent/day23# ./perl_xor.pl
PLAIN: 485631342d637463322d6a6950732d593332312d5070394a2d39527872
CIPHER: 9AFC2E3CFF90DC7EB5DD452DEF84D3F49BC2358DC639D865A57F7A8AD9
KEY: d2aa1f08d2f3a81d87f02f44bff7feada8f004a09649e12f884628f2ab
TARGET: 9AFC2E3CFF82985BB1DD5815DEC0D39EF284318DC1708740A57479A2C6

RESULT: HV14-q0F6-wQa7-3Zt5-W9fo-2QPm


And there we have it, our well earned token :)


References:
bypass anti-debuggers: http://thelegendofrandom.com/blog/archives/2100


Day 24:

CrackMe - again 

but in parallel 

Get your daily job here:  http://hackvent.hacking-lab.com/BPLDdt3whlZ3r1I2opZf.zip

And we reached the final and hardest challenge of the CTF imo =)

The zip file contains versions of a binary to the 3 major platforms: Windows, Linux and MacOSX

Trying to execute the Windows binary just returns an error complaining about missing CUDA libraries.
I have a laptop with a AMD card, so at this point I know that I will never be able to run this executable :\

However I can open it in IDA and try to understand the logic inside. After reading a bit about CUDA, CUDAC and CUDA API, this is what i've extracted:

The program reads 30 characters (0x1E) from the keyboard


Allocates memory for 30 chars to pass to cuda function


Copies the content received from the keyboard to a cuda variable (host to device copy)


This is very important. It configures Cuda Call by setting the 2 DIM3 variables.


According to CUDA API, the 2 dim3 variables are gridDim and blockDim

Since arguments are pushed to the stack in reverse order, we assume that the program is calling the cudaConfigureCall with these parameters:
cudaConfigureCall((6,1,1), (5,1,1), size_t...)

dim3 arguments specify dimensions in 3 dimensions. Since our grid and block size have the Y and Z equal to 1, it means that they are unidimensional and can be thought of as a simple array

This means that the Grid dimension is 6 and the block dimension is 5.
(this will be very important in some steps ahead)

Now it pushes 4 bytes to the stack (address size), pass the address at EAX and call setup argument


Finally it calls the CUDA function.


After the Cuda function returns, it copies the memory from the device back to the host



And now we loop each byte of the returned value and check if it's 0x4D = 77 = 'M'. It loops until ECX is less than 1E = 30. This is basically a FOR loop.


So for success, the cuda function needs to return 30 'M' chars...

Now what's left to do is to find and reverse the Cuda function.
For this I used the CUDA utilities to extract the PTX code from the binary.

There's no need to install the whole 1GB of CUDA libraries. Fortunattly, it's possible to extract files directly from the installer.
So i've only extracted the Toolkit in which the utilities are found (you still need the Visual Studio compilation tools though)

Let's check what we can extract:
D:\Downloads\2014-12\cuda_6.5.14_windows_general_64\CUDAToolkit\bin>cuobjdump.exe --list-ptx c:\Users\ts\Desktop\hackvent\day24\crackme-in-parallel.exe
PTX file    1: visual-studio-kernel.sm_20.ptx

Good, it identified a PTX section within the EXE file. Let's extract it:


D:\Downloads\2014-12\cuda_6.5.14_windows_general_64\CUDAToolkit\bin>cuobjdump.exe --extract-ptx all c:\Users\ts\Desktop\hackvent\day24\crackme-in-parallel.exe
Extracting PTX file and ptxas options    1: visual-studio-kernel.sm_20.ptx -arch=sm_20

Lets open our PTX file:

.version 4.1
.target sm_20
.address_size 32
.const .align 1 .b8 gpu[30] = {46, 47, 83, 79, 77, 84, 52, 17, 84, 76, 58, 39, 0, 23, 75, 46, 61, 18, 0, 82, 32, 40, 40, 10, 81, 53, 59, 16, 28, 101};
.visible .entry _Z5checkPh(
.param .u32 _Z5checkPh_param_0
)
{
.reg .s32 %r<17>;
ld.param.u32 %r1, [_Z5checkPh_param_0];
cvta.to.global.u32 %r2, %r1;
mov.u32 %r3, %ntid.x;                R3 = ntid (number of threads in each CTA dimension)
mov.u32 %r4, %ctaid.x;                R4 = ctaid -> Each CTA has a unique CTA identifier (ctaid) within a grid of CTAs
mov.u32 %r5, %tid.x;                R5 = tid --> thread position within CTA

mad.lo.s32 %r6, %r4, %r3, %r5;            R6 = R4 * R3 + R5 (unique ID?)
add.s32 %r7, %r2, %r6;                R7 = index + input
ld.global.u8 %r8, [%r7];            R8 = _Z5checkPh_param_0[index]

add.s32 %r9, %r8, %r4;                R9 = _Z5checkPh_param_0[index] + R4 (1?)

mov.u32 %r10, gpu;                R10 = gpu              
add.s32 %r11, %r10, %r6;            R11 = R10 + R6    
ld.const.u8 %r12, [%r11];            R12 = gpu[index]

add.s32 %r13, %r12, 1;                R13 = R12 + 1  -> gpu[index]+1
xor.b32 %r14, %r5, %r9;                R14 = R5 xor R9  -> tid xor _Z5checkPh_param_0[index] + 1
xor.b32 %r15, %r14, %r13;            R15 = R14 xor R13 -> _Z5checkPh_param_0[index] xor (gpu[index]+1)
xor.b32 %r16, %r15, 42;                R16 = R15 xor 42 -> and xor 42
st.global.u8 [%r7], %r16;            R7 = R16 (result)
ret;
}

Looks damn confusing, right?
Some things are complex, but we can still read part of it . I placed some comments from what I could figure out on the right part

The problem is that I still couldn't get a clear picture of the code behind it.
So what I set out to do was to reconstruct a C code function and keep fixing, compiling and comparing it with this PTX code until they were an exact match.
After some time debugging it, I've finally got this code:

REVERSE ENGINNERED CODE - codatest.cu

#include <stdio.h>

__constant__ char gpu[30] ={46, 47, 83, 79, 77, 84, 52, 17, 84, 76, 58, 39, 0, 23, 75, 46, 61, 18, 0, 82, 32, 40, 40, 10, 81, 53, 59, 16, 28, 101};
__global__ void checkPh( char* input)
{
    int tid = blockIdx.x * blockDim.x + threadIdx.x;
    int z = input[tid] + blockIdx.x;
    char b = gpu[tid];
    int d = b +1;
    int e = threadIdx.x ^ z;
    int f = e ^ d;
    int g = f ^42;
    input[tid] = g;
    return;
}


int main(void){
    int numBlocks = 6; //this is the grid size (6,1,1)
    char* parameter = "HV14-THIS-WOULD-BE--THE--TOKEN";
    char* cudaparam = "";
    dim3 threadsPerBlock(5, 1, 1); //.x, .y, .z as in ASM -> blockSize
    int paramsize = strlen(parameter); // = 30

    cudaMalloc((void **)cudaparam, paramsize);
    cudaMemcpy(cudaparam, parameter, paramsize, cudaMemcpyHostToDevice);
    checkPh<<<numBlocks, threadsPerBlock>>>(cudaparam);
    cudaMemcpy(parameter, cudaparam, paramsize, cudaMemcpyDeviceToHost);

    for(int i=0; i<strlen(parameter); i++){
        //Check if each char == 77 ("M")
    }
}

Although I included the main function for testing purposes, I'm only interested on the cuda function checkPh.

After setting up my very nice cudac file, I compile it using nvcc utility:

D:\Downloads\2014-12\cuda_6.5.14_windows_general_64\CUDAToolkit\bin>nvcc.exe -m 32 -o cudac_test -ptx c:\Users\ts\Desktop\hackvent\day24\cudac_test.cu

This produces a file called cudac_test which is 100% replica of the disassembled ptx code:

//
// Generated by NVIDIA NVVM Compiler
// Compiler built on Fri Jul 25 12:36:16 2014 (1406288176)
// Cuda compilation tools, release 6.5, V6.5.13
//

.version 4.1
.target sm_20
.address_size 32

.const .align 1 .b8 gpu[30] = {46, 47, 83, 79, 77, 84, 52, 17, 84, 76, 58, 39, 0, 23, 75, 46, 61, 18, 0, 82, 32, 40, 40, 10, 81, 53, 59, 16, 28, 101};

.visible .entry _Z7checkPhPc(
    .param .u32 _Z7checkPhPc_param_0
)
{
    .reg .s32     %r<17>;


    ld.param.u32     %r1, [_Z7checkPhPc_param_0];
    cvta.to.global.u32     %r2, %r1;
    mov.u32     %r3, %ctaid.x;
    mov.u32     %r4, %ntid.x;
    mov.u32     %r5, %tid.x;
    mad.lo.s32     %r6, %r4, %r3, %r5;
    add.s32     %r7, %r2, %r6;
    ld.global.u8     %r8, [%r7];
    add.s32     %r9, %r8, %r3;
    mov.u32     %r10, gpu;
    add.s32     %r11, %r10, %r6;
    ld.const.u8     %r12, [%r11];
    add.s32     %r13, %r12, 1;
    xor.b32      %r14, %r5, %r9;
    xor.b32      %r15, %r14, %r13;
    xor.b32      %r16, %r15, 42;
    st.global.u8     [%r2], %r16;
    ret;
}

Yeah, I've had a lot of work to get this but now since we have a perfect match, we have the exact code that runs through CUDA.
As we can see, the logic is not that hard. Just some XORs and simple additions.

We can easily produce a script to reverse the process:


#!/usr/bin/perl -w

@chars = (46, 47, 83, 79, 77, 84, 52, 17, 84, 76, 58, 39, 0, 23, 75, 46, 61, 18, 0, 82, 32, 40, 40, 10, 81, 53, 59, 16, 28, 101);

$threadsperblock = 5;
print "Threads per block: $threadsperblock\n";
$threadidx = 0;
$blockidx = 0;
$token="";
for ($i=0; $i < scalar(@chars)-1; $i++){
   

    $index = $blockidx * $threadsperblock + $threadidx;

    $d = $chars[$index]+1;
    $e = $threadidx ^  $d ^ 42 ;
    $e = $e ^ 77;     # if we XOR by 77, we will get the plaintext
    $e = $e - $blockidx;  # to compensate z = input[tid] + blockIdx.x;

    $token.=chr($e);
   
    #Cycle threads / blocks
    $threadidx++;
    if($threadidx==$threadsperblock){
        $threadidx = 0;
        $blockidx++;
    }

}
print "\nTOKEN: $token\n";

I've included in my script two important things.
- I'm XORing my character with 77 because that is what the char that it's expected when the CUDA function returns. This will get me the plaintext
- We need to subtract the blockidx to compensate the original code where z = input[tid] + blockIdx.x

So lets run it:

root@kali-ts:~/hackvent/day24# ./xor_bruteforcer.pl
Threads per block: 5

TOKEN: HV14-1Rv0-ZLbz-EUsb-BKHk-LUot

Done, we have the final token!

Happy Christmas! =)


References:
http://www.nvidia.com/docs/IO/116711/sc11-cuda-c-basics.pdf --> awesome pdf about cuda architecture
http://sbel.wisc.edu/Courses/ME964/2013/Lectures/lecture0925.pdf
http://docs.nvidia.com/cuda/parallel-thread-execution/#thread-hierarchy -> Good intro information
http://developer.download.nvidia.com/compute/cuda/4_1/rel/toolkit/docs/online/group__CUDART__EXECUTION_g19a7dd5a102b499c39f6a7648bec757a.html -> CUDA C API
http://docs.nvidia.com/cuda/cuda-binary-utilities/index.html


Closing note:

This CTF was really fun. It was a good idea to give out one problem each day. This enabled me to have something fun (and sometimes frustrating) to do everyday without affecting much of my sleep time :)
When I started this CTF, my objective was to reach the top 10. I was very glad to fulfil my objective after lots of hours of researching and reverse engineering code :)
Here's the final high score:

After submitting a writeup, we could get extra 5 points, which I did.
This is the final highscore table after write-up submission.

So I feel very very happy about my 6th place on this CTF! Actually it would be even less if we filter out the site OPs which knew the answers before hand (HaRdLoCk, DanMcFly, M).

I want to thank clviper and TheZakMan for collaborating with me solving these challenges. It's really fun to share ideas in these CTFs.






2 comments: