Bootstrap

BUUCTF题目Reverse & Pwn部分wp(持续更新)

Reverse & Pwn 常用工具和思路

IDA Pro

IDA Pro(Interactive Disassembler Professional)交互式反汇编器专业版,CTF比赛中Reverse & Pwn必备。

打开一个可执行文件前,应先用file命令或者DIE等工具,确定是32位还是64位,然后用相应的IDA工具打开可执行文件。

IDA界面默认有菜单栏、工具栏、导航带、函数窗口、IDA-View反汇编窗口、Hex-View十六进制显示、Import、Export窗口等部分。点击菜单Windows-Reset/Save/Load Desktop来重置、保存、加载IDA窗口布局,或者菜单View打开被关闭的窗口。 

IDA-View反汇编视图

有文本视图(推荐)和图形视图两种,按空格键切换。文本视图有跳转逻辑关系。

base64_encode 函数自定义了base64码表,返回前转换了字母大小写
文本视图有跳转逻辑关系

中文字符串不显示

如果反汇编的shift+F12字符串不显示中文,那么Options-General-Strings,修改 Default 8bit 的编码选项改为 gb2312 即可。

IDA快捷键汇总

F5反汇编。

TAB汇编代码和伪代码之间切换。

空格键,在IDA-View反汇编视图,文本视图、图形视图切换。

Alt+T,搜索,就是search的搜索。

shift+F12,打开字符串视图。

ctrl+X,跳转到字符串被程序使用的位置。

X查看函数在哪被调用。

G在IDA-View的文本视图里跳转到指定地址位置。

ctrl+W,保存ida数据库,就是保存当前工作进度。
 

N变量、函数重命名。

Y变量、函数重新定义类型。

shift+F1,定义局部变量。结合Y键重新定义变量、函数的类型,在反汇编,辅助逆向分析。

Alt+Q,以结构体形式分析数据。

shift+E,按照不同要求显示字符串,可以避免大端序、小端序的困扰。

R和H,R将数字显示为字符,H将字符串显示为数字(可在十进制和十六进制切换)。

A将数据转换为字符串。

ACDUP对数据操作

  • A以字符串形式显示数据。
  • D以1、2、4、8个字节显示数据。x86为例db、dw、dd、dq(byte、word、double word、quadra word)。mips为例byte、halfword、word分别是1、2、4字节。
  • U取消函数、代码、数据定义。
  • P定义一个函数头,打patch很有用。
  • C以代码显示。

“/”对C伪代码注释。

“;”分号汇编注释(伴随),所有call、jump到这里的位置都会显示注释。

“:”冒号汇编注释,普通的湖边注释。

未完待续……

gdb

布局(layout)

  • 设置分屏布局:                         tui layout split
  • 设置焦点为源代码窗口:           tui layout focus src
  • 设置当前焦点窗口为汇编窗口:tui layout asm

断点(breakpoint)

  • 给函数下断点: b main
  • 给地址下断点: b *0x0800401A

汇编指令(assemble instruction)

  • 设置反汇编模式,这里设置为 Intel 格式: set disassembly-flavor intel 
  • 设置打印出下一条要执行的汇编代码:      set disassemble-next-line on
  • 单条汇编指令执行:                                   si 或 ni

查看数据

查看0xffffcff0开始的,32×4字节的内存内容:   x /32x 0xffffcff0

还可以以十进制d、指令i等形式查看,具体看 help x。

OllyDBG(OD)

OD是一种动态汇编调试工具。这里只讲一般用法,见下表。

搜索ASCII

拖入程序后,找目标字符串。在汇编指令的空白处右键-中文搜索引擎-搜索ASCII。搜索ASCII找到目标字符串,双击跳转到有该字符串的汇编指令地址。

汇编指令的空白处右键-中文搜索引擎-搜索ASCII
搜索ASCII找到目标字符串,双击跳转到有该字符串的汇编指令地址。

编辑立即数

在有该字符串的汇编指令地址上右键-数据窗口中跟随-立即数,跳转到数据窗口中。数据窗口中选中要修改的字符串,按空格弹出窗口编辑数据。

有该字符串的汇编指令地址上右键-数据窗口中跟随-立即数
数据窗口中选中要修改的字符串,按空格弹出窗口编辑数据

执行查看结果,hello world. 改成了learningctf!

重置调试

程序运行结束,状态为“已终止”,可以点击重置键,重新开始调试。

重置

断点

按F2或者在汇编指令的地址处双击,可以下断点,断点处地址为红色背景色。

按F2或者在汇编指令的地址处双击,可以下断点

pyinstxtractor+uncompyle6

截止2023.06.22最新版本的uncompyle6支持python 3.11.x版本,uncompyle6 和 xdis都推荐编译安装,安装后即可使用。

git clone [email protected]:rocky/python-uncompyle6.git
cd python-uncompyle6
sudo python setup.py install
 
git clone [email protected]:rocky/python-xdis.git
cd python-xdis
sudo python setup.py install

jadx

jadx是java、安卓apk的反编译工具。

Reverse部分

easyre

64位exe,IDA64直接F5反汇编,看到逻辑是输入相等的两个整数打印flag,得到flag{this_Is_a_EaSyRe}

reverse1

64位exe,IDA64直接F5反汇编,未找到main,找到start,一层层进入直到sub_1400118C0()函数,发现字符串Str2内容是{hello_world},但是字母o都被改成数字0。输入字符串Str1与修改后的Str2比较。答案是flag{hell0_w0rld}

reverse1主要逻辑

reverse2

64位elf,IDA64直接F5反汇编,直奔main函数,发现字符串flag为{hacking_for_fun},但是经过替换,字母i或r都替换为数字1,输入字符串s2与修改后的flag比较。答案是flag{hack1ng_fo1_fun}

reverse2主要逻辑

内涵的软件

图片有内涵,exe也可以有内涵,也许你等不到答案,赶快行动起来吧!!! 注意:得到的 flag 请包上 flag{} 提交。32位exe,IDA直接F5反汇编,直奔main函数,逻辑上没有意义,直接提交flag{49d3c93df25caad81232130f3d2ebfad}

内涵的软件 没啥逻辑

新年快乐

32位exe,upx加过壳。upx去壳后直接F5反汇编,直奔main函数,逻辑比较简单,答案是flag{HappyNewYear!}

新年快乐主要逻辑

xor

64位MacOS程序,IDA64直接F5反汇编,直奔main函数。flag字符串从第[1]位起逐位与前一位异或,结果与global比较。找到global对应的值,反向操作,写个简单脚本得到答案flag{QianQiuWanDai_YiTongJiangHu}

c = bytes.fromhex(
    '660A6B0C77264F2E4011780D5A3B55117019461F76224D23440E6706680F47324F00')
print(c)
m = bytearray(len(c))
for i in range(len(c) - 1, 0, -1):
    m[i] = c[i] ^ c[i - 1]
m[0]=c[0]
print(m)
xor主要逻辑

helloword

安卓apk程序。使用jadx反编译,在MainActivity里面找到flag。答案是flag{7631a988259a00816deda84afb29430a}

reverse3

32位exe,IDA64直接F5反汇编,直奔main函数。输入的Str被base64编码,base64编码逐位加上index,结果应该和Str2内容一致。那么写个简单脚本就可以反向操作得到答案 flag{i_l0ve_you}

import base64

Str2 = "e3nifIH9b_C@n@dH"
Destination = ""
for i, c in enumerate(Str2):
    Destination += chr(ord(Str2[i]) - i)
print(f'Destination=%s' % Destination)

flag = base64.b64decode(Destination.encode('utf-8')).decode('utf-8')
print(flag)
reverse3 主要逻辑

base64 码表

base64_encode函数

不一样的flag

32位exe,IDA直接F5反汇编,直奔main函数。发现是一个5×5的二维迷宫,起点是字符*,终点是字符#,通路是字符0,墙壁是字符1。答案flag{222441144222}。

