文章目录
- easyre
- reverse1
- reverse2
- 内涵的软件
- 新年快乐[upx脱壳]
- xor
- reverse3[逆算法]
- helloword[APK]
- 不一样的flag[maze]
- SimpleRev[大小端]
- [GXYCTF2019]luck_guy
- Java逆向解密
- [BJDCTF2020]JustRE[gui程序]
- 刮开有奖[findcrypt插件]
- 简单注册器[APK]
- [GWCTF 2019]pyre[python反汇编]
- [ACTF新生赛2020]easyre
- findit[APK]
- [ACTF新生赛2020]rome
- rsa[解析公钥,解密文件]
- [FlareOn4]login
- [WUSTCTF2020]level1
- [GUET-CTF2019]re[z3约束器]
- CrackRTF[Resourcehacker]
- [2019红帽杯]easyRE[fini段]
- [MRCTF2020]Transform[算法异或]
- [WUSTCTF2020]level2
- [SUCTF2019]SignIn[RSA]
- [ACTF新生赛2020]usualCrypt[base64变表]
- [HDCTF2019]Maze[花指令]
- [MRCTF2020]Xor[手撕main汇编]
- [MRCTF2020]hello_world_go
easyre
查看程序基本信息,64bit 无壳
ida64打开一下
找到main函数直接F5发现了flag
reverse1
同样的步骤发现是64bit无壳程序
拖进ida64进去,找到main函数
逻辑就是让我们输入一个字符串,与它的Str2作对比,如果相等就是我们的flag,所以我们只要看一下Str2是什么,然后再做一下运算即可
reverse2
64bit elf文件
逻辑与上道题类似,也是通过找到原来的flag进行一定的运算
内涵的软件
查壳位数,是32位的
打开软件等待过后并没有出现flag
进入main函数发现了flag,v5变量
新年快乐[upx脱壳]
查一下32位有upx壳
用upx.exe -d xxx脱壳找到main函数
非常简单的逻辑,只要你输入的字符串和"HappyNewYear!"相等就行,也就是正确的flag
xor
查一下发现是64bitMACos文件
找到main函数,简单分析一下逻辑
找到global:FKWOXZUPFVMDGH
按A转换字符串,然后shift+E导出数据
脚本处理一下,因为 0^0 = 0 0^1 = 1
所以 0^a = a
又 a^b = c, b = a^c = a^(a^b) = 0^b = b
然后最后的逻辑就是让运算完的b中的元素分别等于global中的元素
也即b[1] = global[1]、b[2] = global[2]
又因为b[1] = b[1] ^ b[0] = global[1] ^ global[0]、b[2] = b[2] ^ b[1] = b[2] ^ b[1]^ b[0] = b[2] ^ global[1] = global[2] ^ global[1]
所以推下来b[ i ] = global[ i ] ^ global[ i - 1 ]
aFK =[
0x66, 0x0A, 0x6B, 0x0C, 0x77, 0x26, 0x4F, 0x2E, 0x40, 0x11,
0x78, 0x0D, 0x5A, 0x3B, 0x55, 0x11, 0x70, 0x19, 0x46, 0x1F,
0x76, 0x22, 0x4D, 0x23, 0x44, 0x0E, 0x67, 0x06, 0x68, 0x0F,
0x47, 0x32, 0x4F, 0x00
]
flag = chr(aFK[0])
for i in range(1,len(aFK)-1):#第一个字符已经确定了,因为题目中下标从1开始的
flag += chr(aFK[i] ^ aFK[i-1])
print(flag)
reverse3[逆算法]
查壳和位数,是一个32位
找到main函数,看一下逻辑
看到首先把destination数组清零,然后自己输入一个flag,用str变量接收,然后v4变量的值为sub_4110BE函数的返回值,最后strcpy到destination里面,然后在对destination的每个元素,分别加上对应的下标。使最后的结果为字符串e3nifIH9b_C@n@dH。
再看一下函数sub_4110BE函数
然后找到加密过程中有一个aAbcdefghijklmn的变量,它的值与base64编码的固定变量一致,那就确定这个函数是进行base64加密的了。
所以最后求flag的脚本如下:
import base64
flag = ''
res = ''
str1 = "e3nifIH9b_C@n@dH"
for i in range(len(str1)):
res += chr(ord(str1[i]) - i)
print("now str1 is :", res)
flag = base64.b64decode(res)
print("flag is :", flag)
helloword[APK]
因为是一个apk安卓的一个安装包,所以我们用jadx打开看一下,并且找到main
直接看到了flag,所以这道题主要考我们会不会对安卓的安装包进行逆向
不一样的flag[maze]
是一个32位的程序
找到main函数
这道题一开始看了半天也没看出来,结果发现是一个maze迷宫题,因为以前没接触过所以有点犹豫了。
由所给的字符串可以看出迷宫是如下:
* 1 1 1 1
0 1 0 0 0
0 1 0 1 0
0 0 0 1 0
1 1 1 1 #
然后注意一下几个exit函数,也就是迷宫的规则。
第一个exit是判断输入错误的不用管
第二个是判断迷宫越界的,因为迷宫的每一行只有五个数,对应下标是0-4,所以一旦超过5,表明越界了。
第三个表明在走迷宫的时候不能碰到1,否则直接结束
所以由上面的那个迷宫我们可以判断出走的顺序为:
下下下右右上上右右下下下对应输入的数字为222441144222
所以flag就是flag{222441144222}
SimpleRev[大小端]
查看一下,是一个64bit的elf文件
看一下main函数,没有什么重要的东西,除了几个函数
其中Decry是进入游戏的关键函数,Exit函数只是退出函数,我们跟进一下Decry函数
其中重要的一段代码是如下:
其中join函数是字符串拼接函数
所以text的值为"killswodah"
key的值首先为key1 ADSFK
然后再拼接src变量 SLCDN
所以最后key的值为 ADSFKSLCDN 长度为10
然后进入循环10次,分别将key的每一个元素加32,即大写变小写
最后key就变为了 adsfkslcdn
分割线
上网首先查了一下资料,对于x64体系结构,通常使用小端序(Little Endian)来存储数据。在x64架构上运行的操作系统,例如Windows、Linux和macOS,都使用小端序作为默认的字节序。
在x64的ELF文件中,数据的存放方式也遵循小端序。最低有效字节存储在最低的内存地址,而最高有效字节存储在最高的内存地址。
但是ida打开以后,会将其自动转换为大端存放,所以在进行数据合并、转换等之类的操作时,要记得转换为正确的小端存放顺序(倒序存放)
所以更改一下上面的错误,key拼接完src以后转换小写的值是 adsfkndcls
同理text的值为 killshadow
然后最后通过输入的字符,分别给str2字符串赋值,最后看是否等于text如果等于则代表输入的字符串为正确的flag.
我们只能输入大写字母,因为代码只对大写字母进行了处理
所以写一下脚本
flag = ''
res = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
text = 'killshadow'
key = 'adsfkndcls'
for i in range(len(key)):
ch = text[i]
for j in res:
if (ord(j) - 39 - ord(key[i % len(key)]) +97 ) % 26 +97 == ord(ch):
flag += j
print("flag{"+flag+"}")
[GXYCTF2019]luck_guy
首先还是查一下有没有壳,发现是64位ELF文件
进入main函数看一下
可以看到代码的逻辑就是让我们输入flag然后进行比较。所以关键就是看一下patch_me这个函数的逻辑
然后发现只要a1模2等于1,那么就会直接结束,否则直接进函数get_flag函数
看一下其中的case1
首先把f1拼接到s中,然后再把f2拼接过去 ,最后输出flag,但是发现f2并没有进行初始化,然后看一下后面的case有没有进行初始化
发现是把一个字符串"icugof\x7F"复制到了s中,然后再把s拼接到f2
所以我们首先写一个脚本来看一下f2最后是什么
#include<stdio.h>
#include<string.h>
int main(){
char str[12]="icug`of\x7F";
int i;
for (i = 0; i<=7; ++i)
{
if (i % 2 == 1)
{
str[i] -= 2;
}
else
{
--(str[i]);
}
}
puts(str);
return 0;
}
可以看出来是hate_me}
再加上前面f1的内容
所以最终结果是GXY{do_not_hate_me}
Java逆向解密
把下载好的class文件放进jd-gui中看看
着重看一下Encrypt函数再结合一下题目的描述,是找不到秘钥了,但是写了秘钥验证算法,也就是反推秘钥。
其中比较秘钥的代码为Resultlist.equals(KEYList),即比较KEYList与Resultlist的元素
其中KEYList为180, 136, 137, 147, 191, 137, 147, 191, 148, 136,
133, 191, 134, 140, 129, 135, 191, 65
写一个脚本跑一下
KEYList=[180, 136, 137, 147, 191, 137, 147, 191, 148, 136, 133, 191, 134, 140, 129, 135, 191, 65]
flag = ''
for i in range(len(KEYList)):
for j in range(32,126):
if (j + 64 ^ 0x20) == KEYList[i]:
flag += chr(j)
print(flag)
最终所得即为flag
[BJDCTF2020]JustRE[gui程序]
依旧是查一下,32位无壳
打开搜索main函数发现是一种创建GUI的函数WinMain,也就是图形化程序
就是一些初始化句柄的操作,然后就会想到最后的flag肯定是通过字符串的形式出来的,所以索性Shift+F12找一下字符串
找到了类似flag的变量,并且发现引用是在一个DialogFunc的函数中
于是得到flag,具体想看一下逻辑也不行,因为都是外部调用的函数extern
BJD{1999902069a45792d233ac}
刮开有奖[findcrypt插件]
32bit无壳
打开是个类似于刮刮乐的程序
ida32打开又是这种GUI程序
其中只有一个有用的函数DialogFunc跟进看看,找到了核心代码,只要让String的每一个元素对应与其相等即可
然后看到最后一个if比较,就能知道如果String的0-7与if条件符合的话,那么就是flag了
首先看一下String0,因为是由v7赋值的所以还要继续跟进一下sub_4010F0这个函数
sub_4010F0
还需要注意的地方:
- 这个v7其实是一个数组,因为地址是连续的(从v7[2] - v16)
所以归根结底,v7这个数组的值为"ZJSECaNH3ng"
- 代码里面的4*i其实就是我们每个数组的下标加一的意思,因为int占4字节,此时 i 乘以4后以后,正好是每一个元素存在数组的位置偏移,与上图对应。所以v6其实是等于a1[i]
直接自己重新写一下跑一下就行
#include<stdio.h>
int sub_4010F0(char *a1,int a2, int a3);
int main()
{
char str[] = "ZJSECaNH3ng";
sub_4010F0(str,0,10);
puts(str);
return 0;
}
int sub_4010F0(char *a1,int a2, int a3)
{
int result; // eax
int i; // esi
int v5;
int v6; // edx
result = a3;
for ( i = a2; i <= a3; a2 = i )
{
v5 = i;
v6 = a1[i];
if ( a2 < result && i < result )
{
do
{
if ( v6 > a1[result] )
{
if ( i >= result )
break;
++i;
a1[v5] = a1[result];
if ( i >= result )
break;
while ( a1[i] <= v6 )
{
if ( ++i >= result )
goto LABEL_13;
}
if ( i >= result )
break;
v5 = i;
a1[result] = a1[i];
}
--result;
}
while ( i < result );
}
LABEL_13:
a1[result] = v6;
sub_4010F0(a1, a2, i - 1);
result = a3;
++i;
}
return result;
}
所以v7经过处理以后就是3CEHJNSZagn
然后接着看sub_401000函数因为他涉及到了v4、v5变量
sub_401000
乍一看这个函数比较复杂,也没有特别的提示,但是一旦注意到有个特殊的变量就知道是什么了
打开一看这明显和以前做过的base64加密要用的数据一样,所以断定这个函数就是base64加密的函数
或者用findcrypt插件直接搜索一下
所以这段代码的含义就是让经过base64加密的v4和v5分别等于ak1w,V1Ax,那我们直接base64解密一下即可
ak1w -> base64-decode -> jMp
V1Ax -> base64-decode -> WP1
再结合赋值方式(注意顺序!)可得String2-7
所以综上可以推出String的每一个元素分别如下
用脚本跑一下
String = ''
dic = '3CEHJNSZagn'
v18='WP1'
String += chr(ord(dic[0]) + 34)
String += dic[4]
String += v18[0]
String += v18[1]
String += v18[2]
v18 = 'jMp'
String += v18[0]
String += v18[1]
String += v18[2]
print('flag{'+String+'}')
简单注册器[APK]
是一个简单的APK文件,直接jadx打开
然后找到关键的一个方法
但是看到逻辑
flag = (xx.length() == 32 && xx.charAt(31) == 'a' && xx.charAt(1) == 'b' && (xx.charAt(0) + xx.charAt(2)) + (-48) == 56) ? 0 : 0;
这句flag的值无论怎么样都是0,所以也就无法注册了,所以我们直接看flag=1的代码
直接用java跑一下
public class register{
public static void main(String[] args) {
char[] x = "dd2940c04462b4dd7c450528835cca15".toCharArray();
x[2] = (char) ((x[2] + x[3]) - 50);
x[4] = (char) ((x[2] + x[5]) - 48);
x[30] = (char) ((x[31] + x[9]) - 48);
x[14] = (char) ((x[27] + x[28]) - 97);
for (int i = 0; i < 16; i++) {
char a = x[31 - i];
x[31 - i] = x[i];
x[i] = a;
}
String bbb = String.valueOf(x);
System.out.println("flag{" + bbb + "}");
}
}
[GWCTF 2019]pyre[python反汇编]
这一看题目就是对python进行逆向的,然后用uncompyle6进行反编译
或者随便找一个在线网站丢进去即可,随便一个网站都可以
https://tool.lu/pyc/
https://www.lddgo.net/string/pyc-compile-decompile
uncompyle6 attachment.pyc > attachment.py
因为input1是我们输入的flag所以要反推flag,首先从异或这里开始,因为题目中给的code是经过xor运算过后的,所以我们要知道最开始的code是什么样,又因为对于code[1]来说,original_code[1] = code[1] ^ original_code [2]
再加上对于字符异或实质上就是对其ascii值异或.
所以写个脚本跑一下
code = ['\x1f', '\x12', '\x1d', '(', '0', '4', '\x01', '\x06', '\x14', '4', ',', '\x1b', 'U', '?', 'o', '6', '*', ':', '\x01', 'D', ';', '%', '\x13']
flag = ''
# 将其都转换为ascii值
for i in range(len(code)):
code[i] = ord(code[i])
print(code)
#从最后一个到第一个,因为无论怎么运算最后一个元素都与最初的一致
for j in range (len(code)-2,-1,-1):
code[j] = code[j] ^ code[j+1]
print (code)
#然后每个元素减去自己的下标
for k in range (len(code)):
code[k] = code[k] - k
if code[k] < 0:
code[k] += 128
flag += chr(code[k])
print(flag)
[ACTF新生赛2020]easyre
打开发现有upx壳脱一下壳
再检测一下发现无壳了,且为32位
定位关键函数
分析一下逻辑
首先第一个if告诉flag的前5位与最后一位分别是ACTF{}
那么就想到flag应该是 v6 + v5 + v10组成的,其中ACTF{}分别为v6、v10
所以现在只要求v5即可,然后由定义知道v5是一个共12字节的数组
然后到for循环,就是让 v4[i] == data_start[ v5[i] -1 ] 也即在
``
我们现在要求v5的具体值,比如当i=0时,data_start[ v5[0] - 1] = ‘*’
假如*在data_start的80索引下,那么此时v5[0] -1 = 80 v5[0] = 80 +1 =81
所以依此类推,写一个脚本
v4 = "*F'\"N,\"(I?+@"
#要把27h转换为单引号,且要转义一下 \'
data_start = '~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)(\'&%$# !"'
flag = ''
for ch in v4:
flag += chr(data_start.find(ch) + 1) #find方法会从字符串中找其子串首次出现的位置并返回其下标
print("flag{"+flag+"}")
findit[APK]
是一个apk文件,用jadx打开看一下有没有什么关键信息
找到MainActivity
提示我们一个flag就在这里,然后找到对应的算法,用java跑一下即可
public class wifi{
public static void main(String[] args) {
final char[] b = {'p', 'v', 'k', 'q', '{', 'm', '1', '6', '4', '6', '7', '5', '2', '6', '2', '0', '3', '3', 'l', '4', 'm', '4', '9', 'l', 'n', 'p', '7', 'p', '9', 'm', 'n', 'k', '2', '8', 'k', '7', '5', '}'};
char[] y = new char[38];
for (int i2 = 0; i2 < 38; i2++) {
if ((b[i2] >= 'A' && b[i2] <= 'Z') || (b[i2] >= 'a' && b[i2] <= 'z')) {
y[i2] = (char) (b[i2] + 16);
if ((y[i2] > 'Z' && y[i2] < 'a') || y[i2] >= 'z') {
y[i2] = (char) (y[i2] - 26);
}
} else {
y[i2] = b[i2];
}
}
String n = String.valueOf(y);
System.out.println(n);
}
}
[ACTF新生赛2020]rome
首先先查看一下是什么位数的程序,32位
定位到main函数中的func函数
发现这段代码的逻辑与上一道题一样,就是flag是由多个部分组合而成的,而且这一堆就是某个算法
第一个while是一个算法,第二个while是循环判断是否与v12一致,也即某个字符串经过第一个while的算法,得出的结果与v12字符串一致,相当于就是对于算法的逆向
写个脚本跑一下吧,直接暴力破解
v12 = "Qsw3sj_lz4_Ujw@l"
flag = ''
ascii_value=[]
for i in range(len(v12)):
ascii_value.append(ord(v12[i])) #先都转换为ASCII值
print(ascii_value)
for j in range(len(v12)):
for ch in range(32,127):
k = ch
if 64 < ch <= 90:
ch = (ch - 51) % 26 + 65
if 96 < ch <= 122:
ch = (ch - 79) % 26 + 97
if chr(ch) == v12[j]:
flag += chr(k)
print("flag{" +flag + "}")
rsa[解析公钥,解密文件]
第一次见到这种加密题,也没有头绪,于是就直接看了一下wp,虽然知道这是ras加密。但是看到两个文件无法下手。
然后得知,从公钥文件,可以推出 e和n
因为 C = m ^ e mod n de = 1mod(φ(n))
直接从在线公钥解析网站中提取一下
https://www.ssleye.com/ssltool/pub_asysi.html
e = 65537
n = 86934482296048119190666062003494800588905656017203025617216654058378322103517
然后再找一个分解质因数的网站或者用yahu
那个数字王国分解的位数为多为70位,这个77位
http://www.factordb.com/index.php
p = 285960468890451637935629440372639283459
q = 304008741604601924494328155975272418463
有了p q n就可以直接解密了
上脚本
import gmpy2
# from Crypto.Util.number import *
import rsa
p = 285960468890451637935629440372639283459
q = 304008741604601924494328155975272418463
e = 65537
n = 86934482296048119190666062003494800588905656017203025617216654058378322103517
phi = (p-1)*(q-1)
d = gmpy2.invert(e,phi) #或者用Crypto库也可以
# d = inverse(e,phi)
key = rsa.Private(n,e,int(d),p,q)
f = open("flag.enc", "rb+")
fr = f.read()
print(rsa.decrypt(fr,key))
[FlareOn4]login
这题下载附件以后,发现是个网页,里面有个js来判断你输入的是否是正确的flag
没想到里面出现了web…
我们看看js代码
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 ("PyvragFvqrYbtvafNerRnfl@syner-ba.pbz" == rotFlag) {
alert("Correct flag!");
} else {
alert("Incorrect flag, rot again");
}
}
(c <= “Z” ? 90 : 122) >= (c = c.charCodeAt(0) + 13) ? c : c - 26 ;
String.fromCharCode(c / c-26)
表达式1:如果c<=Z,即是大写字母A-Z(65-90) 那么该表达式值为90,如果c>z,也即c此时为小写字母a-z(97-122),此时表达式值为122
表达式2: c的ascii值加上13的值
如果 表达式1 >= 表达式2 那么返回 c+13,否则返回c-26
例如此时c = y
,其ascii值为121,首先它大于Z的ascii值,则前一个表达式的值为122,后面表达式的值为121+13=134,又因为此时表达式1 < 表达式2,此时整体返回的是c的ascii值-26也即 134-26 = 108 也即字符l
,这样做的目的相当于让其一直在字母表的顺序循环。
所以写一个脚本穷举一下即可
rotflag = "PyvragFvqrYbtvafNerRnfl@syner-ba.pbz"
ascii_value = []
for i in range(len(rotflag)):
ascii_value.append(ord(rotflag[i])) #将其转换为对应的ascii值
flag = ''
for val in ascii_value:
if 65 <= val <= 90:
val = val - 13
if val < 65:
val = val + 26
flag += chr(val)
elif 97 <= val <= 122:
val = val - 13
if val < 97 :
val = val + 26
flag += chr(val)
else:
flag += chr(val)
print("flag{"+flag+"}")
或者直接在线网站
http://www.hiencode.com/rot13.html
[WUSTCTF2020]level1
丢进ida64去
发现是打开一个叫flag的文件然后一行一行进行运算,最后的结果在output.txt文件
直接用脚本跑一下即可 (因为异或对于计算机来说要比除法好算的多吗,也可以那除法作为res的赋值)
numbers = [
198, 232, 816, 200, 1536, 300, 6144, 984, 51200, 570,
92160, 1200, 565248, 756, 1474560, 800, 6291456, 1782,
65536000 ]
flag = ''
for i in range(len(numbers)):
res = numbers[i] >> (i+1)
if (i+1)&1 == 1:
flag +=chr(numbers[i]>>(i+1))
else:
flag +=chr(int(numbers[i]/(i+1)))
print(flag)
附一张先用除法的
[GUET-CTF2019]re[z3约束器]
64bit elf文件还有壳
拖一下壳
拖入ida64,通过查找字符串定位关键函数
再定位函数sub_4009AE,发现是一堆运算,让其对应相等即可得出flag
脚本跑一下,但是发现好麻烦啊,复制都懒得复制,于是直接从网上找了个用Z3约束器的方法
注:
有几个坑
1.没有a1[6],需要自己爆破,当时在页面提交用burp爆破即可
2.a1[16]与a1[17]的顺序相反
用之前先下载两个库
pip install z3
pip install z3-solver
from z3 import *
z = Solver()
a1 = [0]*32
for i in range(32):
a1[i] = Int('a1['+str(i)+']')
z.add( 1629056 * a1[0] == 166163712 )
z.add( 6771600 * a1[1] == 731332800 )
z.add( 3682944 * a1[2] == 357245568 )
z.add( 10431000 * a1[3] == 1074393000 )
z.add( 3977328 * a1[4] == 489211344 )
z.add( 5138336 * a1[5] == 518971936 )
z.add( 7532250 * a1[7] == 406741500 )
z.add( 5551632 * a1[8] == 294236496 )
z.add( 3409728 * a1[9] == 177305856 )
z.add( 13013670 * a1[10] == 650683500 )
z.add( 6088797 * a1[11] == 298351053 )
z.add( 7884663 * a1[12] == 386348487 )
z.add( 8944053 * a1[13] == 438258597 )
z.add( 5198490 * a1[14] == 249527520 )
z.add( 4544518 * a1[15] == 445362764 )
z.add( 10115280 * a1[16] == 981182160 ) #我这里手动调换位置了
z.add( 3645600 * a1[17] == 174988800 )
z.add( 9667504 * a1[18] == 493042704 )
z.add( 5364450 * a1[19] == 257493600 )
z.add( 13464540 * a1[20] == 767478780 )
z.add( 5488432 * a1[21] == 312840624 )
z.add( 14479500 * a1[22] == 1404511500 )
z.add( 6451830 * a1[23] == 316139670 )
z.add( 6252576 * a1[24] == 619005024 )
z.add( 7763364 * a1[25] == 372641472 )
z.add( 7327320 * a1[26] == 373693320 )
z.add( 8741520 * a1[27] == 498266640 )
z.add( 8871876 * a1[28] == 452465676 )
z.add( 4086720 * a1[29] == 208422720 )
z.add( 9374400 * a1[30] == 515592000 )
z.add(5759124 * a1[31] == 719890500)
z.check()
print(z.model())
a1 = [0] * 32
a1[31] = 125
a1[30] = 55
a1[29] = 51
a1[28] = 51
a1[27] = 57
a1[26] = 51
a1[25] = 48
a1[24] = 99
a1[23] = 49
a1[22] = 97
a1[21] = 57
a1[20] = 57
a1[19] = 48
a1[18] = 51
a1[16] = 97
a1[17] = 48
a1[15] = 98
a1[14] = 48
a1[13] = 49
a1[12] = 49
a1[11] = 49
a1[10] = 50
a1[9] = 52
a1[8] = 53
a1[7] = 54
a1[5] = 101
a1[4] = 123
a1[3] = 103
a1[2] = 97
a1[1] = 108
a1[0] = 102
flag = ''
for i in range(len(a1)):
flag += chr(a1[i])
print(flag)
#flag{e165421110ba03099a1c039337} 注意第6位后需要爆破位1(e后面)
CrackRTF[Resourcehacker]
查壳32位exe,定位到关键函数
需要注意两个函数
sub_40100A、sub_401019这两个分别对输入的密码进行了运算,至于是什么算法需要看函数
CryptCreateHash中的第二个参数其中0x8003u表示MD5、0x8004u表示SHA-1
sub_40100A -> SHA-1 sub_401019 -> MD5
知道这两个以后,可以用脚本跑一下密码因为第一个密码知道是6位
import hashlib
passwd1=''
Destination = "@DBApp"
sha1_hash = "6E32D0943418C2C33385BC35A1470250DD8923A9".lower()
md5_hash = "27019e688a4e62a649fd99cadaafdb4e"
for i in range(100001,1000000):
passwd1 = str(i) + Destination
h1 = hashlib.sha1(passwd1.encode("utf-8")).hexdigest()
if h1 == sha1_hash:
print(i)
break
跑出密码是123321
第二个密码因为没有位数限制,所以就无法进行爆破了
法1:
但是找到了一个顶级网站里面可以爆破
https://www.somd5.com/
这样两个密码都知道了,打开程序输这两个密码就会得到一个名叫dpapp.rtf的文件
里面就有flag
法2:
只能转眼看最后一个匹配的函数了
这个函数的大体意思就是获取AAA的资源,最后生成一个rtf文件,我们首先要用Resource Hacker
获取一下
然后再定位一下sub_401005函数
这里涉及到了异或,且最后的内容返回到了a2也就是lpBuffer,再注意最后写到rtf文件中的指针也是lpBuffer,因为写入文件的指针就是lpBuffer,又因为写的文件是rtf文件,lpBuffer的最开始必然是RTF文件的文件头
RTF文件头 {\rtf1
正常逻辑是通过输入的密码与AAA的资源相异或,然后得到lpBuffer即新RTF文件,又因为异或的逆运算还是异或,所以用RTF文件与AAA资源相异或,同样能得到密码。即RTF头与AAA资源的前6位相异或
写一下脚本看看
AAA = [0x05, 0x7D, 0x41 ,0x15 ,0x26 ,0x01]
ch = ''
passwd2=''
for i in AAA:
ch += chr(int(i))
rtf = "{\\rtf1" #转义反斜杠
for j in range(6):
passwd2 += (chr(ord(ch[j])^ord(rtf[j])))
print(passwd2)
[2019红帽杯]easyRE[fini段]
64位elf文件,拖进去看看,看到一堆函数,直接用Shift+F12来查找一下关键的字符串,定位you found me,应该是代表找到了flag
于是定位到函数sub_4009C6看一下逻辑,先看前一段
首先是一些赋值操作
v12 = Iodl>Qnb(ocy
v13 = y.i
v14 = d`3w}wek9{iy=~yL@EC
再加上v12、v13、v14的内存是连续的,所以可以将其看成一个数组
所以针对中间的异或可以写一个脚本得出v15的值,因为异或的逆运算仍然是异或
v15=''
v14 = "Iodl>Qnb(ocy\x7Fy.i\x7Fd`3w}wek9{iy=~yL@EC"
for i in range(len(v14)):
v15 += chr( ord(v14[i]) ^ i )
print(v15)
得出一个感觉有用又感觉没用的东西,应该是一个提示,告诉我们flag的前四个字符是flag(感觉和没说一样呢0.0)
继续分析代码,通过findcrypt插件可以看出函数sub_400E44是一个base64加密,或者看到这一串字符串也同样能分辨出来
再接着往下看,看到这么一串长的base64加密
它经过了十次base64加密,最后一直解密的结果是
https://bbs.pediy.com/thread-254172.htm
额到这里其实我已经蒙了,因为得出来的网站打开也没有什么可以有用的信息,于是看了一下网上的wp发现还有一段关键代码,就在那一长串10次base64加密的后面
我们双击查看一下引用跳转到了一个sub_400D35的函数,定位关键函数
通过这个if语句的条件和上面的提示,可以得出v4与a5V变量依次异或的结果前四个字节是flag
所以可以先通过脚本来逆求出来v4,再通过v4依次与a5V循环异或得到flag
str1="flag"
v4=''
str2 = [ 0x40, 0x35, 0x20, 0x56, 0x5D, 0x18, 0x22, 0x45, 0x17, 0x2F,
0x24, 0x6E, 0x62, 0x3C, 0x27, 0x54, 0x48, 0x6C, 0x24, 0x6E,
0x72, 0x3C, 0x32, 0x45, 0x5B]
for i in range(4):
v4 += chr (ord(str1[i])^str2[i])
print(v4)
然后跑flag
v4='&YA1'
str2 = [ 0x40, 0x35, 0x20, 0x56, 0x5D, 0x18, 0x22, 0x45, 0x17, 0x2F,
0x24, 0x6E, 0x62, 0x3C, 0x27, 0x54, 0x48, 0x6C, 0x24, 0x6E,
0x72, 0x3C, 0x32, 0x45, 0x5B]
flag = ''
for i in range(len(str2)):
flag += chr(ord(v4[i%4]) ^ str2[i])
print(flag)
[MRCTF2020]Transform[算法异或]
64位无壳,定位关键代码
str的长度是32位
经过一系列运算
先看下dword_40F040
最终异或的结果
所以拿最终异或的结果与dword_40F040的低字节其实也就是本身(因为dword的大小就是32位,正好取低字节其实就是后16个字节,而题目所给的11h、16h都只占16位),相异或即可得到原来的byte_414040
此时再需要调整顺序即可得到flag
#转换为10进制
byte_414040 = ''
lowbyte = [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,8]
res = "gy{\x7Fu+<RSyW^]B{-*fB~LWyAk~e<\EobM" #最后异或的结果即byte_40F0E0
for i in range(32):
byte_414040 += chr ( ord(res[i]) ^ lowbyte[i] )
print(byte_414040)
#然后调整顺序即可
flag = ''
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,8]
for j in range(1,33):
k = 0
for inde in index:
k+=1
if inde == j:
if k > 32: #如果索引越界,则直接跳出循环
break
flag += byte_414040[k-1]
print(flag)
[WUSTCTF2020]level2
32位 elf文件 有upx壳
脱一下壳
然后拖入ida32 这题就结束了
从伪代码模式按 TAB 切换到汇编代码模式,就直接看到了flag
或者在伪代码模式中,shift+ F12 直接查找字符串
或者在查找字符串的选项下,按一下Address让他排序,第一个就是flag
[SUCTF2019]SignIn[RSA]
64 elf文件,定位到关键函数
分析一下逻辑
v8是输入的flag v7是最后要等于的字符串
然后v8会经过sub_96A函数来转换为v9
最后会经过一个类似加密的函数 __gmpz_powm,所以查一下这个函数
void __gmpz_init_set_str(mpz_t rop, const char *str, int base)
rop
:一个指向mpz_t类型的指针,表示要初始化和设置的目标整数。str
:一个表示要设置的整数值的字符串。base
:一个整数,指定字符串中数字的基数(例如,10表示十进制,16表示十六进制)。
void __gmpz_powm(mpz_t rop, const mpz_t base, const mpz_t exp, const mpz_t mod)
该函数计算 (base^exp) mod mod
rop
:一个指向 mpz_t 类型的指针,表示计算结果的目标整数。base
:一个表示底数的 GMP 整数。exp
: 一个表示指数的 GMP 整数。mod
:一个表示模数的 GMP 整数。
所以会首先给v7赋值,然后在将v9以16进制形式赋值给v6,紧接着给v4、v5以十进制形式赋值
最后计算 v6 ^ v5 mod v4,最后将结果更新到v6中,其实就是RSA算法
最后比较是不是与v7一致,如果一致代表正确
对于RSA算法中我们现在已知C、e以及n
首先求一下p、q用在线网站分解一下
p = 282164587459512124844245113950593348271
q = 366669102002966856876605669837014229419
e = 65537
用脚本写一下
from Crypto.Util.number import *
c = "ad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35"
ciphertext = int(c, 16) # 将十六进制转换为10进制
#print(ciphertext)
#78510953323073667749065685964447569045476327122134491251061064910992472210485
p = 282164587459512124844245113950593348271
q = 366669102002966856876605669837014229419
e = 65537
n = p*q
phi = (p-1)*(q-1)
d = inverse(e,phi) #求出私钥d
#91646299298871237857836940212608056141193465208586711901499120163393577626813
#print(d)
m = pow(ciphertext,d,n)
print(m)
#185534734614696481020381637136165435809958101675798337848243069
现在反求出来v6其实也就是v9,这样就能通过sub_96A函数推出flag了
但是仔细一看这个sub_96A函数传入的两个参数,其实最后运算下来是一样的,只是用了十六进制表达
例如:a2=“73…” 此时a2[0] = ‘7’ a2[1] = ‘3’
此时 a0123456789abcd[ flag[0] >> 4 ] = 7
也即flag[0] >> 4 = 7 = 0000 0111 那么原本的flag[0]只需左移四位也即 0111 0000 = “7”(hex)
同理对于a2[1]
a0123456789abcd[ flag[1] & 0xF ] = 3 = 0000 0011 那么原本的flag[1] 也就是后四位(因为&0xF,表示取这个二进制数的后四位) 所以 flag[1] = 0000 0011 = “3” (hex)
所以以此类推,其实flag就等于我们所求的明文。
再写一个脚本跑一下
m = 185534734614696481020381637136165435809958101675798337848243069
#m_str = hex(m)[2:] #去掉0x
#print(m_str)
m_str = "73756374667b50776e5f405f68756e647265645f79656172737d"
byte_str = bytes.fromhex(m_str)
flag = byte_str.decode("utf-8")
print(flag)
[ACTF新生赛2020]usualCrypt[base64变表]
32位无壳,定位关键函数
首先定位到sub_401080函数
用findcrypt插件发现是一个base64加密,所以v5就是输入的v8经过base64加密得到的
再往下看,经过一个while循环可以判断出v5的具体内容
但是感觉上面那个变量有点浪费,而且这些代码也没有给出实质性的东西
索性就Shift+F12来查看字符串了
找到双击,但是并没有找到引用
这下就难住了,然后仔细一看base64那个加密函数不止单纯的一个base64加密,其中还有两个函数
先看这个函数就是判断让其大小写互换
然后就是最开始的函数sub_401000(),这里面对于base64加密的密钥互换了一下
所以写一个脚本跑一下,先大小写互换,然后用base64变表解密即可,所谓变表就是原本对于Base 64来说A就是对应的A,b就是对应的b,但通过变表以后,有一部分对应的内容不同了
#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
char aKlmnopqrstuvwx[60] = "KLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
char BASE64_table_40E0A0[12] = "ABCDEFGHIJ";
char tmp;
for (int i = 6 ; i < 15; i++)
{
tmp = aKlmnopqrstuvwx[i];
aKlmnopqrstuvwx[i] = BASE64_table_40E0A0[i];
BASE64_table_40E0A0[i] = tmp;
}
printf("%s\n", BASE64_table_40E0A0);
return 0;
}
//ABCDEFQRSTUVWXY其中从Q开始是交换后的
然后又因为40E0AA数组与40E0A0实质上地址是连续的是一个数组,所以最后将整个数组交换过后得到的新base64索引表为 ABCDEFQRSTUVWXYPGHIJKLMNOZabcdefghijklmnopqrstuvwxyz0123456789+/
然后再将所给密文转换完大小写即可直接用在线自定义编码的解密
http://web.chacuo.net/netbasex
或者直接用脚本将密文中的对应字符与新的索引表交换即可,例如对于密文中的GNX在索引表中分别对应QXN以此类推即可
import base64
base64_table = "ABCDEFQRSTUVWXYPGHIJKLMNOZabcdefghijklmnopqrstuvwxyz0123456789+/"
original_base64table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
original_dec = "ZmxhZ3tiGNXlXjHfaDTzN2FfK3LycRTpc2L9"
dec = ""
for i in range(len(original_dec)):
dec += original_base64table[base64_table.find(original_dec[i])]
print(dec) # 此时得到的是标准base64索引表下的密文,直接解密即可
print(base64.b64decode(dec))
[HDCTF2019]Maze[花指令]
32位upx壳,脱一下壳
找到main函数但是不知道为什么无法F5进入,查了一下wp才发现,它代码call了一个不存在的地址
也就是俗称的花指令(junk code)
花指令(junk code)是一种专门用来迷惑反编译器的指令片段,这些指令片段不会影响程序的原有功能,但会使得反汇编器的结果出现偏差,从而使破解者分析失败。比较经典的花指令技巧有利用 jmp
、call
、ret
指令改变执行流,从而使得反汇编器解析出与运行时不相符的错误代码。
https://ctf-wiki.org/reverse/obfuscate/junk-code/
这也理解了为什么题目叫做 maze_behind_junk,所以就是要绕过花指令
我们直接用IDA打开,找到错误的花指令直接NOP掉,然后选中main函数按P键更新一下,就可以识别到main函数了
可以看到这个循环里面有wasd这也就提示我们是要上下左右走
结合一下迷宫总共有70个字符,所以就可以猜测是7 * 10 或者是 10 * 7
再结合到终点的信息此时坐标是(5,-4) 起点坐标是(7,0),变量asc_408078的初始值是7
所以最终猜测就是7行10列的一个迷宫,因为结合迷宫字符串中有+和F肯定一个代表起点一个代表终点,所以结合这些信息只能是如下
*******+**
******* **
**** **
** *****
** **F****
** ****
**********
//所以迷宫答案一目了然 ssaaasaassdddw 所以flag也出来了
[MRCTF2020]Xor[手撕main汇编]
32位有壳,脱壳以后定位main函数(但是其中没有直接F5出来,报错:401095: call analysis failed ),然后我直接定位到401095处的汇编代码F5进去伪代码,然后退出去再定位到main函数再按F5就进去了,也不知道是怎么回事
定位到关键函数
让输入的flag等于下列字符串
所以我们根据异或运算的逆运算仍然是异或就可以得到原本输入的字符串
写个脚本跑一下
str1 = 'MSAWB~FXZ:J:`tQJ"N@ bpdd}8g'
flag =''
for i in range(27):
flag += chr( ord(str1[i]) ^ i)
print(flag)
也不知道为什么这么简单,有可能是刚开始进入main函数的时候会挖坑,但是我也不知道具体怎么弄的就进去了
网上看了wp以后,有人的方法和我一致,有的人就是直接看汇编了,不过逻辑比较简单
loc_4010B6:
让edx寄存器,即存着数组的首地址,依次指向下一个字符在这途中al一直不为0,也即ZF标志位一直为0,则jnz指令会一直跳转到自己这里从而形成了循环,直到遇到0字节(即数组的最后一个截止符),因为此时al为0,ZF=1,此时jnz指令不会跳转则执行下一段汇编代码,结束循环以后,edx值为7(因为多了一个字符串结束符’\0’)
这时edx减去ecx,因为前面有 lea ecx, [eax+1],相当于ecx此时是1,则sub edx, ecx则表示这个字符串的长度,最后比较是不是等于27,如果不等于则直接输出错误信息,如果等于则到了下面的关键代码
loc_4010D0
关键代码就是这个拿自己输入的flag byte_4212C0与byte_41EA08进行异或如果相等则跳转loc_4010FF
loc_4010FF
eax增加一直直到27,因为edx在上面被设置为27,如果eax到了27表明所有字符都进行了异或操作,也即表明结果一致,最后跳转到loc_4010D0,即输出Right!
[MRCTF2020]hello_world_go
是一个用go语言编写的64位elf文件
定位到main函数发现代码有点长
// main.main
void __cdecl main_main()
{
__int64 v0; // rcx
__int64 v1; // rax
__int64 v2; // rax
__int64 v3; // [rsp+20h] [rbp-90h]
__int64 v4; // [rsp+58h] [rbp-58h]
__int64 *v5; // [rsp+60h] [rbp-50h]
_QWORD v6[2]; // [rsp+68h] [rbp-48h] BYREF
__int64 v7[4]; // [rsp+78h] [rbp-38h] BYREF
_QWORD v8[2]; // [rsp+98h] [rbp-18h] BYREF
v5 = (__int64 *)runtime_newobject((__int64)&RTYPE_string);
v8[0] = &RTYPE_string;
v8[1] = &off_4EA530;
fmt_Fprint(go_itab__ptr_os_File_comma_io_Writer, os_Stdout, v8, 1LL, 1LL);
v7[2] = (__int64)&RTYPE__ptr_string;
v7[3] = (__int64)v5;
v3 = fmt_Fscanf(go_itab__ptr_os_File_comma_io_Reader, os_Stdin, &unk_4D07C9, 2LL);
v0 = v5[1];
v1 = *v5;
if ( v0 != 24 )
goto LABEL_2;
v4 = *v5;
if ( !runtime_memequal((__int64)&unk_4D3C58, v1, 24LL) )
{
v1 = v4;
v0 = 24LL;
LABEL_2:
runtime_cmpstring((__int64)&unk_4D3C58, 24LL, v1, v0, v3);
if ( v3 >= 0 )
v2 = 1LL;
else
v2 = -1LL;
goto LABEL_4;
}
v2 = 0LL;
LABEL_4:
if ( v2 )
{
v6[0] = &RTYPE_string;
v6[1] = &off_4EA550;
fmt_Fprintln(go_itab__ptr_os_File_comma_io_Writer, os_Stdout, v6, 1LL, 1LL);
}
else
{
v7[0] = (__int64)&RTYPE_string;
v7[1] = (__int64)&off_4EA540;
fmt_Fprintln(go_itab__ptr_os_File_comma_io_Writer, os_Stdout, v7, 1LL, 1LL);
}
}
抱着试一试的态度去查一下字符串flag,发现直接查到了,或者随便点点变量发现26行的变量unk_4D3C58就是flag
到这里前32道题就写完,后续再写剩下的,依旧是抱着学习的态度来写