REVERSE-PRACTICE-BUUCTF-13
- firmware
- [ACTF新生赛2020]Oruga
- [Zer0pts2020]easy strcmp
- [GXYCTF2019]simple CPP
firmware
.bin(二进制)文件,由题目提示知是路由器固件逆向
参考:逆向路由器固件之解包 Part1
linux安装好binwalk和firmware-mod-kit
binwalk会分析二进制文件中可能的固件头或者文件系统,然后输出识别出的每个部分以及对应的偏移量
binwalk该二进制文件,发现包含了squashfs文件系统(squashfs是linux下的一种只读压缩文件系统类型)
binwalk -e 该二进制文件,提取出各部分数据到各个文件,相当于解压固件,解压出来的文件放在当前目录的/_firmware.bin.extracted文件夹下(注意此时的suqashfs-root文件夹是否为空)
使用firmware-mod-kit解包提取出来的squashfs文件
/*******************************************************
firmware-mod-kit的一些用法:
extract-firmware.sh 解包固件
build-firmware.sh 重新封包
check_for_upgrade.sh 检查更新
unsquashfs_all.sh 解包提取出来的squashfs文件
*******************************************************/
解包squashfs文件出来的文件放在了当前目录的squashfs-root-1文件夹中
(实际上,在本人虚拟机binwalk -e 该二进制文件后,生成文件夹/_firmware.bin.extracted中的squashfs-root文件夹,已经是解包squashfs文件出来的文件夹了,即squashfs-root文件夹和squashfs-root-1文件夹中包含的文件是相同的,如果squashfs-root文件夹下内容为空,是由于sasquatch安装有问题导致的,可以通过重新安装sasquatch解决,参考:dir815_FW_102.bin路由器固件解压碰到的坑)
在/_firmware.bin.extracted/squashfs-root/tmp目录或/_firmware.bin.extracted/squashfs-root-1/tmp目录下有一个backdoor文件
backdoor查壳发现有upx壳,脱壳后拖入ida分析
在字符串窗口找到网址
交叉引用网址,在initConnection函数中找到端口
[ACTF新生赛2020]Oruga
elf文件,无壳,ida分析
main函数逻辑清晰,获取输入,检验输入是否为“actf{”开头,验证输入内容,以及最后一个字符是否为“}”
进入check函数,分析可知是一个16x16的迷宫,和常规迷宫的按一次方向键则往该方向前进一格的机制不同,该迷宫的前进机制为,按一次方向键,则往该方向一直前进,直到撞到墙,具体分析知道,在map为零的位置时,可以持续前进,到达第一个非零的位置就停下来,然后减一个步长回到前一个零的位置,再读input的内容更新步长,更新步长实际上就是换方向
_BOOL8 __fastcall check(__int64 input)
{int index; // [rsp+Ch] [rbp-Ch]signed int input_index; // [rsp+10h] [rbp-8h]signed int step; // [rsp+14h] [rbp-4h]index = 0;input_index = 5;step = 0;while ( map[index] != '!' ) // 终点为"!"{index -= step; // 由第48行代码可知,由于index叠加走到了非零的位置,这时需要减一个步长,回到前一个零位置if ( *(_BYTE *)(input_index + input) != 'W' || step == -16 )// 由input的内容决定步长step{if ( *(_BYTE *)(input_index + input) != 'E' || step == 1 ){if ( *(_BYTE *)(input_index + input) != 'M' || step == 16 ){if ( *(_BYTE *)(input_index + input) != 'J' || step == -1 )return 0LL;step = -1; // J-左}else{step = 16; // M-下}}else{step = 1; // E-右}}else{step = -16; // W-上}++input_index; // 读下一个input的字符while ( !map[index] ) // 该while循环体只能在map[index]=='0'时执行{if ( step == -1 && !(index & 0xF) ) // 判断边界的四个ifreturn 0LL;if ( step == 1 && index % 16 == 15 )return 0LL;if ( step == 16 && (unsigned int)(index - 240) <= 0xF )return 0LL;if ( step == -16 && (unsigned int)(index + 15) <= 0x1E )return 0LL;index += step; // index按照当前的步长循环叠加,即按下一个方向键,就会沿着这个方向一直走,直到第一个非零的位置停下}}return *(_BYTE *)(input_index + input) == '}';
}
把map提取出来,制成16x16的迷宫,按照前进机制走完迷宫即可,起始点为左上角的[0,0],终止点为“!”(0x21),W-上,M-下,J-左,E-右,路线即为flag
[Zer0pts2020]easy strcmp
elf文件,无壳,ida分析
main函数,有一个比较字符串的if语句决定输出的内容,其他什么也没有
来到start函数,发现在调用main函数前,先调用了fini函数和init函数
fini函数直接返回了,分析init函数,可以知道,在调用main函数前,程序将.init_array段地址到.fini_array段地址之间的函数全部执行一遍,命令行参数作为段之间函数的参数
这里可以看到,.init_array段和.fini_array段之间有3个函数,依次分析知道,重要的是sub_795函数
sub_795->sub_6EA,分析sub_6EA函数,计算输入的长度,将输入的内容顺序地与qword_201060数组的元素相减,然后去到main函数和那段字符串比较
写脚本即可得到flag
[GXYCTF2019]simple CPP
exe程序,运行后提示输入flag,输入错误退出程序,无壳,ida分析
交叉引用字符串来到sub_140001290函数
__int64 sub_140001290()
{bool v0; // si__int64 v1; // rax__int64 v2; // r8unsigned __int8 *v3; // raxunsigned __int8 *v4; // rbxint v5; // er10__int64 v6; // r11_BYTE *input_copy; // r9void **v8; // r8__int64 arr[3]; // rdi__int64 arr[2]; // r15__int64 arr[1]; // r12__int64 arr[0]; // rbpsigned int v13; // ecxunsigned __int8 *v14; // rdx__int64 v15; // rdi__int64 *v16; // r14__int64 v17; // rbp__int64 v18; // r13_QWORD *v19; // rdi__int64 v20; // r12__int64 v21; // r15__int64 v22; // rbp__int64 v23; // rdx__int64 v24; // rbp__int64 v25; // rbp__int64 v26; // r10__int64 v27; // rdi__int64 v28; // r8bool v29; // dl__int64 v30; // raxvoid *v31; // rdxconst char *v32; // rax__int64 v33; // rax_BYTE *v34; // rcx__int64 v36; // [rsp+20h] [rbp-68h]void *input; // [rsp+30h] [rbp-58h]unsigned __int64 input_len; // [rsp+40h] [rbp-48h]unsigned __int64 v39; // [rsp+48h] [rbp-40h]v0 = 0;input_len = 0i64;v39 = 15i64;LOBYTE(input) = 0;LODWORD(v1) = printf(std::cout, "I'm a first timer of Logic algebra , how about you?");std::basic_ostream<char,std::char_traits<char>>::operator<<(v1, sub_140001B90);printf(std::cout, "Let's start our game,Please input your flag:");sub_140001DE0(std::cin, &input, v2); // 读取inputstd::basic_ostream<char,std::char_traits<char>>::operator<<(std::cout, sub_140001B90);if ( input_len - 5 > 25 ) // input不需要GXY{}包住{LODWORD(v33) = printf(std::cout, "Wrong input ,no GXY{} in input words");std::basic_ostream<char,std::char_traits<char>>::operator<<(v33, sub_140001B90);goto LABEL_45;}v3 = sub_1400024C8(32ui64); // 开辟一块大小为32的,元素类型为unsigned int8的地址空间给v3v4 = v3;if ( v3 ) // 新开辟的地址空间全部赋0值{*v3 = 0i64;*(v3 + 1) = 0i64;*(v3 + 2) = 0i64;*(v3 + 3) = 0i64;}else{v4 = 0i64;}v5 = 0;if ( input_len > 0 ){v6 = 0i64;do{input_copy = &input;if ( v39 >= 16 )input_copy = input;v8 = &Dst;if ( qword_140006060 >= 0x10 )v8 = Dst; // Dst="i_will_check_is_debug_or_not",长度为28v4[v6] = input_copy[v6] ^ *(v8 + v5++ % 27);// input和Dst异或,结果放入v4++v6;}while ( v5 < input_len );}arr[3] = 0i64;arr[2] = 0i64;arr[1] = 0i64;arr[0] = 0i64;if ( input_len > 30 ) // input长度验证goto LABEL_28;v13 = 0;if ( input_len <= 0 )goto LABEL_28;v14 = v4; // v14=v4,是input和Dst异或的结果do // do循环体,实际上是把v14分成四段,前三段长度为8,分别放入v12,v11,v10中,剩下的放在v9// 依次记为arr[0],arr[1],arr[2],arr[3],重要的是算出arr[0~3]{v15 = *v14 + arr[3];++v13;++v14;switch ( v13 ){case 8:arr[0] = v15;goto LABEL_24;case 16:arr[1] = v15;goto LABEL_24;case 24:arr[2] = v15;
LABEL_24:v15 = 0i64;break;case 32:printf(std::cout, "ERRO,out of range");exit(1);break;}arr[3] = v15 << 8;}while ( v13 < input_len );if ( arr[0] ){v16 = sub_1400024C8(32ui64);*v16 = arr[0]; // arr[0~3]放入v16[0~3]中v16[1] = arr[1];v16[2] = arr[2];v16[3] = arr[3];goto LABEL_29;}
LABEL_28:v16 = 0i64;
LABEL_29:v36 = v16[2]; // v36=arr[2]v17 = v16[1]; // v17=arr[1]v18 = *v16; // v18=arr[0]v19 = sub_14000223C(32ui64); // 开辟一块大小为32的,元素类型为unsigned int8的地址空间给v19if ( IsDebuggerPresent() ) // 反调试{printf(std::cout, "Hi , DO not debug me !");Sleep(0x7D0u);exit(0);}v20 = v17 & v18; // v20=arr[1]&arr[0]*v19 = v17 & v18; // v19[0]=arr[1]&arr[0]v21 = v36 & ~v18; // v21=arr[2]&(~arr[0])v19[1] = v21; // v19[1]=arr[2]&(~arr[0])v22 = ~v17; // v22=~arr[1]v23 = v36 & v22; // v23=arr[2]&(~arr[1])v19[2] = v36 & v22; // v19[2]=arr[2]&(~arr[1])v24 = v18 & v22; // v24=arr[0]&(~arr[1])v19[3] = v24; // v19[3]=arr[0]&(~arr[1])if ( v21 != 0x11204161012i64 ) // 验证v19[1]==0x11204161012,即arr[2]&(~arr[0])==0x11204161012{v19[1] = 0i64;v21 = 0i64;}v25 = v21 | v20 | v23 | v24; // 需v25==0x3E3A4717373E7F1F成立,即(arr[2]&(~arr[0])) | (arr[1]&arr[0]) | (arr[2]&(~arr[1])) | (arr[0]&(~arr[1]))==0x3E3A4717373E7F1Fv26 = v16[1]; // v26=arr[1]v27 = v16[2]; // v27=arr[2]v28 = v23 & *v16 | v27 & (v20 | v26 & ~*v16 | ~(v26 | *v16));// (arr[2]&(~arr[1])) & (arr[0]) | (arr[2]) & ((arr[1]&arr[0]) | (arr[1]) & ~(arr[0]) | ~((arr[1]) | (arr[0])))==0x8020717153E3013v29 = 0;if ( v28 == 0x8020717153E3013i64 ) // 由第171行代码可知,v0需要为1,v28==0x8020717153E3013成立v29 = v25 == 0x3E3A4717373E7F1Fi64; // 验证v25==0x3E3A4717373E7F1F,相等返回1,不等返回0if ( (v25 ^ v16[3]) == 0x3E3A4717050F791Fi64 )// v16[3]=arr[3],即arr[3]^0x3E3A4717373E7F1F==0x3E3A4717050F791Fv0 = v29; // v0为v25==0x3E3A4717373E7F1F的返回值,等式成立返回1,不等返回0if ( (v21 | v20 | v26 & v27) != (~*v16 & v27 | 0xC00020130082C0Ci64) || v0 != 1 )// 需要v0为1,意味着v25==0x3E3A4717373E7F1F成立且((arr[2]&(~arr[0])) |(arr[1]&arr[0]) | (arr[1]) & (arr[2])) == (~(arr[0])& (arr[2]) | 0xC00020130082C0C){printf(std::cout, "Wrong answer!try again");j_j_free(v4);}else{LODWORD(v30) = printf(std::cout, "Congratulations!flag is GXY{");// input不需要GXY{}包住v31 = &input;if ( v39 >= 16 )v31 = input;v32 = sub_140001FD0(v30, v31, input_len);printf(v32, "}");j_j_free(v4);}
LABEL_45:if ( v39 >= 0x10 ){v34 = input;if ( v39 + 1 >= 0x1000 ){v34 = *(input - 1);if ( (input - v34 - 8) > 0x1F )invalid_parameter_noinfo_noreturn();}j_j_free(v34);}return 0i64;
}
写解arr[0~3]的脚本
写逆异或运算得到flag的脚本,由flag的明文字符串可知,arr[1]的结果错误,原因是原方程组有多组解,参考别的师傅的wp,比赛给出了第二部分的flag,e!P0or_a,替换掉错误的8个字符,结果为We1l_D0ne!P0or_algebra_am_i