用变量idx_column记录左右动作(按N重命名idx_column)。

用变量maze2d[25]记录上下动作,因为迷宫字符串是25个字符,maze2d[25]的值是'\0',初始值就是0。

最后判断用了idx_row(按N重命名idx_row),注意变量定义的顺序,char maze2d[29]; int idx_column; int step; int i; char idx_row[12]; 因此maze2d地址和idx_row地址差29+3*4=41,这就是为什么会有-41的原因。

SimpleRev

64位elf,IDA64直接F5反汇编,直奔main函数。虽然输入的是数字,但是按下R就可以看到相应字符串(大概),这里注意小端序的问题,用shift+E查看字符串内容。

key3和v9被join函数拼接成了text字符串'killshadow'。

key1和src拼接成了key字符串'ADSFKNDCLS',随后被涮换为小写的'adsfkndcls'。

写个简单的脚本爆破一下对应的输入即可,个人觉得答案不唯一,按下标位置自由组合即可。

text = 'killshadow'
key = 'adsfkndcls'

alpha=[]
alpha += [chr(i) for i in range(ord('A'), ord('A') + 26)]
alpha += [chr(i) for i in range(ord('a'), ord('a') + 26)]

for i, ct in enumerate(key):
    print(i, end=':')
    for cur_ch in alpha:
        if text[i] == chr((ord(cur_ch) - 39 - ord(key[i]) + 97) % 26 + 97):
            print(cur_ch, end=',')
    print()

"""
0:K,e,
1:L,f,
2:D,x,
3:Q,k,
4:C,w,
5:U,o,
6:D,x,
7:F,z,
8:Z,t,
9:O,i,
"""

Java逆向解密

Reverse.class用jadx反编译即可得到源码。源码逻辑简单,对输入的字符串,逐个字符转换为int型,加上'@'符号,再和32抑或,再与KEY数组比较相等即可。写个简单脚本得到答案flag{This_is_the_flag_!}。

nums = [180, 136, 137, 147, 191, 137, 147, 191,
        148, 136, 133, 191, 134, 140, 129, 135, 191, 65]
flag = ''
for i in nums:
    flag += chr((i ^ 32) - ord('@'))
print(flag)
package defpackage;

import java.util.ArrayList;
import java.util.Scanner;

/* renamed from: Reverse  reason: default package */
/* loaded from: Reverse.class */
public class Reverse {
    public static void main(String[] args) {
        Scanner s = new Scanner(System.in);
        System.out.println("Please input the flag :");
        String str = s.next();
        System.out.println("Your input is :");
        System.out.println(str);
        char[] stringArr = str.toCharArray();
        Encrypt(stringArr);
    }

    public static void Encrypt(char[] arr) {
        ArrayList<Integer> Resultlist = new ArrayList<>();
        for (char c : arr) {
            int result = (c + '@') ^ 32;
            Resultlist.add(Integer.valueOf(result));
        }
        int[] KEY = {180, 136, 137, 147, 191, 137, 147, 191, 148, 136, 133, 191, 134, 140, 129, 135, 191, 65};
        ArrayList<Integer> KEYList = new ArrayList<>();
        for (int i : KEY) {
            KEYList.add(Integer.valueOf(i));
        }
        System.out.println("Result:");
        if (Resultlist.equals(KEYList)) {
            System.out.println("Congratulations!");
        } else {
            System.err.println("Error!");
        }
    }
}

[GXYCTF2019]luck_guy

64位的elf,IDA64直接F5反汇编,直奔main函数。get_flag函数里f1字符串内容已知。s=0x7F666F6067756369LL 要注意小端序,char[] s={0x69, 0x63, 0x75, 0x67, 0x60, 0x6F, 0x66, 0x7F}。编写简单脚本得到答案flag{do_not_hate_me}。

f1 = 'GXY{do_not_'
f2 = ''
s = [0x69, 0x63, 0x75, 0x67, 0x60, 0x6F, 0x66, 0x7F]
for i, _ in enumerate(s):
    if i % 2 == 1:
        s[i] -= 2
    else:
        s[i] -= 1
for i in s:
    f2 += chr(i)
flag = f1 + f2
print(flag)
需要输入一个lucky_number

patch_me函数要求lucky_number是个偶数才能进入get_flag函数

get_flag函数case一个随机数模200的余数,注意case4和case5

[BJDCTF2020]JustRE

32位的windows程序,找start、main、WinMain函数意义不大,要看对话框的DialogFunc函数。IDA打开,shift+F12找字符串,发现有个BJD{%d%d2069a45792d233ac},ctrl+X交叉引用跳转到该函数地址,F5反汇编。发现是需要点击19999次才能得到flag(出题人真贱),直接把%d替换得到答案flag{1999902069a45792d233ac}

windows开发
sprintf(String, " BJD{%d%d2069a45792d233ac}", 19999, 0);

刮开有奖【子函数做了什么并未完全看懂,有猜测成分】

32位windows程序,点开后就……

刮开有奖

F5反汇编,看DialogFunc函数。定义了一堆int变量实际是一个11长度的整形数组。数组被process_int_arr函数处理过。这个函数很复杂,我直接copy修改成.c文件,自己编译执行,搞出处理后的数组值,下面是代码。

#include "stdlib.h"
#include "string.h"
#include "stdio.h"

int process_int_arr(int *int_arr, int index_0, int size_10)
{
  int result; // eax
  int i; // esi
  int cur_index; // ecx
  int cur_int; // edx

  result = size_10;
  for ( i = index_0; i <= size_10; index_0 = i )
  {
    cur_index = i;
    cur_int = int_arr[i];
    if ( index_0 < result && i < result )
    {
      do
      {
        if ( cur_int > int_arr[result] )
        {
          if ( i >= result )
            break;
          ++i;
          int_arr[cur_index] = int_arr[result];
          if ( i >= result )
            break;
          while ( int_arr[i] <= cur_int )
          {
            if ( ++i >= result )
              goto LABEL_13;
          }
          if ( i >= result )
            break;
          cur_index = i;
          int_arr[result] = int_arr[i];
        }
        --result;
      }
      while ( i < result );
    }
LABEL_13:
    int_arr[result] = cur_int;
    process_int_arr(int_arr, index_0, i - 1);
    result = size_10;
    ++i;
  }
  return result;
}

int main(int argc, char const *argv[])
{
  char *v4; // esi
  char *v5; // edi
  int int_arr[2]; // [esp+8h] [ebp-20030h] BYREF
  int int_arr_2; // [esp+10h] [ebp-20028h]
  int int_arr_3; // [esp+14h] [ebp-20024h]
  int int_arr_4; // [esp+18h] [ebp-20020h]
  int int_arr_5; // [esp+1Ch] [ebp-2001Ch]
  int int_arr_6; // [esp+20h] [ebp-20018h]
  int int_arr_7; // [esp+24h] [ebp-20014h]
  int int_arr_8; // [esp+28h] [ebp-20010h]
  int int_arr_9; // [esp+2Ch] [ebp-2000Ch]
  int int_arr_10; // [esp+30h] [ebp-20008h]
  char String[65536]; // [esp+34h] [ebp-20004h] BYREF
  char v18[65536]; // [esp+10034h] [ebp-10004h] BYREF
  memset(String, 0, 0xFFFFu);

  // GetDlgItemTextA(hDlg, 1000, String, 0xFFFF);

  int_arr[0] = 90;
  int_arr[1] = 74;
  int_arr_2 = 83;
  int_arr_3 = 69;
  int_arr_4 = 67;
  int_arr_5 = 97;
  int_arr_6 = 78;
  int_arr_7 = 72;
  int_arr_8 = 51;
  int_arr_9 = 110;
  int_arr_10 = 103;
  process_int_arr(int_arr, 0, 10);

  for(int i=0;i<=10;++i){
    printf("int_arr[%d]: %d\n", i, int_arr[i]);
  }

  return 0;
}

