REVERSE-PRACTICE-BUUCTF-21
[SCTF2019]babyre
elf文件,无壳,用ida分析
在start函数中看到main函数的字样,但是左侧函数窗没有找到main函数
原因是main函数中加了花指令,导致ida不能正确地分析main函数
main函数中有三处类似这样的花指令,把jb,jnb,loope三条指令都nop掉,创建函数,F5反编译
main函数中要求输入三个password,三个pwd都正确才能得到flag
先看第一个password的部分,是个走迷宫的逻辑
v28~v44是迷宫的map,长度为125,起点为’s’
往下走,switch case语句是由输入pwd1的字符决定方向,w-上,s-下,d-右,a-左,x-下一块,y-上一块,由此可以知道,长度为125的map,每25分为一个块,每个块又是5x5排列,路线只能走’.’,终点为’#’,要求最短的路线
v28 = '********'; // 注意小端序
v29 = '*.******';
v30 = '.s**.***';
v31 = '****..*.';
v32 = '.****.**';
v33 = '********';
v34 = '***..***';
v35 = '*#..**..';
v36 = '*..***..';
v37 = '*****.**';
v38 = '********';
v39 = '.*******';
v40 = '****..**';
v41 = '.**..***';
v42 = '*.*..*..';
v43 = '.**.';
v44 = '*';
v10 = 0LL;
v11 = 0LL;
v12 = 0;
v7 = (char *)&v30 + 6; // 起点为's'
v8 = '019_ftcs'; // v8='sctf_9102'
v9 = '2';
puts((const char *)(unsigned int)"plz tell me the shortest password1:");
scanf("%s", &pwd1);
v6 = 1;
while ( v6 )
{
v4 = *((_BYTE *)&pwd1 + v5);
switch ( v4 )
{
case 'w':
v7 -= 5;
break;
case 's':
v7 += 5;
break;
case 'd':
++v7;
break;
case 'a':
--v7;
break;
case 'x':
v7 += 25;
break;
case 'y':
v7 -= 25;
break;
default:
v6 = 0;
break;
}
++v5;
if ( *v7 != '.' && *v7 != '#' ) // 只能走'.'
v6 = 0;
if ( *v7 == '#' ) // 最后到'#'
{
puts("good!you find the right way!\nBut there is another challenge!");
break;
}
}
将长度为125的map按照每25一块,每块5x5排列的方式,按w-上,s-下,d-右,a-左,x-下一块,y-上一块的规定,得到最短的路线为sxss,验证正确
往下走,看第二个password的部分
pwd2和v17作为参数传入loc_C22逻辑段的函数,过程中v17被赋值,函数返回后v17和v8比较,已知v8是字符串"sctf_9102"
if ( v6 )
{
puts((const char *)(unsigned int)"plz tell me the password2:");
scanf("%s", &pwd2);
((void (__fastcall *)(__int64 *, __int64 *))loc_C22)(&pwd2, &v17);
if ( (unsigned int)sub_F67((const char *)&v17, (const char *)&v8) == 1 )// v17和v8比较,v8='sctf_9102'
{
puts("Congratulation!");
loc_C22逻辑段的代码也被插入了花指令,jb和jnb直接nop掉,“in"指令的第一个字节改成0x90
下面未被识别成代码的数据按d转成一个一个字节的形式,把前两个值为0的字节改成0x90
创建函数,F5反编译
进入sub_C22函数,来到给v17赋值的部分
0x3f的二进制为0b00111111,v7又被v7<<6赋值,v9等于4时才会进入if语句给v17赋值,由6x4=8x3,猜测是base64的反编码过程,反编码的结果为v8的字符串"sctf_9102”,于是pwd2为v8的base64编码,即为"c2N0Zl85MTAy",验证正确
往下走,看第三个password的部分
pwd3进入sub_FFA函数进行验证,返回1验证成功
puts((const char *)(unsigned int)"Now,this is the last!");
puts("plz tell me the password3:");
scanf("%s", &pwd3);
if ( (unsigned int)sub_FFA(&pwd3) == 1 )
{
puts("Congratulation!Here is your flag!:");
printf("sctf{%s-%s(%s)}", &pwd1, &pwd2, &pwd3);
}
进入sub_FFA函数,将输入的pwd3按大端序规则,16个byte转成4个int,do循环体中的sub_143B进行变换和赋值,最后四次循环赋值给v44~v47,v44到v47的4个int按大端序规则,转成16个byte,与v8比较验证
signed __int64 __fastcall sub_FFA(char *pwd3)
{
int v1; // ST24_4
int v2; // ST28_4
int v3; // ST2C_4
signed int v5; // [rsp+18h] [rbp-158h]
signed int i; // [rsp+18h] [rbp-158h]
int v7; // [rsp+1Ch] [rbp-154h]
int v8; // [rsp+30h] [rbp-140h]
int v9; // [rsp+34h] [rbp-13Ch]
int v10; // [rsp+38h] [rbp-138h]
int v11; // [rsp+3Ch] [rbp-134h]
int v12; // [rsp+40h] [rbp-130h]
int v13; // [rsp+44h] [rbp-12Ch]
int v14; // [rsp+48h] [rbp-128h]
int v15; // [rsp+4Ch] [rbp-124h]
int v16; // [rsp+50h] [rbp-120h]
int v17; // [rsp+54h] [rbp-11Ch]
int v18; // [rsp+58h] [rbp-118h]
int v19; // [rsp+5Ch] [rbp-114h]
int v20; // [rsp+60h] [rbp-110h]
int v21; // [rsp+64h] [rbp-10Ch]
int v22; // [rsp+68h] [rbp-108h]
int v23; // [rsp+6Ch] [rbp-104h]
unsigned int v24; // [rsp+70h] [rbp-100h]
int v25; // [rsp+74h] [rbp-FCh]
int v26; // [rsp+78h] [rbp-F8h]
int v27; // [rsp+7Ch] [rbp-F4h]
unsigned int v28; // [rsp+80h] [rbp-F0h]
int v29; // [rsp+84h] [rbp-ECh]
int v30; // [rsp+88h] [rbp-E8h]
int v31; // [rsp+8Ch] [rbp-E4h]
unsigned int v32; // [rsp+90h] [rbp-E0h]
int v33; // [rsp+94h] [rbp-DCh]
int v34; // [rsp+98h] [rbp-D8h]
int v35; // [rsp+9Ch] [rbp-D4h]
unsigned int v36; // [rsp+A0h] [rbp-D0h]
int v37; // [rsp+A4h] [rbp-CCh]
int v38; // [rsp+A8h] [rbp-C8h]
int v39; // [rsp+ACh] [rbp-C4h]
int v40; // [rsp+B0h] [rbp-C0h]
int v41; // [rsp+B4h] [rbp-BCh]
int v42; // [rsp+B8h] [rbp-B8h]
int v43; // [rsp+BCh] [rbp-B4h]
unsigned int v44; // [rsp+118h] [rbp-58h]
unsigned int v45; // [rsp+11Ch] [rbp-54h]
unsigned int v46; // [rsp+120h] [rbp-50h]
unsigned int v47; // [rsp+124h] [rbp-4Ch]
unsigned __int64 v48; // [rsp+168h] [rbp-8h]
v48 = __readfsqword(0x28u);
v8 = 0xBE;
v9 = 4;
v10 = 6;
v11 = 0x80;
v12 = 0xC5;
v13 = 0xAF;
v14 = 0x76;
v15 = 0x47;
v16 = 0x9F;
v17 = 0xCC;
v18 = 0x40;
v19 = 0x1F;
v20 = 0xD8;
v21 = 0xBF;
v22 = 0x92;
v23 = 0xEF;
v1 = (pwd3[6] << 8) | (pwd3[5] << 16) | (pwd3[4] << 24) | pwd3[7];// 大端序
v2 = (pwd3[10] << 8) | (pwd3[9] << 16) | (pwd3[8] << 24) | pwd3[11];
v3 = (pwd3[14] << 8) | (pwd3[13] << 16) | (pwd3[12] << 24) | pwd3[15];
v7 = 0;
v5 = 4;
v40 = sub_78A((pwd3[2] << 8) | (pwd3[1] << 16) | (*pwd3 << 24) | (unsigned int)pwd3[3]);// 大端序
v41 = sub_78A(v1);
v42 = sub_78A(v2);
v43 = sub_78A(v3); // v40~v43被pwd3赋值,按大端序规则,16个byte转成4个int
do
{
*(&v40 + v5) = sub_143B(*(&v40 + v7), *(&v40 + v7 + 1), *(&v40 + v7 + 2), *(&v40 + v7 + 3));// 变换
++v7;
++v5;
}
while ( v5 <= 29 );
v24 = v44 >> 24; // do循环体填充v43~v44之间的地址,v44~v47是最后四次循环被赋值
v25 = BYTE2(v44);
v26 = BYTE1(v44);
v27 = (unsigned __int8)v44;
v28 = v45 >> 24;
v29 = BYTE2(v45);
v30 = BYTE1(v45);
v31 = (unsigned __int8)v45;
v32 = v46 >> 24;
v33 = BYTE2(v46);
v34 = BYTE1(v46);
v35 = (unsigned __int8)v46;
v36 = v47 >> 24;
v37 = BYTE2(v47);
v38 = BYTE1(v47);
v39 = (unsigned __int8)v47;
for ( i = 0; i <= 15; ++i )
{
if ( *(&v24 + i) != *(&v8 + i) ) // v44~v47的4个int分成16个byte,与v8比较
return 0xFFFFFFFFLL;
}
return 1LL;
}
sub_143B函数,异或运算
__int64 __fastcall sub_143B(int a1, int a2, int a3, unsigned int a4)
{
return a1 ^ (unsigned int)sub_1464(a2 ^ a3 ^ a4);
}
sub_1464函数,主要也是异或运算
__int64 __fastcall sub_1464(unsigned int a1)
{
int v1; // ST18_4
int v3[290]; // [rsp+20h] [rbp-490h]
unsigned __int64 v4; // [rsp+4A8h] [rbp-8h]
v4 = __readfsqword(0x28u);
qmemcpy(v3, &off_1940, 1152uLL);
v1 = (v3[BYTE2(a1)] << 16) | v3[(unsigned __int8)a1] | (v3[BYTE1(a1)] << 8) | (v3[a1 >> 24] << 24);
return __ROL4__(v1, 12) ^ (unsigned int)(__ROL4__(v1, 8) ^ __ROR4__(v1, 2)) ^ __ROR4__(v1, 6);
}
于是do循环体中的变换赋值主要是异或运算,直接可逆,sub_1464函数的代码直接用,写脚本即可解出pwd3
#include <stdio.h>
#include "ida_defs.h"
unsigned int off_1940[288] = {
0xD6, 0x90, 0xE9, 0xFE, 0xCC, 0xE1, 0x3D, 0xB7,
0x16, 0xB6, 0x14, 0xC2, 0x28, 0xFB, 0x2C, 0x05,
0x2B, 0x67, 0x9A, 0x76, 0x2A, 0xBE, 0x04, 0xC3,
0xAA, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,
0x9C, 0x42, 0x50, 0xF4, 0x91, 0xEF, 0x98, 0x7A,
0x33, 0x54, 0x0B, 0x43, 0xED, 0xCF, 0xAC, 0x62,
0xE4, 0xB3, 0x1C, 0xA9, 0xC9, 0x08, 0xE8, 0x95,
0x80, 0xDF, 0x94, 0xFA, 0x75, 0x8F, 0x3F, 0xA6,
0x47, 0x07, 0xA7, 0xFC, 0xF3, 0x73, 0x17, 0xBA,
0x83, 0x59, 0x3C, 0x19, 0xE6, 0x85, 0x4F, 0xA8,
0x68, 0x6B, 0x81, 0xB2, 0x71, 0x64, 0xDA, 0x8B,
0xF8, 0xEB, 0x0F, 0x4B, 0x70, 0x56, 0x9D, 0x35,
0x1E, 0x24, 0x0E, 0x5E, 0x63, 0x58, 0xD1, 0xA2,
0x25, 0x22, 0x7C, 0x3B, 0x01, 0x21, 0x78, 0x87,
0xD4, 0x00, 0x46, 0x57, 0x9F, 0xD3, 0x27, 0x52,
0x4C, 0x36, 0x02, 0xE7, 0xA0, 0xC4, 0xC8, 0x9E,
0xEA, 0xBF, 0x8A, 0xD2, 0x40, 0xC7, 0x38, 0xB5,
0xA3, 0xF7, 0xF2, 0xCE, 0xF9, 0x61, 0x15, 0xA1,
0xE0, 0xAE, 0x5D, 0xA4, 0x9B, 0x34, 0x1A, 0x55,
0xAD, 0x93, 0x32, 0x30, 0xF5, 0x8C, 0xB1, 0xE3,
0x1D, 0xF6, 0xE2, 0x2E, 0x82, 0x66, 0xCA, 0x60,
0xC0, 0x29, 0x23, 0xAB, 0x0D, 0x53, 0x4E, 0x6F,
0xD5, 0xDB, 0x37, 0x45, 0xDE, 0xFD, 0x8E, 0x2F,
0x03, 0xFF, 0x6A, 0x72, 0x6D, 0x6C, 0x5B, 0x51,
0x8D, 0x1B, 0xAF, 0x92, 0xBB, 0xDD, 0xBC, 0x7F,
0x11, 0xD9, 0x5C, 0x41, 0x1F, 0x10, 0x5A, 0xD8,
0x0A, 0xC1, 0x31, 0x88, 0xA5, 0xCD, 0x7B, 0xBD,
0x2D, 0x74, 0xD0, 0x12, 0xB8, 0xE5, 0xB4, 0xB0,
0x89, 0x69, 0x97, 0x4A, 0x0C, 0x96, 0x77, 0x7E,
0x65, 0xB9, 0xF1, 0x09, 0xC5, 0x6E, 0xC6, 0x84,
0x18, 0xF0, 0x7D, 0xEC, 0x3A, 0xDC, 0x4D, 0x20,
0x79, 0xEE, 0x5F, 0x3E, 0xD7, 0xCB, 0x39, 0x48,
0xC6, 0xBA, 0xB1, 0xA3, 0x50, 0x33, 0xAA, 0x56,
0x97, 0x91, 0x7D, 0x67, 0xDC, 0x22, 0x70, 0xB2,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
unsigned int sub_1464(unsigned int a1)
{
int v1;
v1 = (off_1940[BYTE2(a1)] << 16) | off_1940[(unsigned __int8)a1] | (off_1940[BYTE1(a1)] << 8) | (off_1940[a1 >> 24] << 24);
return __ROL4__(v1, 12) ^ (unsigned int)(__ROL4__(v1, 8) ^ __ROR4__(v1, 2)) ^ __ROR4__(v1, 6);
}
int main()
{
unsigned int v40[30] = { 0 };
v40[26] = 0xBE040680;
v40[27] = 0xC5AF7647;
v40[28] = 0x9FCC401F;
v40[29] = 0xD8BF92EF;
int i;
for (i = 25; i >= 0; i--)
v40[i] = sub_1464(v40[i + 1] ^ v40[i + 2] ^ v40[i + 3]) ^ v40[i + 4];
for (i = 0; i < 4; i++)
printf("%c%c%c%c", ((char*)&v40[i])[0], ((char*)&v40[i])[1], ((char*)&v40[i])[2], ((char*)&v40[i])[3]);
return 0;
//fl4g_is_s0_ug1y!
}
将三个password输入,得到flag
但是提交失败,看里别的师傅的wp,第一个password走迷宫出错了
正确的pwd1为ddwwxxssxaxwwaasasyywwdd
于是flag为sctf{ddwwxxssxaxwwaasasyywwdd-c2N0Zl85MTAy(fl4g_is_s0_ug1y!)}
[MRCTF2020]EasyCpp
elf文件,无壳,ida分析
main函数,读取输入,输入赋到v21,利用迭代器将v21从头到尾全部异或1
异或1后的输入进入depart函数,将输入分解成其因子,各因子间用空格分隔,结果放到v15,v15进入func进行替换,数字变字符,空格变等号,替换后的结果进入check与已知比较
depart函数
写逆运算脚本即可得到正确的输入
#coding:utf-8
res=['=zqE=z=z=z','=lzzE','=ll=T=s=s=E','=zATT',
'=s=s=s=E=E=E','=EOll=E','=lE=T=E=E=E',
'=EsE=s=z','=AT=lE=ll']
for s in res:
ss=s.replace('O','0').replace('l','1').replace('z','2').replace('E','3').replace('A','4').replace('s','5').replace('G','6').replace('T','7').replace('B','8').replace('q','9').replace('=',' ')
data=ss.split(' ')[1:] #第0个均为空格 故从第1个开始
sum=1
for i in data: #乘回去
sum*=int(i,10)
sum^=1 #异或1
print(sum),
#2345 1222 5774 2476 3374 9032 2456 3531 6720
将数字串取32位大写md5散列即可提交成功
[GUET-CTF2019]encrypt
elf文件,无壳,ida分析
main函数,逻辑清晰,读取输入,对输入进行RC4加密,再进行很像base64的变换,只是没有从表中取值,变换后的输入与已知的数据比较,验证输入
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int v3; // eax
int v4; // eax
char v6; // [rsp+4h] [rbp-93Ch]
int i; // [rsp+8h] [rbp-938h]
int v8; // [rsp+Ch] [rbp-934h]
char v9; // [rsp+10h] [rbp-930h]
char v10; // [rsp+420h] [rbp-520h]
char v11; // [rsp+421h] [rbp-51Fh]
char v12; // [rsp+422h] [rbp-51Eh]
char v13; // [rsp+423h] [rbp-51Dh]
char v14; // [rsp+424h] [rbp-51Ch]
char v15; // [rsp+425h] [rbp-51Bh]
char v16; // [rsp+426h] [rbp-51Ah]
char v17; // [rsp+427h] [rbp-519h]
char input; // [rsp+430h] [rbp-510h]
char v19[1032]; // [rsp+530h] [rbp-410h]
unsigned __int64 v20; // [rsp+938h] [rbp-8h]
v20 = __readfsqword(0x28u);
v10 = 16;
v11 = 32;
v12 = 48;
v13 = 48;
v14 = 32;
v15 = 32;
v16 = 16;
v17 = 64;
memset(&input, 0, 0x100uLL);
v8 = strlen(&input);
memset(v19, 0, 0x400uLL);
printf("please input your flag:", a2, v19);
scanf("%s", &input); // 获取输入
memset(&v9, 0, 1032uLL);
RC4_S(&v9, (__int64)&v10, 8); // RC4加密算法,生成S盒,打乱S盒,密钥为v10~17的8个字节
v3 = strlen(&input);
RC4_encrypt(&v9, (__int64)&input, v3); // RC4加密算法,对输入进行异或加密
v4 = strlen(&input);
base64_like((__int64)&input, v4, v19, &v6); // 对加密后的输入进行变换,变换结构很像base64,变换的结果放到v19
for ( i = 0; i <= 50; ++i )
{
if ( v19[i] != res[i] ) // v19与res比较,验证输入
{
puts("Wrong");
return 0LL;
}
}
puts("Good");
return 0LL;
}
由最后要比较的数据,逆很像base64的变换,得到输入经RC4加密后的密文
res="Z`TzzTrD|fQP[_VVL|yneURyUmFklVJgLasJroZpHRxIUlH\\vZE="
data=[]
for i in range(len(res)-1):
data.append(ord(res[i])-61)
data.append(ord('='))
cipher=[]
for i in range(0,len(data),4):
tmp=bin(data[i]).replace('0b','').zfill(6)+bin(data[i+1]).replace('0b','').zfill(6)+bin(data[i+2]).replace('0b','').zfill(6)+bin(data[i+3]).replace('0b','').zfill(6)
a=int('0b'+tmp[0:8],2)
b=int('0b'+tmp[8:16],2)
c=int('0b'+tmp[16:24],2)
cipher.append(a)
cipher.append(b)
cipher.append(c)
print(cipher)
#[118, 53, 253, 245, 125, 71, 254, 149, 19, 122, 38, 89, 63, 255, 49, 161, 133, 124, 99, 2, 110, 189, 147, 106, 62, 77, 141, 215, 39, 115, 45, 94, 204, 98, 242, 223, 229, 210, 61]
由已知的密钥(v10~v17的8个字节)和密文cipher,解密RC4,即可得到flag
#include<stdio.h>
void rc4_init(unsigned char* s, unsigned char* key, unsigned long Len_k) //初始化函数
{
int i = 0, j = 0;
char k[256] = { 0 };
unsigned char tmp = 0;
for (i = 0; i < 256; i++) {
s[i] = i;
k[i] = key[i % Len_k];
}
for (i = 0; i < 256; i++) {
j = (j + s[i] + k[i]) % 256;
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
}
/*
RC4加解密函数
unsigned char* Data 加解密的数据
unsigned long Len_D 加解密数据的长度
unsigned char* key 密钥
unsigned long Len_k 密钥长度
*/
void rc4_crypt(unsigned char* Data, unsigned long Len_D, unsigned char* key, unsigned long Len_k) //加解密
{
unsigned char s[256];
rc4_init(s, key, Len_k);
int i = 0, j = 0, t = 0;
unsigned long k = 0;
unsigned char tmp;
for (k = 0; k < Len_D; k++) {
i = (i + 1) % 256;
j = (j + s[i]) % 256;
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
t = (s[i] + s[j]) % 256;
Data[k] = Data[k] ^ s[t];
}
}
void main()
{
//密钥
unsigned char key[] = {16,32,48,48,32,32,16,64};
//密钥长度
unsigned long key_len = sizeof(key);
//密文
unsigned char data[] = { 118, 53, 253, 245, 125, 71, 254, 149, 19, 122, 38, 89, 63, 255, 49, 161, 133, 124, 99, 2, 110, 189, 147, 106, 62, 77, 141, 215, 39, 115, 45, 94, 204, 98, 242, 223, 229, 210, 61 };
//解密
rc4_crypt(data, sizeof(data), key, key_len);
for (int i = 0; i < sizeof(data); i++)
{
printf("%c", data[i]);
}
}
//flag{e10adc3949ba59abbe56e057f20f883e}
[QCTF2018]Xman-babymips
mips文件,无壳,用ida7.5打开(其他版本的ida可能不能反编译mips)
main函数,读取输入,长度为32,先进行input[i]^=32-i的变换,变换后的input的前5个字符与"Q|j{g"比较,相同时进入sub_4007F0函数,对后27个字符再进行变换
sub_4007F0函数,不考虑前5个字符,下标从5开始,分奇偶数对输入进行移位变换,变换后与已知数据比较
写逆脚本即可得到flag
#coding:utf-8
s="Q|j{g"
off_410D04=[0,0,0,0,0,0x52, 0xFD, 0x16, 0xA4, 0x89, 0xBD, 0x92, 0x80, 0x13, 0x41,
0x54, 0xA0, 0x8D, 0x45, 0x18, 0x81, 0xDE, 0xFC, 0x95, 0xF0,
0x16, 0x79, 0x1A, 0x15, 0x5B, 0x75, 0x1F] #前面补5个0,保持下标一致
flag=[0]*32
for i in range(len(s)):
flag[i]=ord(s[i])
for i in range(5,len(off_410D04)):
if i&1:
flag[i]=(off_410D04[i]<<2)&0xfc|(off_410D04[i]>>6)&0x03
else:
flag[i]=(off_410D04[i]>>2)&0x3f|(off_410D04[i]<<6)&0xc0
for i in range(len(flag)):
flag[i]^=32-i
print(''.join(chr(i) for i in flag))
#qctf{ReA11y_4_B@89_mlp5_4_XmAn_}