- 基础框架
- 指令体系
- 逆向分析
- 算法识别
- 反调试
- Windows
- 自动化反调试
- Linux
- 虚拟机保护
- 脚本语言逆向
- RxEncode
- 分析
- Real_easy_python
- 分析
- EXP
- Protection
- 分析
- EXP
- SimpleRe
- 分析
- Thank you Javascript
- 分析
- EasyCPP
- 分析
- EXP
- AlgorithmTask-HardTask
- 分析
- EasyShell
- 分析
- [MRCTF2020]Xor
- 分析
- EXP
- [GWCTF 2019]xxor
- 分析
- EXP
- [ACTF新生赛2020]usualCrypt
- 分析
- EXP
通常CTF中会出现base64、TEA、AES、RC4、MD5等算法
特征:
| 算法 | ||||||||
|---|---|---|---|---|---|---|---|---|
| base64 | 64+1位表、右移左移取二进制操作 | |||||||
| TEA | 每轮加密涉及移位 <<4 >>5 与 delta常量(0x9e3779b9) | |||||||
| AES | 主要特征为sbox(0x63)和逆sbox(0x52) | |||||||
| RC4 | 涉及初始化函数和加密函数(交换s[i]与s[j])、 %256 | |||||||
| MD5 | 散列常量:0x67452301、0xefcdab89、0x98badcfe、0x10325476 |
当遇到花指令或其他需要Patch汇编时
- 使用IDAPatch
- 使用IDCPython自动化批量Patch
脚本语言逆向通常,VMProtect等商用软件采用了虚拟机保护技术,它们核心都会有一个vm_init阶段完成初始化自己的一套(ISA)指令集架构
.NET/Python/Java
RxEncode 分析静态分析 很像是换表的base64
关键算法:
C 库函数 **char strrchr(const char str, int c) 在参数 str 所指向的字符串中搜索最后一次出现字符 c(一个无符号字符)的位置。
已经确定是换表的basee64解码,等待进一步完善RXEncode.py的EXP
Real_easy_python 分析查壳是 python3.7的pyc文件
使用uncompyle6工具解码转换py:
root@ubuntu:~# uncompyle6 -o puzzle.py puzzle.pyc
解码代码:
# uncompyle6 version 3.7.4
# Python bytecode 3.7 (3394)
# Decompiled from: Python 3.5.2 (default, Jan 26 2021, 13:30:48)
# [GCC 5.4.0 20160609]
# Embedded file name: ./source.py
# Compiled at: 2020-08-03 05:55:47
# Size of source mod 2**32: 515 bytes
key = [
115, 76, 50, 116, 90, 50, 116, 90, 115, 110, 48, 47, 87, 48, 103, 50, 106, 126, 90, 48, 103, 116, 126, 90, 85, 126, 115, 110, 105, 104, 35]
print('Input your flag: ', end='')
flag = input()
out = []
for i in flag:
out.append(ord(i) >> 4 ^ ord(i))
if len(out) != len(key):
print('TRY AGAIN!')
exit()
for i in range(len(out)):
if out[i] != key[i]:
print('TRY AGAIN!')
exit()
print('you are right! the flag is : moectf{%s}' % flag)
EXP
顺加倒解
key = [
115, 76, 50, 116, 90, 50, 116, 90, 115, 110, 48, 47, 87, 48, 103, 50, 106, 126, 90, 48, 103, 116, 126, 90, 85, 126, 115, 110, 105, 104, 35]
for i in range(len(key)):
out.append(chr(key[i] >>4 ^ key[i]))
print(''.join(out))#moectf{tH1s_1s_th3-R3a1ly_3asy_Python!}
Protection
分析
查壳UPX3.96
脱壳后,发现是xor,照葫芦还原x^y=flag
快速转换数组(convert)
丝滑~moectf{upx_1S_simp1e-t0_u3e}
EXPx=[0x61, 0x6F, 0x75, 0x76, 0x23, 0x40, 0x21, 0x56, 0x30, 0x38, 0x61, 0x73, 0x64, 0x6F, 0x7A, 0x70, 0x6E, 0x6D, 0x61, 0x26, 0x2A, 0x23, 0x25, 0x21, 0x24, 0x5E, 0x26, 0x2A, 0x00]
y=[0x0C, 0x00, 0x10, 0x15, 0x57, 0x26, 0x5A, 0x23, 0x40, 0x40, 0x3E, 0x42, 0x37, 0x30, 0x09, 0x19, 0x03, 0x1D, 0x50, 0x43, 0x07, 0x57, 0x15, 0x7E, 0x51, 0x6D, 0x43, 0x57, 0x00, 0x00, 0x00, 0x00]
ans=''
for i in range(len(x)):
ans+=chr(x[i]^y[i])
print(ans)
SimpleRe
分析
MOE2020
简单的XOR,在动态中解密
顺加逆解,xor一遍是加密,xor第二遍就是解密
满足公式:
a^b=c c^a=b a^b^c=0Thank you Javascript 分析
去在线解混淆JS代码
http://edit.89tool.com/
const io = require('console-read-write');
async
function main() {
io.write('MoeCTF 2020 ThankYouJavascript --written by Reverier');
io.write(await io.read());
io.write(`Hello $ {
await io.ask('Who are you?')
} ! `);
let saidHi = false;
while (!saidHi) {
io.write('Please input the true flag:');
saidHi = await io.read() === 'moectf{Fx' + 'c' + 'k_' + 'Y' + '0' + 'u-' + 'Jav' + 'aS' + 'cr' + 'ipt' + '!}'
}
io.write('Congratulations! You find the flag!')
}
main();
得到flag:moectf{Fxck_Y0u-Javascript!}
EasyCPP 分析nop
继续分析发现是一个简单的变换算法,其余的都是混淆:
笔记
- unknown lib name
- 对于调试保护可以用Patch修改
import binascii
def encode(old):
#old="YWFhYQ=="
ans=""
for i in range(len(old)):
i=old[i]
num=ord(i)
if(i.islower()):
ans+=chr((num+3-97)%26+97)
elif i.isupper():
ans+=chr((num+3-65)%26+65)
else:ans+=chr(num)
def decode(old):
#old="BZIkBT=="#YWFhYQ=="
ans=""
for i in range(len(old)):
i=old[i]
num=ord(i)
if(i.islower()):
anx=(num-97)%26 +97-3
if(anx<97):
anx=97+26-(97-anx)
ans+=chr(anx)
print((num-97)%26 +97-3)
elif i.isupper():
anx=(num-65)%26 +65-3
if(anx<65):
anx=65+26-(65-anx)
ans+=chr(anx)
print((num-65)%26 +65-3)
else:ans+=chr(num)
return ans
print(decode("eZ9oB3Uph0QTXI9FBYQIQmUiT2IoX0EbAcIWA3PzA2YkBZIkf3o9"))
print(chr((89+3-65)%26+65))
AlgorithmTask-HardTask
分析
MOE2019
strncmp
若str1与str2的前n个字符相同,则返回0;
程序流程被混淆,稍微分析了下、将字符串用a转换了一下,重新反编译恢复字符串:
case 1750495256: // 第一段flag关键算法
一共分2段flag
- dcba3261b6ef0d77(16位)
- X1I0X0YxYWdfWTB1XzRyZV9TdHIwbmd9
第二段base64可以解出来:_R4_F1ag_Y0u_4re_Str0ng}
看了WP,盲猜是MD5算法,在cmd5查询是enj0y
要大胆猜RE,就那几种算法,不用想太复杂~
flag:moectf{enj0y_R4_F1ag_Y0u_4re_Str0ng}
EasyShell 分析MOE2019 RE
DIE查询是UPX3.94
使用UPX Shell脱壳
这个似乎会阻止伪代码转换
很明显,在动态调试算法在这:
在动态调试中,发现算法是还原xor真实密码,并且逐个比较
还原公式=(变形flag ^ 字符索引)
笔记:
- 未转换的变量为字符串,再按A重新转换(第一个字符)
oldstr=[0x4D, 0x53, 0x41, 0x57, 0x42, 0x7E, 0x46, 0x58, 0x5A, 0x3A, 0x4A, 0x3A, 0x60, 0x74, 0x51, 0x4A, 0x22, 0x4E, 0x40, 0x20, 0x62, 0x70, 0x64, 0x64, 0x7D, 0x38,0x67]
ans=''
for i in range(len(oldstr)):
ans+=chr(oldstr[i]^i)
print(ans)
[GWCTF 2019]xxor
分析
是一道算法题 (类似于TEA)
先解方程,通过z3求解:
主要验证代码:
主要算法代码:(类似TEA轮加密)
在数组中,伪代码
*input = input[0]
看了WP提示
a2[1] a2[2]取值,要看类型 _DWORD 占4位 *a2=a2+0*4=a2 a2[1]= a2 +1*4
在动态调试中观察,可以很明显发现
其实是取
byte_601078 = v6[j];
unk_60107C = v6[j+1];
注意的点:
- unsigned 符号类型 变量类型很重要
- 加密顺,解密倒着完事
void __fastcall decalc()
{
__int64 xorm[6];
xorm[0] = 3746099070;
xorm[1] = 550153460;
xorm[2] = 3774025685;
xorm[3] = 1548802262;
xorm[4] = 2652626477;
xorm[5] = 2230518816;
unsigned int i = 0, j = 0, sum;
unsigned int temp[2] = { 0 };
unsigned int data[4] = { 2,2,3,4 };//unk哪个数字
for (i = 0; i < 5; i += 2)
{
temp[0] = xorm[i];
temp[1] = xorm[i + 1];
sum = 0x458BCD42 * 64;//类似于tea 逆向
for (j = 0; j < 64; j++)
{
temp[1] -= (temp[0] + sum + 20) ^ ((temp[0] << 6) + 3) ^ ((temp[0] >> 9) + 4) ^ 0x10;
temp[0] -= (temp[1] + sum + 11) ^ ((temp[1] << 6) + 2) ^ ((temp[1] >> 9) + 2) ^ 0x20;
sum -= 0x458BCD42;
}
xorm[i] = temp[0];
xorm[i + 1] = temp[1];
}
for (i = 0; i < 6; i++)
printf("%c%c%c", *((char*)&xorm[i] + 2), *((char*)&xorm[i] + 1), *(char*)&xorm[i]);
}
int main()
{
decalc();
}
[ACTF新生赛2020]usualCrypt
分析
经典的base64
动态调试后换表:
ABCDEFQRSTUVWXYPGHIJKLMNOZabcdefghijklmnopqrstuvwxyz0123456789+/
继续分析,发现base64后还有一个大小写颠倒的算法函数:
import string
from pwn import *
oldtable="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
newtable="ABCDEFQRSTUVWXYPGHIJKLMNOZabcdefghijklmnopqrstuvwxyz0123456789+/"
dictA={}
for i in range(len(oldtable)):
dictA[newtable[i]]=oldtable[i]
dictA["="]="="
convert_data="zMXHz3TIgnxLxJhFAdtZn2fFk3lYCrtPC2l9"
ans=""
for i in range(len(convert_data)):
now=ord(convert_data[i])
if(now<97 or now>122):# no || is or
if((now<65 or now>90)==False):
now=now+32
else:
now=now-32
ans=ans+dictA[chr(now)]
print(b64d(ans))