/*
int_arr[0]: 51
int_arr[1]: 67
int_arr[2]: 69
int_arr[3]: 72
int_arr[4]: 74
int_arr[5]: 78
int_arr[6]: 83
int_arr[7]: 90
int_arr[8]: 97
int_arr[9]: 103
int_arr[10]: 110
*/

接下来,对String[2-4]和String[5-7]分别base64加密得到ak1w和V1Ax。编写简单脚本得到答案flag{UJWP1jMp}。(You Jump,I jump!)

import base64

int_arr = [51, 67, 69, 72, 74, 78, 83, 90, 97, 103, 110, ]
flag = ''
flag += chr(int_arr[0] + 34)
flag += chr(int_arr[4])
# flag += chr(int((int_arr[2] * 3 + 141) / 4)) # String[2]已经被包含在后面了
# flag += chr(int(int_arr[7] / 9) * 2 * 4)     # String[3]已经被包含在后面了
flag += base64.b64decode('V1Ax'.encode('utf-8')).decode('utf-8')  # WP1
flag += base64.b64decode('ak1w'.encode('utf-8')).decode('utf-8')  # jMp
print('flag{' + flag + '}')
# flag{UJWP1jMp}
DialogFunc函数解析
process_int_arr函数

 base64_encode函数

简单注册器

简单注册器.apk,用jadx反编译可以看到源码。flag初始值为1,但是有个flag=(xxx)? 0:0,也就是说flag一定会变成0,永远验证不通过。直接看后面的代码,用python重新实现一下,打印flag得到答案flag{59acc538825054c7de4b26440c0999dd}

x = bytearray('dd2940c04462b4dd7c450528835cca15', 'utf-8')
x[2] = (x[2] + x[3]) - 50
x[4] = (x[2] + x[5]) - 48
x[30] = (x[31] + x[9]) - 48
x[14] = (x[27] + x[28]) - 97

for i in range(16):
    a = x[31 - i]
    x[31 - i] = x[i]
    x[i] = a

bbb = x.decode('utf-8')
print('flag{' + bbb + '}')
flag==1永远不可能成立

[GWCTF 2019]pyre

.pyc文件,用uncompyle6进行反编译,uncompyle6 -o flag.py flag.pyc得到python源代码如下。

# uncompyle6 version 3.9.1.dev0
# Python bytecode version base 2.7 (62211)
# Decompiled from: Python 3.11.4 (main, Jun  7 2023, 10:13:09) [GCC 12.2.0]
# Embedded file name: encode.py
# Compiled at: 2019-08-19 21:01:57
print 'Welcome to Re World!'
print 'Your input1 is your flag~'
l = len(input1)
for i in range(l):
    num = ((input1[i] + i) % 128 + 128) % 128
    code += num

for i in range(l - 1):
    code[i] = code[i] ^ code[i + 1]

print code
code = ['\x1f', '\x12', '\x1d', '(', '0', '4', 
        '\x01', '\x06', '\x14', '4', ',', '\x1b', 
        'U', '?', 'o', '6', '*', ':', '\x01', 
        'D', ';', '%', '\x13']

根据代码内容将算法反向进行即可。注意((input1[i]+i) % 128 +128)  % 128 结果就是(input1[i]+i) % 128。逐个字符爆破一下。得到答案flag{Just_Re_1s_Ha66y!}

code = bytearray(b'\x1f\x12\x1d(04\x01\x06\x144,\x1bU?o6*:\x01D;%\x13')
l = len(code)

for i in range(l - 2, -1, -1):
    code[i] = code[i] ^ code[i + 1]

input1 = ''
for i in range(l):
    for j in range(10):                # 爆破一下
        temp = (j * 128 + code[i]) - i
        if 32 <= temp < 127:           # ascii字符32-126是可显示、可输入字符
            input1 += chr(temp)
            break
print(input1)

[ACTF新生赛2020]easyre

32位exe用upx加壳,脱壳后放入IDA反汇编。函数逻辑显示永远不可能通过输入获得flag。但是给出了v4可以反推出flag是什么,写个简单脚本得到答案flag{U9X_1S_W6@T?}

table = bytearray(
    '~}|{zyxwvutsrqponmlkjihgfedcba`_^]\\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)(\'&%$# !"', 'utf-8')

v4 = bytearray("*F'\"N,\"(I?+@", 'utf-8')
v5 = bytearray(len(v4))
for i in range(12):
    v5[i] = table.index(v4[i]) + 1
print('flag{' + v5.decode('utf-8') + '}')
easyre.exe主要逻辑

findit

apk包放入jadx反编译,逻辑比较简单,输入字符串经处理后匹配,输出flag。编写简单脚本得到输入LzakAkLzwXdsyZgew和答案flag{c164675262033b4c49bdf7f9cda28a75}

a = b'ThisIsTheFlagHome'
b = b'pvkq{m164675262033l4m49lnp7p9mnk28k75}'

# 输入
la = len(a)
x = bytearray(la)
for i in range(la):
    if ((a[i] < ord('I') and a[i] >= ord('A')) or (a[i] < ord('i') and a[i] >= ord('a'))):
        x[i] = a[i] + 18
    elif ((a[i] >= ord('A') and a[i] <= ord('Z')) or (a[i] >= ord('a') and a[i] <= ord('z'))):
        x[i] = a[i] - ord('\b')
    else:
        x[i] = a[i]
print(x.decode('utf-8'))

# flag
lb = len(b)
y = bytearray(lb)
for i2 in range(lb):
    if((b[i2] >= ord('A') and b[i2] <= ord('Z')) or (b[i2] >= ord('a') and b[i2] <= ord('z'))):
        y[i2] = b[i2] + 16
        if ((y[i2] > ord('Z') and y[i2] < ord('a')) or y[i2] >= ord('z')):
            y[i2] = y[i2] - 26
    else:
        y[i2] = b[i2]
print(y.decode('utf-8'))
apk的主要逻辑

安卓模拟器

[ACTF新生赛2020]rome

32位exe,IDA反汇编。根据You are correct!字符串找到func函数。该函数对输入字符串ACTF{****xxxx****xxxx}中16个字符做处理,看结果是否与Qsw3sj_lz4_Ujw@l匹配。注意前面各个变量的定义,用4个int存储了中间的16个字符,用index[0]-index[15]存储处理后的结果,用index[16]存'\0',用index[17]字节开始的int存储数组下标。写脚本爆破一下得到答案flag{Cae3ar_th4_Gre@t}

result = b'Qsw3sj_lz4_Ujw@l'
inputs = bytearray(16)
for i in range(16):
    for j in range(32, 127):  # 遍历ascii可显示字符,经过rome的处理
        inputs[i] = j
        if inputs[i] > 64 and inputs[i] <= 90:
            inputs[i] = (inputs[i] - 51) % 26 + 65
        if inputs[i] > 96 and inputs[i] <= 122:
            inputs[i] = (inputs[i] - 79) % 26 + 97
        if inputs[i] == result[i]:
            inputs[i] = j  # 如果匹配说明j是正确的值
            break
print('flag{' + inputs.decode('utf-8') + '}')
func函数主要逻辑
实际是凯撒密码

rsa

附件不是程序,二是pub.key公钥文件和flag.enc密文,更像是密码学。此题毫无思路,查看writeup之后记录于此。感谢buu Reverse学习记录(21) rsa_buu recursive-CSDN博客

在kali输入命令openssl rsa -pubin -text -modulus -in warmup -in pub.key就可以得到e和n 

用yafu分解n得到p和q
 

再写脚本得到flag{decrypt_256}

import gmpy2
import rsa

e = 65537
n = 0xC0332C5C64AE47182F6C1C876D42336910545A58F7EEFEFC0BCAAF5AF341CCDD
p = 304008741604601924494328155975272418463
q = 285960468890451637935629440372639283459
phi_n = (p - 1) * (q - 1)
d = gmpy2.invert(e, phi_n)

