内涵的软件
首先使用file命令,发现exe是32位的,将其拖入ida中
进入main函数
点击进入main0函数
可以看到一串v2字符串
结合程序运行时出现的文字:”这里本来应该是答案的“
猜测v2就是答案
直接提交
发现不对
将开头修改为flag
提交后直接通过
新年快乐
首先使用file命令查看exe文件信息,发现是32位
尝试运行发现是要求输入flag
拖入ida进行静态分析
发现只有一个start函数,ida各种报错,无法分析
猜测使用了upx壳
使用exeinfope软件进行查看,upx壳实锤
脱壳后再次拖入ida
发现v4为"HappyNewYear!"
对于输入v5,直接与v4进行strncmp的比较
因此只需要与v4相同即可
尝试后发现没问题
套上flag提交
成功
guessgame
使用软件,发现是一个猜数游戏
拖入ida分析
发现整个代码与flag没有任何关系,就是一个猜测随机数的游戏
于是猜测flag隐藏在字符串常量中
进入字符串常量最顶部,发现如下字符串:
BJD{S1mple_ReV3r5e_W1th_0D_0r_IDA}
即为flag
helloword
下载后发现是apk文件
首先将apk后缀更改为zip
解压后发现文件夹中含有一个classes.dex文件
使用dex2jar软件将其转换为jar文件
使用jd-gui.jar对其进行逆向分析
在源代码中有com.example.helloword文件夹,用过Java的都知道com.example是什么东西,因此进入该目录下,发现有个MainActivity.class文件,查看源代码,发现有个flag字符串并对其使用了一个比较方法(compareTo),猜测这个就是需要的flag,提交后发现正确
xor
拖入ida进行静态分析,发现对输入进行了异或处理,处理结果符合某个特定的函数值即可求解
看到一个for循环,是对每个字符与前面一个字符进行异或
根据异或的性质可知,只需要再异或一次就能还原
所以从后往前进行异或
随便写个脚本计算一下:
#include <stdio.h>
int main(){
char flag[35];
flag[0] = 'f';
flag[1] = '\n';
flag[2] = 'k';
flag[3] = '\f';
flag[4] = 'w';
flag[5] = '&';
flag[6] = 'O';
flag[7] = '.';
flag[8] = '@';
flag[9] = '\x11';
flag[10] = 'x';
flag[11] = '\r';
flag[12] = 'Z';
flag[13] = ';';
flag[14] = 'U';
flag[15] = '\x11';
flag[16] = 'p';
flag[17] = '\x19';
flag[18] = 'F';
flag[19] = '\x1F';
flag[20] = 'v';
flag[21] = '"';
flag[22] = 'M';
flag[23] = '#';
flag[24] = 'D';
flag[25] = '\x0E';
flag[26] = 'g';
flag[27] = 6;
flag[28] = 'h';
flag[29] = '\x0F';
flag[30] = 'G';
flag[31] = '2';
flag[32] = 'O';
flag[33] = 0;
for (int i = 32; i >= 1; --i) {
flag[i] ^= flag[i - 1];
}
printf("%s", flag);
return 0;
}
// flag{QianQiuWanDai_YiTongJiangHu}
reverse3
进行初步检查后拖入ida进行静态分析
发现对于输入内容使用了一个函数进行变换
查看字符串发现使用了一个"ABCD…+/=“的字符串,猜测使用了BASE64编码
回到主函数,发现使用了一个str2
与base64后的结果进行比较
查看字符串可知:str2=e3nifIH9b_C@n@dH
明显不是base64的结果
回到主函数发现对base64结果进行了加法操作
编程进行反向操作后得到:e2lfbDB2ZV95b3V9
使用在线工具可得解码结果:{i_l0ve_you}
套上flag提交,通过
不一样的flag
首先测试一下软件
发现好像是个前进后退的游戏
猜测是一个迷宫
进入ida静态分析
发现当v8[x]==35
时为成功v8[x]==49
时失败
查阅ASCII发现35对应#
,49对应1
因此查找同时含有这两个字符的字符串:
*11110100001010000101111#
总长度为24,与5*v4+v5
的最大值一致,因此忽略-41
要求是不能进入1的位置而到达#
因此进行简单的推导即可得到要求的序列(完全可以写个代码跑一下,但由于这个题比较简单,直接手算即可)
得到输入序列:222441144222
套上flag提交即可
注:此题可以看作是一个5*5的迷宫:
* 1 1 1 1
0 1 0 0 0
0 1 0 1 0
0 0 0 1 0
1 1 1 1 #
1是障碍,0是可以走的路,*是起点,#是重点
这样就与运行程序时的 up, down, left, right 对应上了
SimpleRev
拖入ida
发现这道题涉及到类型转换,需要将int转换为char插入到字符串后面
需要注意的是在转换时要把顺序倒过来
转换后text为killshadow
,key为ADSFKNDCLS
随后将key转换为小写字母,即adsfkndcls
编程枚举求解
int main{
char text[25];
strcpy(text, "killshadow");
char key[25];
strcpy(key, "adsfkndcls");
char str2[25];
int textlen = strlen(text);
for (int i = 0; i < textlen; ++i) {
for (char c = 'A'; c <= 'z'; ++c) {
if (c <= 96 || c > 122) {
if (c > 64 && c <= 90) {
if(text[i] == (c - 39 - key[i] + 97) % 26 + 97) {
printf("%d%c ", i, c);
}
}
}
else {
if(text[i] == (c - 39 - key[i] + 97) % 26 + 97) {
printf("%d%c ", i, c);
}
}
}
}
return 0;
}
每个位置均可以算出两个解,一个是大写字母,一个是小写字母
全部挑选大写字母后提交,可以通过
想尝试一下其它组合方案是否也可以,但可惜提交平台不允许多次提交
Java逆向解密
将.class文件拖入jd-gui.jar中进行逆向
可以得到Reverse.class源代码:
import java.util.ArrayList;
import java.util.Scanner;
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 (int i = 0; i < arr.length; i++) {
int result = arr[i] + 64 ^ 0x20;
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 j = 0; j < KEY.length; j++)
KEYList.add(Integer.valueOf(KEY[j]));
System.out.println("Result:");
if (Resultlist.equals(KEYList)) {
System.out.println("Congratulations);
} else {
System.err.println("Error);
}
}
}
这段代码就是将输入的每个字符加64后与0x20进行异或
因此使用python进行暴力破解(其实完全可以反向计算就出来的,当时大意了)
key = [180, 136, 137, 147, 191, 137, 147, 191, 148, 136, 133, 191, 134, 140, 129, 135, 191, 65]
for k in key:
for c in range(0x21, 0x80):
if (c + 64) ^ 0x20 == k:
print chr(c)
# This_is_the_flag_!
[WUSTCTF2020]Crossfun
拖入ida,发现有个判断函数,里面对输入的每个字符进行了判断,把所有判断函数整合起来,就得到flag了
flag: wctf2020{cpp_@nd_r3verse_@re_fun}
[WUSTCTF2020]level1
拖入ida
stream = fopen("flag", "r");
fread(ptr, 1uLL, 0x14uLL, stream);
fclose(stream);
for ( i = 1; i <= 19; ++i ) {
if ( i & 1 )
printf("%ld\n", (unsigned int)(ptr[i] << i));
else
printf("%ld\n", (unsigned int)(i * ptr[i]));
}
显然 ptr
里面就是flag
看到 output.txt 里面正好有19行
对其进行逆变换(注意0)
ptr = [0, 198, 232, 816, 200, 1536, 300, 6144, 984, 51200, 570, 92160, 1200, 565248, 756, 1474560, 800, 6291456, 1782, 65536000]
flag = ''
for i in range(1, 20):
if i % 2 == 1:
flag += chr(ptr[i] >> i)
else:
flag += chr(ptr[i] // i)
print (flag)
得到flag ctf2020{d9-dE6-20c}
[WUSTCTF2020]level2
先使用upx进行脱壳
upx -d attachment
进入main函数
还没来得及按 tab
前就看到了flag
; __unwind {
lea ecx, [esp+4]
and esp, 0FFFFFFF0h
push dword ptr [ecx-4]
push ebp
mov ebp, esp
push ecx
sub esp, 14h
mov [ebp+var_C], offset flag ; "wctf2020{Just_upx_-d}"
sub esp, 0Ch
push offset aWhereIsIt ; "where is it?"
call puts
add esp, 10h
mov eax, 0
mov ecx, [ebp+var_4]
leave
lea esp, [ecx-4]
retn
; } // starts at 804887C
这件事情告诉我们还是要看汇编代码的
flag{Just_upx_-d}
[WUSTCTF2020]level3
首先尝试运行程序,发现是一个base64编码程序
进行测试后看到程序中输出一个字符串 Is there something wrong?
推测这个base64可能不正确
使用在线工具测试后发现大写字母出现错误
查看base64_table字符串,发现没有问题,说明源代码中有对字符串进行操作的部分
查看源代码,发现一个函数是 O_OLootAtYou
for ( i = 0; i <= 9; ++i ) {
v0 = base64_table[i];
base64_table[i] = base64_table[19 - i];
result = 19 - i;
base64_table[result] = v0;
}
主函数中说有一个奇怪的字符串,显然是用错误的加密得到的结果
对其进行更正后解密
import base64
str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
table = []
for c in str:
table.append(c)
for i in range(10):
c = table[i]
table[i] = table[19 - i]
table[19 - i] = c
crypto = "d2G0ZjLwHjS7DmOzZAY0X2lzX3CoZV9zdNOydO9vZl9yZXZlcnGlfD=="
msg = ""
for i in range(len(crypto)):
if ord(crypto[i]) >= ord('A') and ord(crypto[i]) <= ord('Z'):
for j in range(26):
if table[j] == crypto[i]:
msg += chr(j + ord('A'))
else:
msg += crypto[i]
print (msg)
print (base64.b64decode(msg))
运行得到flag为 wctf2020{Base64_is_the_start_of_reverse}
[WUSTCTF2020]level4
运行一下程序,发现这道题和结构体有关
拖入ida
看到主函数中输出的是type1和type2函数的结果
查看type1和type2
if ( a1 ) {
type1(*((_QWORD *)a1 + 1));
putchar(*a1);
result = type1(*((_QWORD *)a1 + 2));
}
if ( a1 ) {
type2(*((_QWORD *)a1 + 1));
type2(*((_QWORD *)a1 + 2));
result = putchar(*a1);
}
明显是二叉树的中序遍历和后序遍历
反推出前序遍历,得到flag: wctf2020{This_IS_A_7reE}
[WUSTCTF2020]funnyre
拖入ida后,通过start函数进入main
发现main函数未定义,按p创建函数后F5,查看代码
看到一串如下代码:
v7 = 0LL;
do
*(_BYTE *)(v6 + v7++ + 5) ^= 0x91u; // 每一项异或0x91
while ( v7 != 32 );
除此之外,还有一些错误代码标红
以及JUMPOUT函数
例如:
JUMPOUT(1, (char *)&loc_400B81 + 1);
JUMPOUT(0, (char *)&loc_400B81 + 1);
v89 = MEMORY[0xFFFFFFFF81003D16](v6);
JUMPOUT(v91, (char *)&loc_400B88 + 2);
*(_DWORD *)((unsigned int)(v89 - 1065138106) - 125LL) += v92;
*(_BYTE *)(a3 - 15) &= BYTE1(v90);
经过学习后发现JUMPOUT函数是经典花指令,需要patch去除
.text:0000000000400607 loc_400607: ; CODE XREF: main+64↓j
.text:0000000000400607 xor byte ptr [rdx+rax+5], 91h
.text:000000000040060C add rax, 1
.text:0000000000400610 cmp rax, 20h
.text:0000000000400614 jnz short loc_400607
.text:0000000000400616 jz short near ptr loc_40061A+1
.text:0000000000400618 jnz short near ptr loc_40061A+1
.text:000000000040061A
.text:000000000040061A loc_40061A: ; CODE XREF: main+66↑j
.text:000000000040061A ; main+68↑j
.text:000000000040061A call near ptr 0FFFFFFFF810037AFh
.text:000000000040061F jz short near ptr loc_400621+2
.text:0000000000400621
发现40061A这个位置其实被跳过了
将这个位置的数据变为90(即nop)
按c将其转换回汇编代码
得到正确代码
.text:0000000000400616 jz short loc_40061B
.text:0000000000400618 jnz short loc_40061B
.text:000000000040061A nop
.text:000000000040061B
.text:000000000040061B loc_40061B: ; CODE XREF: main+66↑j
.text:000000000040061B ; main+68↑j
.text:000000000040061B nop
.text:000000000040061C xor eax, eax
.text:000000000040061E
.text:000000000040061E loc_40061E: ; CODE XREF: main+7B↓j
.text:000000000040061E xor byte ptr [rdx+rax+5], 0CDh
.text:0000000000400623 add rax, 1
.text:0000000000400627 cmp rax, 20h
.text:000000000040062B jnz short loc_40061E
.text:000000000040062D xor eax, eax
.text:000000000040062F
在ida汇编模式中将后面几个标红的部分进行同样的修改,随后再F5即可查看到正确的代码
找到函数结尾
if ( memcmp(v5 + 5, &unk_4025C0, 0x20uLL) )
JUMPOUT(&loc_4005BB);
发现unk字符串,将其进行如上函数的逆变换,即可得到最终结果
我选择的方法是复制到vscode然后利用快捷键进行快速复制,最终得到答案
代码如下:
res = [0xD9, 0x2C, 0x27, 0xD6, 0xD8, 0x2A, 0xDA, 0x2D, 0xD7, 0x2C, 0xDC, 0xE1, 0xDB, 0x2C, 0xD9, 0xDD, 0x27, 0x2D, 0x2A, 0xDC, 0xDB, 0x2C, 0xE1, 0x29, 0xDA, 0xDA, 0x2C, 0xDA, 0x2A, 0xD9, 0x29, 0x2A]
add = [80, 64, 79, 30, 91, 9, 5, 99, 87, 83, 59, 58, 1, 90, 57, 65, 53, 41, 85, 89, 44, 70, 12, 84, 10, 74, 17, 38, 43, 33, 11, 94, 86, 55, 32, 97, 68, 50, 67, 71, 96, 56, 6, 73, 52, 42, 61, 69, 14, 75, 4, 40, 37, 15, 77, 2, 23, 62, 29, 49, 47, 27, 66, 82, 46, 19, 88, 63, 39, 35, 25, 51, 18, 92, 95, 3, 72, 48, 36, 93, 76, 22, 98, 81, 13, 45, 34, 78, 26, 54, 28, 31, 20, 16, 7, 24, 60, 21, 8]
xor1 = [0xF9, 0xA9, 0x4E, 0xD3, 0xC7, 0xE2, 0xD2, 0x33, 0xA8, 0x96, 0xBD, 0xC, 0x13, 0x2F, 0x73, 0x65, 0x9C, 0x12, 2, 0x32, 0x10, 0x84, 0xED, 0x95, 0x4D, 0x75, 0x2C, 0x5D, 0x39, 0x18, 0x4C, 0x49, 0x37, 0xF0, 0x99, 0x41, 0x86, 0x76, 0xF5, 5, 0xC8, 0x64, 0xFA, 0x50, 0x3B, 8, 0xE9, 0x23, 0xC3, 0x68, 0x67, 0xff, 0x7D, 0x9D, 0x1D, 0xDA, 0xD8, 0xEB, 0xF6, 0xE3, 0x98, 0xE1, 0x34, 0x82, 0x7F, 0xD5, 0xE7, 0xB8, 0xDC, 0x97, 0xA3, 7, 0xB6, 0xB, 0x14, 0xCE, 0x66, 0x62, 0xEF]
# 中间有一个0xff,对应这取反的那个循环
for i in range(len(res)):
for j in add:
res[i] -= j
res[i] %= 256
for i in range(len(res)):
for j in xor1:
res[i] ^= j
for i in range(len(res)):
res[i] += 128
res[i] %= 256
xor2 = [0x91, 0xCD, 0x6A, 0x59, 0xA, 0xF3, 0xCA, 0x3E, 0x6C, 0x4F, 0x24, 0x83, 0xC4, 0x53, 4, 0x9E, 0x42, 0xE, 0x8D, 0x38, 0x7A, 0xDD, 0x52, 0x1B, 0xAA, 0xAE, 0xF8, 0x58, 0xF2, 0x9F, 0x3C, 0xA1, 6, 0x78, 0x70, 0x28, 0xEA, 0x48, 0xE4, 0x6E, 0x40, 0x89, 0x16, 0xD7, 0xB5, 0xD, 0x17, 0x5A, 0xB1, 0x69, 0x5C, 0x21, 0xE5, 0x7E, 0x2A, 0x94, 0xBC, 1, 0x74, 0x57, 0x6D, 0x1E, 0xA2, 0x6B, 0x22, 0xC2, 0x3D, 0x44, 0x90, 0x8C, 0xB3, 0xA6, 0x79, 0x61, 0xD9, 0x5B, 0x1A, 0x43, 0x8F, 0xA5, 0xEE, 0x25, 0x46, 0xE6, 0x88, 0x20, 0x71, 0xE8, 9, 0x8A, 0x7B, 0xB4, 0x19, 0x15, 0x4A, 0x47, 0xDB, 0x72, 0x5F, 0x26, 0xA7, 0x8B, 0xBA, 0xBF, 0x87, 0x36, 0x3F, 0xFE, 0x77, 0x1C, 0x81, 0x11, 0x2E, 0x7C, 0x63, 0x45, 0xF4, 0x56, 0xF1, 0xB0, 0xD1, 0xE0, 0xF, 0x93, 0xD6, 0x1F, 0xCC, 0x4B, 0xCF, 0xDF, 0x55, 0xB9, 0x2B, 0x85, 0x31, 0x29, 0xFD, 0x3A, 0x5E, 0xDE, 3, 0xC6, 0xC1, 0xC5, 0x54, 0xBB, 0xFC, 0xBE, 0xEC, 0xC0, 0xAD, 0xA4, 0xD0, 0x35, 0xB7, 0x51, 0xAB, 0x2D, 0xAF, 0x92, 0x60, 0xAC, 0x30, 0xD4, 0xCB, 0x9B, 0x9A, 0xFB, 0x6F, 0xF7, 0x8E, 0xA0, 0x27, 0xC9]
for i in range(len(res)):
for j in xor2:
res[i] ^= j
print(chr(res[i]), end='')
print ()
得到 flag: 1dc20f6e3d497d15cef47d9a66d6f1af
[GUETCTF2019]re
先upx脱壳
然后拖入ida
找到关键字符串
进入函数
发现是对每一位进行乘法然后判断
直接扔到python里算一下,发现 a[6]
缺失,以及有一位算不出来,可能出现了溢出
用c语言把这一位算出来,得到是 a
对第7位进行暴力破解,发现1可以,得到flag
x = [ 1629056, 6771600, 3682944, 10431000, 3977328, 5138336, 7532250, 5551632, 3409728, 13013670, 6088797, 7884663, 8944053, 5198490, 4544518, 10115280,3645600, 9667504, 5364450, 13464540, 5488432, 14479500, 6451830, 6252576, 7763364, 7327320, 8741520, 8871876, 4086720, 9374400, 5759124 ]
a = [ 166163712 , 731332800 , 357245568 , 1074393000, 489211344 , 518971936 , 406741500 , 294236496 , 177305856 , 650683500, 298351053, 386348487, 438258597, 249527520, 445362764, 981182160, 174988800, 493042704, 257493600, 767478780, 312840624, 140451150, 316139670, 619005024, 372641472, 373693320, 498266640, 452465676, 208422720, 515592000, 719890500 ]
for i in range(len(x)):
if (a[i] % x[i] == 0):
print (chr(a[i] // x[i]), end='')
else:
print ('\n', i)
print ('\nfinish')
输出为
flag{e65421110ba03099
21
1c039337}
finish
flag: flag{e165421110ba03099a1c039337}
再写个c把这一位算一下(其实不如直接用c暴力破解了)
#include <stdio.h>
int main(){
char x;
for (x = 0x21; x < 0x7f; x++){
if (14479500 * x == 1404511500){
printf("%c\n", x);
}
}
printf("finish\n");
return 0;
}
[GUETCTF2019]number game
先拖入ida进行静态分析,查看main函数核心代码
__isoc99_scanf("%s", &v5, a3);
if ( (unsigned int)check_input((const char *)&v5) )// 长度为10且只能是"01234"
{
v3 = sub_400758((__int64)&v5, 0, 10);
sub_400807((__int64)v3, (__int64)&v7);
v9 = 0;
sub_400881((char *)&v7); // v7 = "0421421430"
if ( (unsigned int)sub_400917() )
{
puts("TQL!");
printf("flag{", &v7);
printf("%s", &v5);
puts("}");
}
else
{
puts("your are cxk!!");
}
}
输入的字符串需要通过两个if语句
先看第一个if语句对应的函数
if ( strlen(a1) == 10 )
{
for ( i = 0; i <= 9; ++i )
{
if ( a1[i] > 52 || a1[i] <= 47 )
{
puts("Wrong!");
return 0LL;
}
}
result = 1LL;
}
else
{
puts("Wrong!");
result = 0LL;
}
这是对输入数据的简单要求,要求是10个0-4自成的字符串
再查看第二个if中的函数
v1 = 1
for ( i = 0; i <= 4; ++i )
{
for ( j = 0; j <= 4; ++j )
{
for ( k = j + 1; k <= 4; ++k )
{
if ( *((_BYTE *)&unk_601060 + 5 * i + j) == *((_BYTE *)&unk_601060 + 5 * i + k) )
v1 = 0;
if ( *((_BYTE *)&unk_601060 + 5 * j + i) == *((_BYTE *)&unk_601060 + 5 * k + i) )
v1 = 0;
}
}
}
要求在某条件下unk_601060中的两个字符不相同
再查看一下中间的几个函数
先查看 sub_400881
函数
__int64 __fastcall sub_400881(char *a1)
{
__int64 result; // rax
byte_601062 = *a1;
byte_601067 = a1[1];
byte_601069 = a1[2];
byte_60106B = a1[3];
byte_60106E = a1[4];
byte_60106F = a1[5];
byte_601071 = a1[6];
byte_601072 = a1[7];
byte_601076 = a1[8];
result = (unsigned __int8)a1[9];
byte_601077 = a1[9];
return result;
}
是将a1中的数据放到指定的内存位置上
查看这个内存地址
data:0000000000601060 unk_601060 db 31h ; 1
.data:0000000000601061 db 34h ; 4
.data:0000000000601062 byte_601062 db 23h ; DATA XREF: sub_400881+F↑w
.data:0000000000601063 db 32h ; 2
.data:0000000000601064 db 33h ; 3
.data:0000000000601065 db 33h ; 3
.data:0000000000601066 db 30h ; 0
.data:0000000000601067 byte_601067 db 23h ; DATA XREF: sub_400881+1D↑w
.data:0000000000601068 db 31h ; 1
.data:0000000000601069 byte_601069 db 23h ; DATA XREF: sub_400881+2B↑w
.data:000000000060106A db 30h ; 0
.data:000000000060106B byte_60106B db 23h ; DATA XREF: sub_400881+39↑w
.data:000000000060106C db 32h ; 2
.data:000000000060106D db 33h ; 3
.data:000000000060106E byte_60106E db 23h ; DATA XREF: sub_400881+47↑w
.data:000000000060106F byte_60106F db 23h ; DATA XREF: sub_400881+55↑w
.data:0000000000601070 db 33h ; 3
.data:0000000000601071 byte_601071 db 23h ; DATA XREF: sub_400881+63↑w
.data:0000000000601072 byte_601072 db 23h ; DATA XREF: sub_400881+71↑w
.data:0000000000601073 db 30h ; 0
.data:0000000000601074 db 34h ; 4
.data:0000000000601075 db 32h ; 2
.data:0000000000601076 byte_601076 db 23h ; DATA XREF: sub_400881+7F↑w
.data:0000000000601077 byte_601077 db 23h ; DATA XREF: sub_400881+8D↑w
.data:0000000000601078 db 31h ; 1
发现与 unk_601060
对应
说明这个程序是将输入数据填到指定内存上,然后判断是否符合某个特殊条件
由于总共有25个字符,并且程序中出现了形如 5 * i + j
的值,以及题目名称 number_game
推测是一种类似数独的填数游戏,每行每列数字只能为01234且不能重复
查看已有的数字
14 23
30 1
0 23
3 0
42 1
那么最后v7中应该为 0421421430
简单查看了 sub_400758
和 sub_400807
后,确定这两个函数只进行了顺序的转换,只要得知其结果就可以
因此使用gdb进行动态调试,目的是找到这两个函数的顺序调换结果
首先 start
运行程序
gdb-peda$ start
在 0x40a62
处打上断点(输入后,第一个if前)
gdb-peda$ b *0x400a62
运行,并输入 0123456789
接下来需要绕过第一个if判断
在调用函数前修改eip,直接跳到jz后面
gdb-peda$ set var $rip=0x400a76
接下来需要查看两个函数运行后的值,这个值是 sub_400881
的参数,因此在call这个函数前打上断点,并查看 rdi 寄存器的结果即可
gdb-peda$ b *0x400aae
Breakpoint 3 at 0x400aae
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
RAX: 0x7ffffffedf40
RBX: 0x400b20 --> 0x41ff894156415741
RCX: 0xe
RDX: 0x7ffffffedf40
RSI: 0x7ffffffedf40
RDI: 0x7ffffffedf40
RBP: 0x7ffffffedf60
RSP: 0x7ffffffedf20
RIP: 0x400aae --> 0xb8fffffdcee8
R8 : 0x6033d0 --> 0x36 ('6')
R9 : 0x7c ('|')
R10: 0x4003ce --> 0x5f00636f6c6c616d ('malloc')
R11: 0x7fffff78bbe0 --> 0x6033e0 --> 0x0
R12: 0x4005e0 --> 0x89485ed18949ed31
R13: 0x7ffffffee050
R14: 0x0
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x400aa3: mov BYTE PTR [rbp-0x16],0x0
0x400aa7: lea rax,[rbp-0x20]
0x400aab: mov rdi,rax
=> 0x400aae: call 0x400881
0x400ab3: mov eax,0x0
0x400ab8: call 0x400917
0x400abd: test eax,eax
0x400abf: je 0x400afc
Guessed arguments:
arg[0]: 0x7ffffffedf40
[------------------------------------stack-------------------------------------]
Invalid $SP address: 0x7ffffffedf20
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 3, 0x0000000000400aae in ?? ()
注意到 RDI: 0x7ffffffedf40
因此查看这个内存地址下的值即可
gdb-peda$ x/2x 0x7ffffffedf40
0x7ffffffedf40: 0x3530343931383337 0x0000000000003632
根据小端模式
0123456789
被转换为 7381940526
根据这个信息将 0421421430
还原,得到 1134240024
运行程序
$ ./number_game
1134240024
TQL!
flag{1134240024}
[GUETCTF2019]encrypt
拖入ida,查看主函数
printf("please input your flag:", a2, v19);
scanf("%s", &s);
memset(&v9, 0, 0x408uLL);
sub_4006B6(&v9, (__int64)&v10, 8);
v3 = strlen(&s);
sub_4007DB(&v9, (__int64)&s, v3);
v4 = strlen(&s);
sub_4008FA((__int64)&s, v4, v19, &v6);
for ( i = 0; i <= 50; ++i )
{
if ( v19[i] != byte_602080[i] )
{
puts("Wrong");
return 0LL;
}
}
puts("Good");
在进行判断前执行了三个函数
最后一个函数是一个类base64
while ( v20 < a2 )
{
v4 = v20++;
v17 = *(_BYTE *)(v4 + a1);
if ( v20 >= a2 )
{
v6 = 0;
}
else
{
v5 = v20++;
v6 = *(_BYTE *)(v5 + a1);
}
v18 = v6;
if ( v20 >= a2 )
{
v8 = 0;
}
else
{
v7 = v20++;
v8 = *(_BYTE *)(v7 + a1);
}
v9 = v8;
v10 = v19;
v11 = v19 + 1;
a3[v10] = ((v17 >> 2) & 0x3F) + 61;
v12 = v11++;
a3[v12] = ((((v18 & 0xFF) >> 4) | 16 * v17) & 0x3F) + 61;
a3[v11] = ((((v9 & 0xFF) >> 6) | 4 * v18) & 0x3F) + 61;
v13 = v11 + 1;
v19 = v11 + 2;
a3[v13] = (v9 & 0x3F) + 61;
}
if ( a2 % 3 == 1 )
{
a3[--v19] = 61;
}
else if ( a2 % 3 != 2 )
{
goto LABEL_15;
}
a3[v19 - 1] = 61;
将每一位减少61后映射到base64的字符串上就可以得到base64结果
str = 'Z`TzzTrD|fQP[_VVL|yneURyUmFklVJgLasJroZpHRxIUlH\\vZE' # 注意转义字符
base = ''
map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
for c in str:
base += map[ord(c) - 61]
print (base)
# base = 'djX99X1H/pUTeiZZP/8xoYV8YwJuvZNqPk2N1ydzLV7MYvLf5dI'
# 最后再补充一个 '=' 即可
破解完这个函数后再查看上两个函数
根据调用判断第一个函数为第二个函数提供密钥,这个应该是固定结果,动态调试即可得到
查看第二个函数
for ( i = 0; i < a3; ++i ) {
v7 = (unsigned __int8)(v7 + 1);
v3 = *(_DWORD *)(4LL * v7 + v9);
v8 = (unsigned __int8)(v8 + v3);
v4 = *(_DWORD *)(4LL * v8 + v9);
*(_DWORD *)(v9 + 4LL * v7) = v4;
*(_DWORD *)(v9 + 4LL * v8) = v3;
*(_BYTE *)(i + a2) ^= *(_BYTE *)(4LL * (unsigned __int8)(v3 + v4) + v9);
}
这对输入的每一位做了个异或运算,异或的数字也是固定的,直接进行动态调试即可(根据经验猜测是RC4或者与其类似)
根据base64的位数得知flag最多有39位
进行动态调试
gdb-peda$ start
gdb-peda$ c
Continuing.
please input your flag:flag{abcdefghijklmnopqrstuvwxyz0123456}
[----------------------------------registers-----------------------------------]
RAX: 0x27 ("'")
RBX: 0x0
RCX: 0x0
RDX: 0x7ffffffed9c0 ("flag{abcdefghijklmnopqrstuvwxyz0123456}")
RSI: 0x7ffffffed9b0 --> 0x4010202030302010
RDI: 0x7ffffffed9e0 --> 0x7d363534333231 ('123456}')
RBP: 0x7ffffffeded0 --> 0x400c80 --> 0x41ff894156415741
RSP: 0x7ffffffed590 --> 0x0
RIP: 0x400bbc --> 0xfffaf08d8d48c289
R8 : 0x0
R9 : 0x0
R10: 0x3
R11: 0x7fffff18ee90 (<__memset_avx2_unaligned_erms>: vmovd xmm0,esi)
R12: 0x4005c0 --> 0x89485ed18949ed31
R13: 0x7ffffffedfb0 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x400bad: lea rax,[rbp-0x510]
0x400bb4: mov rdi,rax
0x400bb7: call 0x400550 <strlen@plt>
=> 0x400bbc: mov edx,eax
0x400bbe: lea rcx,[rbp-0x510]
0x400bc5: lea rax,[rbp-0x930]
0x400bcc: mov rsi,rcx
0x400bcf: mov rdi,rax
[------------------------------------stack-------------------------------------]
0000| 0x7ffffffed590 --> 0x0
0008| 0x7ffffffed598 --> 0x5f4f636d ('mcO_')
0016| 0x7ffffffed5a0 --> 0x0
0024| 0x7ffffffed5a8 --> 0x31000000b0
0032| 0x7ffffffed5b0 --> 0x7000000075 ('u')
0040| 0x7ffffffed5b8 --> 0xdf000000f8
0048| 0x7ffffffed5c0 --> 0x3c00000007
0056| 0x7ffffffed5c8 --> 0x7100000078 ('x')
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 2, 0x0000000000400bbc in ?? ()
gdb-peda$ x/10x $rdx
0x7ffffffed9c0: 0x6362617b67616c66 0x6b6a696867666564
0x7ffffffed9d0: 0x737271706f6e6d6c 0x307a797877767574
0x7ffffffed9e0: 0x007d363534333231 0x0000000000000000
0x7ffffffed9f0: 0x0000000000000000 0x0000000000000000
0x7ffffffeda00: 0x0000000000000000 0x0000000000000000
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
RAX: 0x7ffffffed5a0 --> 0x1c00000027
RBX: 0x0
RCX: 0x138
RDX: 0x1c
RSI: 0x7d ('}')
RDI: 0x7ffffffed5a0 --> 0x1c00000027
RBP: 0x7ffffffeded0 --> 0x400c80 --> 0x41ff894156415741
RSP: 0x7ffffffed590 --> 0x0
RIP: 0x400bd7 --> 0x48fffffaf0858d48
R8 : 0x0
R9 : 0x0
R10: 0x3
R11: 0x7fffff18ee90 (<__memset_avx2_unaligned_erms>: vmovd xmm0,esi)
R12: 0x4005c0 --> 0x89485ed18949ed31
R13: 0x7ffffffedfb0 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x400bcc: mov rsi,rcx
0x400bcf: mov rdi,rax
0x400bd2: call 0x4007db
=> 0x400bd7: lea rax,[rbp-0x510]
0x400bde: mov rdi,rax
0x400be1: call 0x400550 <strlen@plt>
0x400be6: mov esi,eax
0x400be8: lea rcx,[rbp-0x93c]
[------------------------------------stack-------------------------------------]
0000| 0x7ffffffed590 --> 0x0
0008| 0x7ffffffed598 --> 0x5f4f636d ('mcO_')
0016| 0x7ffffffed5a0 --> 0x1c00000027
0024| 0x7ffffffed5a8 --> 0x67000000b0
0032| 0x7ffffffed5b0 --> 0x4b000000b8
0040| 0x7ffffffed5b8 --> 0xe000000069
0048| 0x7ffffffed5c0 --> 0x3f000000b9
0056| 0x7ffffffed5c8 --> 0xc5000000ec
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 3, 0x0000000000400bd7 in ?? ()
gdb-peda$ x/10x 0x7ffffffed9c0
0x7ffffffed9c0: 0xc6ad437df5fd3576 0xa862a26e0d237b16
0x7ffffffed9d0: 0x2c84ae7c0c342488 0x5e656c6895cb5d7c
0x7ffffffed9e0: 0x00e699b5d8f9689b 0x0000000000000000
0x7ffffffed9f0: 0x0000000000000000 0x0000000000000000
0x7ffffffeda00: 0x0000000000000000 0x0000000000000000
将输入在函数执行前后两次的结果进行异或即可得到异或的数字,再与base64解码的结果进行异或即可,注意大小端问题
from libnum import n2s, s2n
before = 0x7d363534333231307a797877767574737271706f6e6d6c6b6a6968676665646362617b67616c66
after = 0xe699b5d8f9689b5e656c6895cb5d7c2c84ae7c0c342488a862a26e0d237b16c6ad437df5fd3576
flag = b"v5\xfd\xf5}G\xfe\x95\x13z&Y?\xff1\xa1\x85|c\x02n\xbd\x93j>M\x8d\xd7's-^\xccb\xf2\xdf\xe5\xd2"[::-1]
print (len(flag))
print (n2s(s2n(flag) ^ before ^ after)[::-1])
# b'flag{e10adc3949ba59abbe56e057f20f883e}\x9b'
得到flag
flag{e10adc3949ba59abbe56e057f20f883e}
[MRCTF2020]Transform
dword = [ 9, 0xa, 0xf, 0x17, 0x7, 0x18, 0xc, 0x6, 0x1, 0x10, 0x3, 0x11, 0x20, 0x1D, 0x0b, 0x1e, 0x1b, 0x16, 0x4, 0x0d, 0x13, 0x14, 0x15, 0x2, 0x19, 0x5, 0x1f, 0x8, 0x12, 0x1a, 0x1c, 0xe, 0 ]
flag = [ 0x67, 0x79, 0x7b, 0x7f, 0x75, 0x2b, 0x3c, 0x52, 0x53, 0x79, 0x57, 0x5E, 0x5D, 0x42, 0x7B, 0x2D, 0x2A, 0x66, 0x42, 0x7E, 0x4C, 0x57, 0x79, 0x41, 0x6B, 0x7E, 0x65, 0x3C, 0x5C, 0x45, 0x6F, 0x62, 0x4D, 0x3f]
print(len(dword), len(flag))
for i in range(len(dword)):
flag[i] ^= dword[i]
code = [ 0 for i in range(33)]
for i in range(len(dword)):
code[dword[i]] = flag[i]
for i in range(len(code)):
print(chr(code[i]), end = '')
print()
flag: MRCTF{Tr4nsp0sltiON_Clph3r_1s_3z}
[MRCTF2020]Xor
flag = 'MSAWB~FXZ:J:`tQJ"N@ bpdd}8g'
for i in range(27):
print(chr(ord(flag[i]) ^ i), end='')
print()
flag: MRCTF{@_R3@1ly_E2_R3verse!}
[MRCTF2020]hello_world_go
.rodata:00000000004D3C58 unk_4D3C58 db 66h ; f ; DATA XREF: main_main:loc_49A40A↑o
.rodata:00000000004D3C58 ; main_main+25C↑o
.rodata:00000000004D3C59 db 6Ch ; l
.rodata:00000000004D3C5A db 61h ; a
.rodata:00000000004D3C5B db 67h ; g
.rodata:00000000004D3C5C db 7Bh ; {
.rodata:00000000004D3C5D db 68h ; h
.rodata:00000000004D3C5E db 65h ; e
.rodata:00000000004D3C5F db 6Ch ; l
.rodata:00000000004D3C60 db 6Ch ; l
.rodata:00000000004D3C61 db 6Fh ; o
.rodata:00000000004D3C62 db 5Fh ; _
.rodata:00000000004D3C63 db 77h ; w
.rodata:00000000004D3C64 db 6Fh ; o
.rodata:00000000004D3C65 db 72h ; r
.rodata:00000000004D3C66 db 6Ch ; l
.rodata:00000000004D3C67 db 64h ; d
.rodata:00000000004D3C68 db 5Fh ; _
.rodata:00000000004D3C69 db 67h ; g
.rodata:00000000004D3C6A db 6Fh ; o
.rodata:00000000004D3C6B db 67h ; g
.rodata:00000000004D3C6C db 6Fh ; o
.rodata:00000000004D3C6D db 67h ; g
.rodata:00000000004D3C6E db 6Fh ; o
.rodata:00000000004D3C6F db 7Dh ; }
[MRCTF2020]PixelShooter
先使用NoxPlayer打开这个apk,发现游戏结束后有个flag信息,说得分不够高,说明flag会在游戏结束时根据得分获得
使用apktool解压
$ java -jar apktool_2.5.0.jar d PixelShooter.apk
这个显然是unity3d逆向,找到 /assets/bin/Data/Managed
,使用 dnSpy打开 Assembly-CSharp.dll
看到有个gameController,其中有个gameOver
public void GameOver()
{
this.isGameOver = true;
this.UI.GetComponent<UIController>().GameOver(this.score, this.bestScore);
if (PlayerPrefs.HasKey("bestScore"))
{
this.bestScore = Mathf.Max(this.score, PlayerPrefs.GetInt("bestScore"));
}
else
{
this.bestScore = this.score;
}
base.GetComponent<AudioSource>().Stop();
}
有个获取ui的函数,查看
public void GameOver(int score, int bestScore)
{
this.pad.SetActive(false);
Time.timeScale = 0f;
string text = "您的飞机已坠毁\n";
if (bestScore < score)
{
string text2 = text;
text = string.Concat(new object[]
{
text2,
"获得最高分:",
score,
"!\n"
});
PlayerPrefs.SetInt("bestScore", score);
}
if (score < 20)
{
text += "少年继续努力!要拿到flag还差亿点点\n";
}
else if (score < 100)
{
text += "战绩不错!但是要拿到flag还差亿点";
}
else if (score < 500)
{
text += "惊人的成绩!!但是要拿到flag还差一点\n";
}
else
{
text += "MRCTF{Unity_1S_Fun_233}\n";
}
if (Time.time - this.lastTime < 15f)
{
text += "以及,别作死啊!\n";
}
else if (Time.time - this.lastTime < 60f)
{
text += "以及注意闪避!";
}
this.gameOverText.text = text;
this.gameOverUI.SetActive(true);
}
拿到flag