2021津门杯逆向
一:easyRe
调用lua的程序逆向
找到main函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+4h] [rbp-1Ch]
char *input; // [rsp+10h] [rbp-10h]
_DWORD *v6; // [rsp+18h] [rbp-8h]
input = (char *)malloc(0x21uLL);
printf("input the a str:");
__isoc99_scanf("%s", input);
if ( (unsigned int)strlen(input) != 32 )
{
printf("oh,no!");
exit(0);
}
v6 = encrypt(~(53 * (2 * input[6] + input[15] + 3 * input[29])) & 0xFFF, (__int64)input);
for ( i = 0; i <= 31; ++i )
{
if ( dword_63A380[i] != v6[i] )
{
printf("oh,no!");
exit(0);
}
}
puts("Success!");
return 0;
}
输入长度为32的字符串,然后从中截取三个字符来计算得到encrypt函数的参数1,并将字符串作为参数2传入函数
因为是会调用lua,所以我们先在linux平台下编译好一份lua
curl -R -O http://www.lua.org/ftp/lua-5.3.0.tar.gz
tar zxf lua-5.3.0.tar.gz
cd lua-5.3.0
make linux test
make install
然后cp /usr/local/bin/lua /home/ub20/Desktop(将/usr/local/bin/lua这个文件复制到桌面)
然后将其从虚拟机拖到windows本机即可
使用ida先载入lua,退出后生成lua.i64
重新打开ida,载入re1这个elf文件,ctrl+6
Diff Database载入我们之前生成的lua.i64(记住路径千万不要有中文)
然后再次ctrl+6,点击Import Symbols and Comments…
并设置confidence和similarity为0.5
效果如下:
可以很准确的识别出lua的函数
lua文档链接:http://www.lua.org/manual/5.4/
下面我们分析encrypt函数:(请查看注释)
void *__fastcall encrypt(unsigned int a1, __int64 a2)
{
_BYTE *functionname; // rax
double lua_func_parameter; // xmm0_8
__int64 v4; // rcx
__int64 v5; // r8
__int64 v6; // r9
int i; // [rsp+20h] [rbp-70h]
int j; // [rsp+24h] [rbp-6Ch]
int k; // [rsp+28h] [rbp-68h]
FILE *fp; // [rsp+38h] [rbp-58h]
__int64 size; // [rsp+40h] [rbp-50h]
void *ptr; // [rsp+48h] [rbp-48h]
const char *decode_file; // [rsp+50h] [rbp-40h]
__int64 L; // [rsp+58h] [rbp-38h]
void *s; // [rsp+70h] [rbp-20h]
void *final; // [rsp+78h] [rbp-18h]
setvbuf(stdout, 0LL, 2, 0LL);
fp = fopen("my.lua", "rb");
if ( !fp )
exit(0);
fseek(fp, 0LL, 2);
size = ftell(fp);
ptr = malloc(size);
rewind(fp);
fread(ptr, 1uLL, size, fp); // 读取文件
decode_file = xorfunc((__int64)ptr, size); // 对文件进行异或解密
L = luaL_newstate(); // luaL_newstate创建lua状态机
luaopen_base(L); // luaopen_base调用基本库
luaL_openlibs(L); // /* 打开之前luaopen_base调用的lua状态机中所有Lua标准库 */
if ( !L )
exit(0);
if ( (unsigned int)luaL_loadstring(L, decode_file) )// Loads a string as a Lua chunk.
exit(0);
s = malloc(132uLL);
memset(s, 0, 132uLL);
for ( i = 0; i <= 32; ++i ) // 根据传入的a1值,循环33次生成一个序列
{
*((_DWORD *)s + i) = (0x1ED0675 * (unsigned __int64)a1 + 0x6C1) % 254;
a1 = *((_DWORD *)s + i);
}
final = malloc(0x100uLL);
memset(final, 0, 0x100uLL);
for ( j = 0; j <= 31; ++j )
{
for ( k = 0; k <= 32; ++k )
{
*((_DWORD *)final + j + k) += *(char *)(j + a2) ^ *((_DWORD *)s + k);
lua_pcallk(L, 0, 0, 0, 0LL, 0LL); // Calls a function (or a callable object) in protected mode
functionname = xorfunc((__int64)off_63A360, 7);// 对这个字符串进行异或解密
lua_getglobal(L, (__int64)functionname); // lua_getglobal 要调用的lua的函数名
lua_func_parameter = (double)*((int *)final + j + k);
lua_pushnumber(L, lua_func_parameter); // 参数入栈
lua_pcallk(L, 1, 1, 0, 0LL, 0LL);
lua_tonumberx(L, 0xFFFFFFFFLL, 0LL, v4, v5, v6);// lua_tonumberx 取出返回值
*((_DWORD *)final + j + k) = (int)lua_func_parameter;
}
}
return final;
}
本来想动态调试,让程序自己载入my.lua进行解密,但是拖入winhex查看,发现OEP(e_entry)被修改过,直接./re1,会提示非法指令
只能静态实现它对文件的异或解密了
# _*_ coding: utf-8 _*_
# editor: SYJ
# function: Reversed By SYJ
# describe:
fp = open("D:\\CTF\\COMPETITIONS\\2021jingmen\\REVERSE\\easyRe\\my.lua", "rb")
data = fp.read()
xor_arr = [2, 3, 5]
def xor_func(data):
for i in range(len(data)):
print(chr((data[i] ^ xor_arr[i % 3]) & 0xff), end='')
print("下面是调用xorfuncd对文件解密的结果")
xor_func(data)
print("\n下面是循环中调用xorfunc对off_63A360位置的字符串cgfffce解密的结果")
xor2 = [0x63, 0x67, 0x66, 0x66, 0x66, 0x63, 0x65]
xor_func(xor2)
运行可以得到:
下面是调用xorfuncd对文件解密的结果
function BitXOR(a,b)
local p,c=1,0
while a>0 and b>0 do
local ra,rb=a%2,b%2
if ra~=rb then c=c+p end
a,b,p=(a-ra)/2,(b-rb)/2,p*2
end
if a<b then a=b end
while a>0 do
local ra=a%2
if ra>0 then c=c+p end
a,p=(a-ra)/2,p*2
end
return c
end
function adcdefg(j)
return BitXOR(5977654,j)
end
下面是循环中调用xorfunc对字符串cgfffce解密的结果
adcdefg
分析这段lua:
function BitXOR(a,b)
local p,c=1,0
while a>0 and b>0 do
local ra,rb=a%2,b%2 # 取最后一bit
if ra~=rb then c=c+p end # 如果最后一bit不相等,c = c+p
a,b,p=(a-ra)/2,(b-rb)/2,p*2 # a等于除开最后一bit后还有多少个2,b等于除开最后一bit后还有多少个2,p=p*2,二进制进位
end
if a<b then a=b end # 如果上面那个循环执行完了还在未判断完,就让a里面存放a和b里面大的那个数
while a>0 do
local ra=a%2
if ra>0 then c=c+p end # a的最后一bit如果为1,就和b对应的bit(因为b小于a,这个bit必为0)异或,等于1,要二进制进位
a,p=(a-ra)/2,p*2 # a等于除开最后一bit后还有多少个2,p二进制进位
end
return c
end
function adcdefg(j)
return BitXOR(5977654,j) # 所以这个函数就是一个bit一个bit的进行判断,得到最后异或的值
end
其实就是实现的按bit异或,跟函数名BitXOR照应,所以function abcdefg(j)就是5977654 ^ j
然后返回最后的结果,与内存中的一段数据进行比较
seg023:000000000063A380 ; int dword_63A380[32]
seg023:000000000063A380 dword_63A380 dd 5B368Bh, 136h, 5B37CBh, 0E0Ch
seg023:000000000063A380 ; DATA XREF: main+D3↑r
seg023:000000000063A380 dd 5B396Ah, 39h, 5B32A5h, 0CBAh
seg023:000000000063A380 dd 5B2683h, 0CB9h, 5B3777h, 0E47h
seg023:000000000063A380 dd 5B33BDh, 1F49h, 5B34A8h, 4D1h
seg023:000000000063A380 dd 5B27E7h, 0D76h, 5B3BDFh, 2EFh
seg023:000000000063A380 dd 5B3CD3h, 1F6Eh, 5B3D90h, 3DAh
seg023:000000000063A380 dd 5B3184h, 524h, 5B330Eh, 1A10h
seg023:000000000063A380 dd 5B2E84h, 0D3h, 5B3DA1h, 2F7Bh
写出最后的解密脚本
# decrypt部分
from z3 import *
dest_enc = [0x005B368B, 0x00000136, 0x005B37CB, 0x00000E0C, 0x005B396A, 0x00000039, 0x005B32A5, 0x00000CBA, 0x005B2683, 0x00000CB9, 0x005B3777, 0x00000E47, 0x005B33BD, 0x00001F49, 0x005B34A8, 0x000004D1, 0x005B27E7, 0x00000D76, 0x005B3BDF, 0x000002EF, 0x005B3CD3, 0x00001F6E, 0x005B3D90, 0x000003DA, 0x005B3184, 0x00000524, 0x005B330E, 0x00001A10, 0x005B2E84, 0x000000D3, 0x005B3DA1, 0x00002F7B]
for seed in range(0xfff):
xor_data = []
for i in range(33): # 循环33次生成序列
r = (0x1ED0675 * seed + 0x6c1) % 0xfe
xor_data.append(r)
seed = r
s=Solver()
flag = [BitVec(('x%d' % i), 8) for i in range(32)]
xor_result = [0 for i in range(64)]
for i in range(32):
for j in range(33):
a = flag[i] ^ xor_data[j]
xor_result[i + j] += a
xor_result[i+j] = (xor_result[i+j] ^ 5977654)
for i in range(0, 32):
s.add(flag[i] <= 127)
s.add(flag[i] >= 32)
s.add(xor_result[i] == dest_enc[i])
if s.check() == sat:
model = s.model()
str = [chr(model[flag[i]].as_long().real) for i in range(32)]
print("".join(str))
exit()
# flag{cfcd208495d565ef66e7dff9f98764da}
二:GoodRE
这道题不知道为什么和强网杯的LongTimeAgo很像
逻辑分析,关键部分就是TEA,key直接动调之后看即可
delta是tea加密函数内部生成的
sub_55C46D20A408((__int64)v7, 0x830A5376);
sub_55C46D20A408((__int64)v8, 0x1D3D2ACF);
xor(delta, v8, v7);
可以得到delta就是
num1 = 0x830A5376
num2 = 0x1D3D2ACF
print(hex(num1 ^ num2)[2:]) # 9e3779b9
9e3779b9
一个标准的TEA并没有魔改过,只是不知道用了什么库,将+,<<, >>, ^等都分开成用函数实现的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zr2GjGii-1626758207675)(2021%E6%B4%A5%E9%97%A8%E6%9D%AF%E9%80%86%E5%90%91%20222b8f36769c42b0b92b1fd76d121063/Untitled%202.png)]
解密脚本:
#include <stdio.h>
#include <stdint.h>
void decrypt (uint32_t* v, uint32_t* k) {
uint32_t delta=0x9E3779B9; /* a key schedule constant */
uint32_t v0=v[0], v1=v[1], sum=(delta*32)&0xffffffff, i; /* set up */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i<32; i++) { /* basic cycle start */
v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
sum -= delta;
}
//v0 = v0 ^data1;
//v1 = v1 ^data2;
v[0]=v0; v[1]=v1; //现在v0和v1已经解密成功
}
int main()
{
uint32_t array[] = {0x79AE1A3B,0x596080D3,0x80E03E80,0x846C8D73,0x21A01CF7,0xC7CACA32,0x45F9AC14,0x0C5F5F22F};
uint32_t key[4] = {0x11,0x11,0x11,0x11};
int i = 0;
for(i=0;i<8;i+=2)
{
uint32_t temp[2]; //定义来解密
temp[0] = array[i];
temp[1] = array[i+1];
decrypt(temp, key);
printf("%X%X",temp[0],temp[1]);
}
return 0;
}
# 7DEA3F6D3B3D6C0C620864ADD2FA2AE1A61F2736F0060DA0B97E8356D017CE59
得到flag为:flag{7DEA3F6D3B3D6C0C620864ADD2FA2AE1A61F2736F0060DA0B97E8356D017CE59}