key = rsa.PrivateKey(n, e, int(d), p, q)

with open('flag.enc', "rb") as f:
    c = f.read()
    print(rsa.decrypt(c, key))

[FlareOn4]login

login.html内容比较简单,如下。对输入进行rot13加密,密文解密得到答案flag{[email protected]}

<!DOCTYPE Html />
<html>
    <head>
        <title>FLARE On 2017</title>
    </head>
    <body>
        <input type="text" name="flag" id="flag" value="Enter the flag" />
        <input type="button" id="prompt" value="Click to check the flag" />
        <script type="text/javascript">
            document.getElementById("prompt").onclick = function () {
                var flag = document.getElementById("flag").value;
                var rotFlag = flag.replace(/[a-zA-Z]/g, function(c){return String.fromCharCode((c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + 13) ? c : c - 26);});
                if ("[email protected]" == rotFlag) {
                    alert("Correct flag!");
                } else {
                    alert("Incorrect flag, rot again");
                }
            }
        </script>
    </body>
</html>

[WUSTCTF2020]level1

64位elf,IDA64反汇编,逻辑比较简单,注意下标是从1开始的。写个脚本搞出答案flag{d9-dE6-20c}

nums = [198, 232, 816, 200, 1536, 300, 6144, 984, 51200, 570, 92160,
        1200, 565248, 756, 1474560, 800, 6291456, 1782, 65536000, ]
