REVERSE-PRACTICE-BUUCTF-21
- [SCTF2019]babyre
- [MRCTF2020]EasyCpp
- [GUET-CTF2019]encrypt
- [QCTF2018]Xman-babymips
[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_4int v2; // ST28_4int v3; // ST2C_4signed 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个intdo{*(&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_4int 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=1for i in data: #乘回去sum*=int(i,10)sum^=1 #异或1print(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; // eaxint v4; // eaxchar 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,变换的结果放到v19for ( 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)&0x03else: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_}