flag = ''
for i, n in enumerate(nums, start=1):
    if i % 2 == 1:
        flag += chr(n >> i)
    else:
        flag += chr(n // i)
print(flag)
mian函数主要逻辑

CrackRTF

32位exe文件,反汇编后发现需要两次输入,每次输入6个字符。第一次输入后,追加字符串1@DBApp,再做MD5哈希,哈希匹配进入第二次输入。第二次输入后,追加第一次的字符串,再做MD5哈希,哈希匹配后生成一个dbapp.rtf文件。那么去MD5免费在线解密破解_MD5在线加密-SOMD5网碰撞得到两次的输入分别是123321和~!3a@0,然后生成dbapp.rtf文件即可得到答案flag{N0_M0re_Free_Bugs}

word打开dbapp.rtf
CrackRTF主要逻辑

[GUET-CTF2019]re

64位elf程序,有壳。upx去壳后再IDA反汇编。没有main函数,只好shift+F12找字符串,根据运行提示“Wrong!”找到“Correct!”,进而找到主要的逻辑。挺坑的,只好写个脚本。注意,逻辑里是没有下标6的元素的,而且下标16,17的两个验证是反顺序写的。得到缺少下标6元素的答案,然后看writeup才知道要尝试提交,试出下标6元素。也有用Z3库求解的。

我运气不好,从a试到z,没试出来,试一下数字1就行,答案flag{e165421110ba03099a1c039337}

flag = ''
flag += chr(166163712 // 1629056)
flag += chr(731332800 // 6771600)
flag += chr(357245568 // 3682944)
flag += chr(1074393000 // 10431000)
flag += chr(489211344 // 3977328)
flag += chr(518971936 // 5138336)
flag += '<?>'
flag += chr(406741500 // 7532250)
flag += chr(294236496 // 5551632)
flag += chr(177305856 // 3409728)
flag += chr(650683500 // 13013670)
flag += chr(298351053 // 6088797)
flag += chr(386348487 // 7884663)
flag += chr(438258597 // 8944053)
flag += chr(249527520 // 5198490)
flag += chr(445362764 // 4544518)
flag += chr(981182160 // 10115280)
flag += chr(174988800 // 3645600)
flag += chr(493042704 // 9667504)
flag += chr(257493600 // 5364450)
flag += chr(767478780 // 13464540)
flag += chr(312840624 // 5488432)
flag += chr(1404511500 // 14479500)
flag += chr(316139670 // 6451830)
flag += chr(619005024 // 6252576)
flag += chr(372641472 // 7763364)
flag += chr(373693320 // 7327320)
flag += chr(498266640 // 8741520)
flag += chr(452465676 // 8871876)
flag += chr(208422720 // 4086720)
flag += chr(515592000 // 9374400)
flag += chr(719890500 // 5759124)
print(flag)
# flag{e<?>65421110ba03099a1c039337}
主要逻辑

[2019红帽杯]easyRE

64位elf,逆向题做到这里开始有意思了。直接反汇编分析逻辑比较简单。先拼接字符串,逐个字符和下标亦或得到提示信息Info:The first four chars are `flag`,似乎没有用。又发现了一个10次base64加密信息,解密后得到地址https://bbs.pediy.com/thread-254172.htm,这地址毫无营养。

m = bytearray('Iodl>Qnb(ocy\x7Fy.i\x7Fd`3w}wek9{iy=~yL@EC', 'utf-8')
print(m)
lm = len(m)
for i in range(lm):
    m[i] = m[i] ^ i
print(m.decode('utf-8'))
没用的逻辑

最后看了writeup才知道,start函数除了调用上面的main_logic,还有后面的两个函数。这两个函数都是用函数指针执行多个函数,其中sub_400D35函数对字符数组做异或操作得到flag。写个脚本,结合前面提示前4个字符是flag可以得到加密秘钥,然后再得到答案flag{Act1ve_Defen5e_Test}。

c = bytes.fromhex('403520565D182245172F246E623C2754486C246E723C32455B')
key = bytearray(4)
key[0] = c[0] ^ ord('f')
key[1] = c[1] ^ ord('l')
key[2] = c[2] ^ ord('a')
key[3] = c[3] ^ ord('g')
lc = len(c)
flag = bytearray(lc)
for i in range(lc):
    flag[i] = c[i] ^ key[i % len(key)]
print(flag.decode('utf-8'))
这两个函数才有用

用函数指针调用多个函数
用函数指针调用多个函数
其中sub_400D35函数对字符数组做异或,结合先检验了'f'和'g'两个字符,推断这是flag

找到base64密文,下方还有这个字符串,ctrl+x也可以赵傲函数sub_400D35

[MRCTF2020]Transform

64位exe程序,IDA64反汇编。main的逻辑比较清晰。需要注意index_index的元素是下标,是用dd定义的数组,也就是说一个元素占4个字节,LOBYTE就是取最低字节,用shift+E选择initialized C variable可以看到数组元素LOBYTE的数值(否则4字节转换太麻烦)。写个简单脚本反推得到答案flag{Tr4nsp0sltiON_Clph3r_1s_3z}

index_index = [9, 10, 15, 23, 7, 24, 12, 6, 1, 16, 3,
               17, 32, 29, 11, 30, 27, 22, 4, 13, 19, 20,
               21, 2, 25, 5, 31, 8, 18, 26, 28, 14, 0, ]
DestStr = [103, 121, 123, 127, 117, 43, 60, 82, 83, 121, 87,
           94, 93, 66, 123, 45, 42, 102, 66, 126, 76, 87,
           121, 65, 107, 126, 101, 60, 92, 69, 111, 98, 77, ]

ld = len(DestStr)
InputStr = [0 for i in range(ld)]
for i in range(ld):
  DestStr[i] = DestStr[i] ^ index_index[i]
  InputStr[index_index[i]] = DestStr[i]

flag = ''
for i in range(ld):
  flag += chr(InputStr[i])
print(flag)
main主要逻辑
用shift+E选择initialized C variable可以看到数组元素LOBYTE的数值

[WUSTCTF2020]level2

32位elf程序,执行输出where is it?。upx脱壳后反汇编,程序main里只有一个puts,shift+F12后直接找到字符串 wctf2020{Just_upx_-d} 从而得到答案。但是这个怎么做到的呢?

main函数只有一个puts

shif+F12

[SUCTF2019]SignIn

64位elf程序,IDA反汇编,main函数主要逻辑是对输入字符串操作得到明文m,再用RSA算法加密得到密文c,对比密文c是否正确。因此,RSA解密,再反推输入即可。

main 函数的主要逻辑
operate_input_str 函数对输入字符串的操作

使用yafu工具对n进行分解,得到p和q。第一次执行分解大概120秒。 

用yafu分解n得到p和q,第一次执行分解大概需要120秒时间

 再进行RSA解密,解密后对字符串逆向操作得到输入 suctf{Pwn_@_hundred_years}

import gmpy2

n = 103461035900816914121390101299049044413950405173712170434161686539878160984549
# yafu - factor(n)=p*q
p = 366669102002966856876605669837014229419
q = 282164587459512124844245113950593348271
phi_n = (p - 1) * (q - 1)
c = int('ad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35', 16)
e = 65537
d = gmpy2.invert(e, phi_n)
m = pow(c, d, n)
print(m)

# Reverse of operate_input_str
buf_str = hex(m)[2:]
lb = len(buf_str)
table = '0123456789abcdef'
input_str = ''
for i in range(0, lb, 2):
  index = (table.index(buf_str[i]) << 4) + table.index(buf_str[i + 1])
  input_str += chr(index)
print(input_str)
答案

[ACTF新生赛2020]usualCrypt

32位exe程序,IDA反汇编。可以看到输入字符串input_str,经过base64加密后得到zMXHz3TIgnxLxJhFAdtZn2fFk3lYCrtPC2l9。这个base64_encode函数是定制的码表ABCDEFQRSTUVWXYPGHIJKLMNOZabcdefghijklmnopqrstuvwxyz0123456789+/。最后swap_case函数还转换了字母的大小写。使用CyberChef工具得到答案 flag{bAse64_h2s_a_Surprise}

main 函数主要逻辑

custom_table 函数自定义了base64的码表

table = bytearray(
    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', 'utf-8')
offset = table.index(ord('K'))
for i in range(6, 15):
  table[offset + i], table[i] = table[i], table[offset + i]
print(table.decode('utf-8')) # 自定义码表
swap_case 函数转换了字母大小写

非常好用的CyberChef

[HDCTF2019]Maze【花指令】

此题是简单的10x7迷宫,蒙都能蒙出答案flag{ssaaasaassdddw}。

upx加壳的32位exe,脱壳后反汇编。发现没有main函数,只能才能从start进入,双击main发现反汇编失败。

为什么没有 main 函数呢?
调换到 loc_40102E+1 就是跳转到 loc_40102F

看了别的writeup才知道花指令。本例的特征:地址40102C的指令要跳转到loc_40102E+1也就是地址40102F。注意地址40102E的指令0E8h是call指令,但并没有真的call什么。所以把地址40102C到地址40102E的指令都nop掉。

发现花指令

nop没发现快捷键,点击地址40102C,菜单 -> Edit -> Patch program -> Change program bytes,把指令75h、01h、E8h都改成90h,也就是nop。选中main函数的范围(地401000-401112),按P创建函数。再F5,成功反汇编。

菜单 -> Edit -> Patch program -> Change program bytes

选中 main 函数的汇编指令,F5

反编译出 main 函数

[MRCTF2020]Xor

32位exe,反编译 main 函数报错,地址401095分析错误。那么,我们在汇编代码视图按G,输入401095,跳转到该地址。发现另一个调用sub_401020。我们先F5反编译sub_401020,再反编译main就不会报错了。题目逻辑比较简单,写个脚本得到答案

反编译 main 函数报错
跳转到401095地址发现调用

发现一个调用

main 的逻辑比较简单

s = bytearray('MSAWB~FXZ:J:`tQJ"N@ bpdd}8g', 'utf-8')
for i in range(27):
  s[i] ^= i
print(s.decode('utf-8'))

[MRCTF2020]hello_world_go【工具IDAGolangHelper 】

DIE查看发现是64位elf,无壳,用go语言开发。找到字符串flag{hello_world_gogogo}。据说 IDA Golang Helper工具可以反编译go程序,后续试试。

main_main 函数有这个变量是flag

flag

Youngter-drive

32位exe,upx壳。去壳后IDA反汇编。程序先检查了是否打开ollyice.exe、ollydbg.exe、peid.exe、ida.exe、idap.exe等逆向工具,如果打开了,只能得到warning退出。

检查了是否打开ollyice.exe、ollydbg.exe、peid.exe、ida.exe、idap.exe等逆向工具
如果打开了以上逆向工具,程序会警告并退出

 IDA先捋清楚逻辑。main函数开启了两个线程,一个处理字符串,另一个让计数器递减1。操作字符串线程,每操作一次,sleep 100,计数器从29递减到0;可以看出要求输入都是字母,对大小写字母有不同操作。另一个线程只是对计数器减1,然后sleep 100。因为sleep时长一样,两个线程交替进行,相当于只对字符串的奇数位做了操作。

可以逆向出来ThisisthreadofwindowshahaIsES。但是注意,注意check flag的时候只check了0-28,没有校验下标29。所以最后还有一个字符。输入任何字符程序都能通过,但是buuctf上只能尝试,得到一个正确答案flag{ThisisthreadofwindowshahaIsESE}

"""
TOiZiZtOrYaToUwPnToBsOaOapsyS
"""
table = 'QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm'
Dest = 'TOiZiZtOrYaToUwPnToBsOaOapsyS'
ld = len(Dest)
Source = ''
for i in range(ld):
    if i % 2 == 1: # 只有奇数位才变换
        index = table.index(Dest[i])
        if ord('a') <= index + 96 <= ord('z'):
            Source += chr(index + 96)
        elif ord('A') <= index + 38 <= ord('Z'):
            Source += chr(index + 38)
        else:
            Source += '<?>'
    else: # 偶数位是原值
        Source += Dest[i]
print(Source)
main函数开启了两个线程,一个处理字符串,另一个让计数器递减1

操作字符串线程,每操作一次,sleep 100,计数器从29递减到0

  

操作字符串线程,对字符操作。

另一个线程只是对计数器减1,然后sleep 100

注意check flag的时候只check了0-28,没有校验下标29

[WUSTCTF2020]level3

64位elf,直接base64解码得到乱码,但base64_encode函数是正常的。看了writeup才知道,_libc_start_main函数有个入参init,这个函数调用O_OLookAtYou函数,自定义了码表。用自定义码表解码得到 wctf2020{Base64_is_the_start_of_reverse}

d2G0ZjLwHjS7DmOzZAY0X2lzX3CoZV9zdNOydO9vZl9yZXZlcnGlfD== base64解码是乱码

base64_table 还被O_OLookAtYou 函数使用

O_OLookAtYou 函数是libc_start_main函数入参init函数调用的
自定义码表是TSRQPONMLKJIHGFEDCBAUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

相册

题目描述:你好,这是上次聚会相片,你看看(病毒,不建议安装到手机,提取完整邮箱即为flag)。

把apk拖入jadx,没发现 MainActivity。去 AndroidManifest.xml 文件里找带有android.intent.action.MAIN 的action属性的类,这是安卓运行的第一个 Activity,是 cn.baidujiayuan.ver5304.C1 类(应该是做了代码混淆?)。在C1的onCreate方法里发现发送邮件的类 cn.baidujiayuan.ver5304.MailTask。MailTask 里面调用了A2的发送邮件方法,C2提供了邮件服务器MAILSERVER。A2 的 sendMailByJavaMail 方法并没多大用。C2在static初始化时用NativeMethod 类提供的密文设置了邮箱。注意C2的静态初始化(static{}内容),有加载loadLibrary("core")。用7z打开apk,解压缩找到 libcore.so,发现是32位elf,IDA反编译,找到libcore.so 里的 m() 方法有邮箱地址,base64解码即可,答案 flag{[email protected]}

cn.baidujiayuan.ver5304.C1

cn.baidujiayuan.ver5304.MailTask

A2 和 C2

A2 的 sendMailByJavaMail 方法并没多大用

C2 在 static 初始化时用 NativeMethod 类提供的密文设置了邮箱,注意loadLibrary("core")

libcore.so 里一定提供了 m() p() pwd() 函数

libcore.so 里的 m() 方法有邮箱地址,base64解码即可

[FlareOn4]IgniteMe

32位exe程序。读取字符串,从后向前逐个异或,再对比是否正确。注意异或字符的初始值,是0x80070000循环左移4位,再右移1位,复制给char类型,也就是4。写个脚本得到答案flag{[email protected]}

DestStr = b'\x0D&IE*\x17xD+l]^E\x12/\x17+DonV\x09_EGs&\x0A\x0D\x13\x17HB\x01@M\x0C\x02i'
input_str = bytearray(len(DestStr))

ld = len(DestStr)
last = 4 # 
for i in range(ld - 1, -1, -1):
    input_str[i] = DestStr[i] ^ last
    last = input_str[i]
print(input_str.decode('utf-8'))
Start 函数逻辑清晰

CheckFlag 函数对字符串的异或操作清晰,但是不知道LastCh的初始值是多少

GetInitValue 函数的__RIL4__难以理解,看writeup说是循环右移的意思

GetInitValue函数的汇编指令rol和shr可看出位移操作

[WUSTCTF2020]Cr0ssfun

64位elf,逻辑比较简单,按下标填字符串得到 wctf2020{cpp_@nd_r3verse_@re_fun}

几个函数都是这个逻辑

flag

[GWCTF 2019]xxor

64位elf,反汇编发现逻辑是清晰的,但是怎么你向出异或算法没有想出来,看了writeup才知道算法。另外,就是要注意数据类型,int和unsigned int的区别,c语言数据溢出的问题。

先看main函数,顺序走下来,对6个输入的整数做operate,再检查结果。分析check函数就可以得到dest_arr的值是dest_arr[] = { -548868226, 550153460, 3774025685, 1548802262, 2652626477, -2064448480}。

from z3 import *

a0, a1, a2, a3, a4, a5 = Ints('a0 a1 a2 a3 a4 a5')
s = Solver()
s.add(a2 - a3 == 2225223423)
s.add(a3 + a4 == 4201428739)
s.add(a2 - a4 == 1121399208)
s.add(a0 == -548868226)
s.add(a5 == -2064448480)
s.add(a1 == 550153460)
print(s.check() == 'sat')
print(s.model())
main 函数逻辑清晰,注释基本能看懂

check函数解方程就能得到dest_arr

再分析operate函数。注意:t0只是用上一步t1操作出来的,t1只用上一步t0操作出来的。因此把步骤反过来,可以逆向出答案。另一点,注意数据类型的大小,因为python的int不限大小不会溢出,所以逆向用c代码做会好一些(因为题目的逻辑int类型有溢出)。src_arr[] = {6712417, 6781810, 6643561, 7561063, 7497057, 7610749}

注意:t0只是用上一步t1操作出来的,t1只用上一步t0操作出来的。
#include "stdio.h"

int main(int argc, char const *argv[])
{
    int dest_arr[] = { -548868226, 550153460, 3774025685, 
                       1548802262, 2652626477, -2064448480};
    int src_arr[6] = {0, 0, 0, 0, 0, 0};
    int ch_2234[] = {2, 2, 3, 4};
    // 把算法步骤反过来
    for (int j = 0; j <= 4; j += 2) {
        int t = 1166789954 * 64;
        unsigned int t0 = dest_arr[j];
        unsigned int t1 = dest_arr[j + 1];
        for (int i = 0; i <= 63; ++i) {
            t1 -= (t0 + t + 20) ^ ((t0 << 6) + ch_2234[2]) ^ ((t0 >> 9) + ch_2234[3]) ^ 0x10;
            t0 -= (t1 + t + 11) ^ ((t1 << 6) + *ch_2234) ^ ((t1 >> 9) + ch_2234[1]) ^ 0x20;
            t -= 1166789954;
        }
        src_arr[j] = t0;
        src_arr[j + 1] = t1;
    }

    for (int i = 0; i < 6; ++i) {
        printf("src_arr[%d]=%d\n", i, src_arr[i]);
    }
    return 0;
}
还有最后一步

最后把输入的6个数,转成16进制,连接起来转成字节流就是答案 flag{re_is_great!}

nums = [6712417, 6781810, 6643561, 7561063, 7497057, 7610749, ]
hexstr=''
for n in nums:
    hexstr += hex(n)[2:]
print(bytes.fromhex(hexstr).decode('utf-8'))

[FlareOn6]Overlong

32位exe,反汇编发现一共3个函数。各个函数只让Text数组内容发生变化,内容是字符串"I never broke the encoding: "共28个字符(最后有个空格)。联想把入参28改成Text的长度128应该可以得到答案 flag{[email protected]}

start函数对28个字符处理

main_logic函数中ch_arr指针一直再动

只有Text内容变化,ch_arr内容不变

#include "stdio.h"

int  main_logic_sub(char *Text, char *ch_arr)
{
  int step; 
  char dest_ch; 

  if ( (int)(unsigned char)*ch_arr >> 3 == 30 )
  {
    dest_ch = ch_arr[3] & 0x3F | ((ch_arr[2] & 0x3F) << 6);
    step = 4;
  }
  else if ( (int)(unsigned char)*ch_arr >> 4 == 14 )
  {
    dest_ch = ch_arr[2] & 0x3F | ((ch_arr[1] & 0x3F) << 6);
    step = 3;
  }
  else if ( (int)(unsigned char)*ch_arr >> 5 == 6 )
  {
    dest_ch = ch_arr[1] & 0x3F | ((*ch_arr & 0x1F) << 6);
    step = 2;
  }
  else
  {
    dest_ch = *ch_arr;
    step = 1;
  }
  *Text = dest_ch;
  return step;
}

unsigned int main_logic(char *Text, char *ch_arr, unsigned int len_28) {
    unsigned int i;
    for (i = 0; i < len_28; ++i)
    {
        ch_arr += main_logic_sub(Text, ch_arr);
        if ( !*Text++ )
            break;
    }
    return i;
}



int main(int argc, char const *argv[])
{
    char Text[128];
    unsigned int end; 

    unsigned char ch_arr[] =
    {
      0xE0, 0x81, 0x89, 0xC0, 0xA0, 0xC1, 0xAE, 0xE0, 0x81, 0xA5, 
      0xC1, 0xB6, 0xF0, 0x80, 0x81, 0xA5, 0xE0, 0x81, 0xB2, 0xF0, 
      0x80, 0x80, 0xA0, 0xE0, 0x81, 0xA2, 0x72, 0x6F, 0xC1, 0xAB, 
      0x65, 0xE0, 0x80, 0xA0, 0xE0, 0x81, 0xB4, 0xE0, 0x81, 0xA8, 
      0xC1, 0xA5, 0x20, 0xC1, 0xA5, 0xE0, 0x81, 0xAE, 0x63, 0xC1, 
      0xAF, 0xE0, 0x81, 0xA4, 0xF0, 0x80, 0x81, 0xA9, 0x6E, 0xC1, 
      0xA7, 0xC0, 0xBA, 0x20, 0x49, 0xF0, 0x80, 0x81, 0x9F, 0xC1, 
      0xA1, 0xC1, 0x9F, 0xC1, 0x8D, 0xE0, 0x81, 0x9F, 0xC1, 0xB4, 
      0xF0, 0x80, 0x81, 0x9F, 0xF0, 0x80, 0x81, 0xA8, 0xC1, 0x9F, 
      0xF0, 0x80, 0x81, 0xA5, 0xE0, 0x81, 0x9F, 0xC1, 0xA5, 0xE0, 
      0x81, 0x9F, 0xF0, 0x80, 0x81, 0xAE, 0xC1, 0x9F, 0xF0, 0x80, 
      0x81, 0x83, 0xC1, 0x9F, 0xE0, 0x81, 0xAF, 0xE0, 0x81, 0x9F, 
      0xC1, 0x84, 0x5F, 0xE0, 0x81, 0xA9, 0xF0, 0x80, 0x81, 0x9F, 
      0x6E, 0xE0, 0x81, 0x9F, 0xE0, 0x81, 0xA7, 0xE0, 0x81, 0x80, 
      0xF0, 0x80, 0x81, 0xA6, 0xF0, 0x80, 0x81, 0xAC, 0xE0, 0x81, 
      0xA1, 0xC1, 0xB2, 0xC1, 0xA5, 0xF0, 0x80, 0x80, 0xAD, 0xF0, 
      0x80, 0x81, 0xAF, 0x6E, 0xC0, 0xAE, 0xF0, 0x80, 0x81, 0xA3, 
      0x6F, 0xF0, 0x80, 0x81, 0xAD, 0x00
    };

    end = main_logic(Text, ch_arr, 128); // 入参从28改成128 

    for (int i = 0; i < 128; ++i) {
        printf("%c", Text[i]);
    }

    for (int i = 0; i < 128; ++i) {
        if (i % 10 == 0) {
            printf("\n");
        }
        printf("%x, ", Text[i]);
    }

    return 0;
}

入参改成128即可

[UTCTF2020]basic-re

看汇编代码,flag被load之后call的函数不是printf,而是一个错误的函数。直接提交flag{str1ngs_1s_y0ur_fr13nd}。

[FlareOn3]Challenge1

32位exe,定制码表的base64加密,flag{[email protected]}

特殊的 BASE64

64位exe,定制码表的base64加密,flag{Special_Base64_By_Lich}

[ACTF新生赛2020]Oruga

64位elf,反汇编发现是个16×16二维迷宫,通路是'\0',终点是'!',其他字符是障碍。W向上,M向下,J向左,E向右。不同点在于,每次方向确定之后,要一直沿着一个方向走,直到碰到障碍物才可以停下,且不能碰到四周边界。因此每步只能选择左右或者上下,再根据不能碰四周边界可以得到 actf{MEWEMEWJMEWJM} 。迷宫如下

0000#0000000####
000##000OO000000
00000000OO0PP000
000L0OO0OO0PP000
000L0OO0OO0P0000
00LL0OO0000P0000
00000OO0000P0000
#000000000000000
000000000000#000
000000MMM000#000
0000000MMM0000EE
00000M0M0M0000E0
00000000000000EE
TTTI0M0M0M0000E0
0T0I0M0M0M0000E0
0T0I0M0M0M!000EE
main 函数
pass_maze 函数

[ACTF新生赛2020]Universe_final_answer

64位elf程序,puzzle_function(key)函数内容是一个十元一次方程组,用z3求解器解方程组得到输入key,执行得到flag。

flag
from z3 import *

keys = Ints('key_0 key_1 key_2 key_3 key_4 key_5 key_6 key_7 key_8 key_9')
key_0, key_1, key_2, key_3, key_4, key_5, key_6, key_7, key_8, key_9 = keys

s = Solver()
s.add(-85 * key_8 + 58 * key_7 + 97 * key_6 + key_5 + -45 * key_4 + 84 * key_3
      + 95 * key_0 - 20 * key_1 + 12 * key_2 == 12613)
s.add(30 * key_9 + -70 * key_8 + -122 * key_6 + -81 * key_5 + -66 * key_4 +
      -115 * key_3 + -41 * key_2 + -86 * key_1 - 15 * key_0 - 30 * key_7 == -54400)
s.add(-103 * key_9 + 120 * key_7 + 108 * key_5 + 48 * key_3 + -89 * key_2 +
      78 * key_1 - 41 * key_0 + 31 * key_4 - (key_6 * 64) - 120 * key_8 == -10283)
s.add(71 * key_6 + (key_5 * 128) + 99 * key_4 + -111 * key_2 + 85 * key_1 +
      79 * key_0 - 30 * key_3 - 119 * key_7 + 48 * key_8 - 16 * key_9 == 22855)
s.add(5 * key_9 + 23 * key_8 + 122 * key_7 + -19 * key_6 + 99 * key_5 +
      -117 * key_4 + -69 * key_2 + 22 * key_1 - 98 * key_0 + 10 * key_3 == -2944)
s.add(-54 * key_9 + -23 * key_7 + -82 * key_2 + -85 * key_0 + 124 * key_1 -
      11 * key_3 - 8 * key_4 - 60 * key_5 + 95 * key_6 + 100 * key_8 == -2222)
s.add(-83 * key_9 + -111 * key_5 + -57 * key_0 + 41 * key_1 + 73 * key_2 -
      18 * key_3 + 26 * key_4 + 16 * key_6 + 77 * key_7 - 63 * key_8 == -13258)
s.add(81 * key_9 + -48 * key_8 + 66 * key_7 + -104 * key_6 + -121 * key_5 +
      95 * key_4 + 85 * key_3 + 60 * key_2 + -85 * key_0 + 80 * key_1 == -1559)
s.add(101 * key_9 + -85 * key_8 + 7 * key_6 + 117 * key_5 + -83 * key_4 +
      -101 * key_3 + 90 * key_2 + -28 * key_1 + 18 * key_0 - key_7 == 6308)
s.add(99 * key_9 + -28 * key_8 + 5 * key_7 + 93 * key_6 + -18 * key_5 +
      -127 * key_4 + 6 * key_3 + -9 * key_2 + -93 * key_1 + 58 * key_0 == -1697)

if s.check() == sat:
    model = s.model()
    print(model)

    input_key = ''
    for k in keys:
        n = model[k].as_long()  # 转换成int类型
        input_key += chr(n)
    print(input_key)
# F0uRTy_7w@

[Zer0pts2020]easy strcmp

64位elf,伪代码简单,但执行发现不对。从_libc_start_main函数的init函数看,init执行了ptr_func_0、ptr_func_1、ptr_func_2。ptr_func_1函数取了strcmp和cook_flag两个函数的地址。重点来了,main函数汇编代码,看似call了_strcmp函数,实际上_strcmp又跳转到了p_cook_flag,也就是cook_flag函数。cook_flag函数加密方式比较简单,看成qword整形,把key加回来就行了。注意bytes和int之间转换时的小端序问题。

crypt_str = b'zer0pts{********CENSORED********}'
key = bytes.fromhex('000000000000000042094A4935430A41F019E60BF5F20E0B2B28354A063A0A4F0000000000000000')

lc = len(crypt_str)
flag = ''
for i in range(0, lc, 8):
    c = int.from_bytes(crypt_str[i:i + 8], 'little', signed=True)  # 小端序
    k = int.from_bytes(key[i:i + 8], 'little', signed=True)  # 小端序
    t = (c + k) & 0xFFFFFFFFFFFFFFFF
    m = bytes.fromhex(hex(t)[2:])
    flag += m.decode('utf-8')[::-1]  # 小端序转成字符串后倒转一下
print(flag)
# zer0pts{l3ts_m4k3_4_DETOUR_t0d4y}
main伪代码

init函数执行了ptr_func_0、ptr_func_1、ptr_func_2

ptr_func_1函数取了strcmp和cook_flag两个函数的地址

main函数汇编代码,看似call了_strcmp函数

实际上_strcmp又跳转到了p_cook_flag,也就是cook_flag函数

cook_flag函数加密方式比较简单,看成qword整形,把key加回来就行了

[WUSTCTF2020]level4

64位elf,根据提示,应该是有个type3(x[22])函数,需要自己找到才能出flag。
观察init函数和数据结构,x数组间隔0x18字节,推测该结构体有3个qword长。再根据输出提示,推断是个树状结构。init函数按照this-left-right方式初始化这棵树,也就是flag。

x = 'I{_}Af2700ih_secTS2Et_wr'
flag = x[22] + x[15] + x[20] + x[5] + x[6] + x[8] + x[18] + x[9] + \
    x[1] + x[16] + x[11] + x[10] + x[13] + x[2] + x[0] + x[17] + \
    x[21] + x[4] + x[12] + x[7] + x[23] + x[14] + x[19] + x[3]
print(flag)
# wctf2020{This_IS_A_7reE}
提示树状结构
struct Tree

init函数按照this-left-right方式初始化这棵树,也就是flag

[网鼎杯 2020 青龙组]singal

32位exe,发现是模拟CPU操作。opcodes数组就是操作码和操作数。

  • 10是读取输入。
  • 2,3,4,5,11,12是运算。
  • 8是暂存。
  • 1是存最终结果。
  • 6是空指令。
  • 7是检查最终结果。

可以模拟出这个过程,然后逐位爆破输入。注意C语言的隐式类型转换,以及python的位操作和C的位操作不同,需要自己写转换。
答案flag{757515121f3d478}

def get_opcodes():
    """
    初始化opcodes用的guide的内容
    :return:
    """
    guide = bytes.fromhex(
        '0A0000000400000010000000080000000300000005000000010000000400000020000000080000000500000003000000010000000300000002000000080000000B000000010000000C000000080000000400000004000000010000000500000003000000080000000300000021000000010000000B000000080000000B000000010000000400000009000000080000000300000020000000010000000200000051000000080000000400000024000000010000000C000000080000000B000000010000000500000002000000080000000200000025000000010000000200000036000000080000000400000041000000010000000200000020000000080000000500000001000000010000000500000003000000080000000200000025000000010000000400000009000000080000000300000020000000010000000200000041000000080000000C000000010000000700000022000000070000003F0000000700000034000000070000003200000007000000720000000700000033000000070000001800000007000000A7FFFFFF070000003100000007000000F1FFFFFF07000000280000000700000084FFFFFF07000000C1FFFFFF070000001E000000070000007A000000')
    lg = len(guide)
    opcodes = []
    for i in range(0, lg, 4):
        op = int.from_bytes(guide[i:i + 4], 'little', signed=True)
        opcodes.append(op)
    return opcodes


def int_to_char(n):
    """
    将算数运算结果转换到char类型(-128~127)
    :param n:
    :return:
    """
    while n > 127:
        n -= 0x100
    while n < -128:
        n += 0x100
    return n


def char_to_logic(n):
    """
    python只能对正整数位运算
    将char类型(-128~127)转换到可以进行逻辑运算的形式
    :param n:
    :return:
    """
    if n < 0:
        return (0x100 + n) | 0xFFFFFF00
    else:
        return n


def logic_to_char(i):
    """
    位运算结果转换为char类型(-128~127)
    :param i:
    :return:
    """
    if (i & 0xFF) >= 0x80:
        return (i & 0xFF) - 0x100
    else:
        return i


def vm_operad(str_buf, one_ops, dest, index):
    fc = str_buf[0]
    op_idx = 0  # 操作码操作数index
    str_idx = 0  # str的index
    str_idx_last = 0  # 加密结果暂存index
    buf = 0x00
    while op_idx < len(one_ops):
        if one_ops[op_idx] == 1:
            if buf == dest:
                ch = chr(fc)
                print(f'{index} {fc} {ch}')
                return ch
            op_idx += 1
        elif one_ops[op_idx] == 2:
            buf = one_ops[op_idx + 1] + str_buf[str_idx]
            buf = int_to_char(buf)  # C语言隐式类型转换
            op_idx += 2
        elif one_ops[op_idx] == 3:
            buf = str_buf[str_idx] - one_ops[op_idx + 1]
            buf = int_to_char(buf)  # C语言隐式类型转换
            op_idx += 2
        elif one_ops[op_idx] == 4:
            buf = one_ops[op_idx + 1] ^ \
                  char_to_logic(str_buf[str_idx])  # C语言和python位操作不同点
            buf = logic_to_char(buf)  # C语言隐式类型转换
            op_idx += 2
        elif one_ops[op_idx] == 5:
            buf = one_ops[op_idx + 1] * str_buf[str_idx]
            buf = int_to_char(buf)  # C语言隐式类型转换
            op_idx += 2
        elif one_ops[op_idx] == 6:
            op_idx += 1
        elif one_ops[op_idx] == 8:
            str_buf[str_idx_last] = buf
            op_idx += 1
            str_idx_last += 1
        elif one_ops[op_idx] == 11:
            buf = str_buf[str_idx] - 1
            buf = int_to_char(buf)  # C语言隐式类型转换
            op_idx += 1
        elif one_ops[op_idx] == 12:
            buf = str_buf[str_idx] + 1
            buf = int_to_char(buf)  # C语言隐式类型转换
            op_idx += 1


if __name__ == '__main__':
    opcodes = get_opcodes()
    print(opcodes)  # 打印所有操作
    str_buf = [0] * 200
    enc = [34, 63, 52, 50, 114, 51, 24, -89,
           49, -15, 40, -124, -63, 30, 122]
    ops = [[4, 16, 8, 3, 5, 1],
           [4, 32, 8, 5, 3, 1],
           [3, 2, 8, 11, 1],
           [12, 8, 4, 4, 1],
           [5, 3, 8, 3, 33, 1],
           [11, 8, 11, 1],
           [4, 9, 8, 3, 32, 1],
           [2, 81, 8, 4, 36, 1],
           [12, 8, 11, 1],
           [5, 2, 8, 2, 37, 1],
           [2, 54, 8, 4, 65, 1],
           [2, 32, 8, 5, 1, 1],
           [5, 3, 8, 2, 37, 1],
           [4, 9, 8, 3, 32, 1],
           [2, 65, 8, 12, 1], ]

    # 爆破输入
    flag = ''
    for index in range(15):  # 逐个爆破
        for i in range(0x80):  # 爆破0x80以下的可输入字符
            str_buf[0] = i
            ch = vm_operad(str_buf, ops[index], enc[index], index)
            if ch:
                flag += ch
    print(flag)
main函数

vm_operad函数

以下是opcodes的内容 

读取15个字符
10, 

对15个字符做操作
4, 16, 8, 3, 5, 1, 
4, 32, 8, 5, 3, 1, 
3, 2, 8, 11, 1, 
12, 8, 4, 4, 1, 
5, 3, 8, 3, 33, 1, 
11, 8, 11, 1, 
4, 9, 8, 3, 32, 1, 
2, 81, 8, 4, 36, 1, 
12, 8, 11, 1, 
5, 2, 8, 2, 37, 1, 
2, 54, 8, 4, 65, 1, 
2, 32, 8, 5, 1, 1, 
5, 3, 8, 2, 37, 1, 
4, 9, 8, 3, 32, 1, 
2, 65, 8, 12, 1, 

检查最终结果
7, 34, 
7, 63, 
7, 52, 
7, 50, 
7, 114, 
7, 51, 
7, 24, 
7, -89, 
7, 49, 
7, -15, 
7, 40, 
7, -124, 
7, -63, 
7, 30, 
7, 122

[羊城杯 2020]easyre

三次加密,第一层base64,第二层等分成4段调换位置,第三层增强的凯撒密码(数字也变,偏移3)。
密文:EmBmP5Pmn7QcPU4gLYKv5QcMmB3PWHcP5YkPq3=cT6QckkPckoRG
第三次解密:BjYjM2Mjk4NzMR1dIVHs2NzJjY0MTEzM2VhMn0=zQ3NzhhMzhlOD
第二层解密:R1dIVHs2NzJjYzQ3NzhhMzhlODBjYjM2Mjk4NzM0MTEzM2VhMn0=
第一层解密:GWHT{672cc4778a38e80cb362987341133ea2}

next

Pwn部分

test_your_nc

靶机node4.buuoj.cn:29299,使用命令 nc node4.buuoj.cn 29299,ls可以看到flag文件,cat flag得到 flag{4ab7f725-20fc-4fe8-99d2-409a1a69f208}

next

;