REVERSE-PRACTICE-BUUCTF-17
- [网鼎杯 2020 青龙组]jocker
- [2019红帽杯]childRE
- [MRCTF2020]PixelShooter
- [ACTF新生赛2020]SoulLike
[网鼎杯 2020 青龙组]jocker
exe程序,运行后提示输入flag,无壳,用ida分析
main函数平衡栈后,F5反汇编
主要逻辑为
读取输入,检验输入长度是否为24,对输入进行变换,与已知数组比较,执行一段从encrypt函数起始地址开始的SMC,分析可知,finally函数也会完全被SMC,未经变换的输入作为参数,调用encrypt函数和finally函数
先写input经过wrong变换和omg验证的逆运算脚本,得到的是假flag
往下走,在ida中使用idapython脚本完成SMC自修改代码
from idaapi import *
from idautils import *
start_addr = 0x401500
key = 0x41
for i in range(start_addr,start_addr+187):PatchByte(i,Byte(i)^key)
SMC前,encrypt函数和finally函数各自包含了一大段数据
SMC后,encrypt函数和finally函数的指令间都有一些db指令,实际上为花指令
encrypt函数:
finally函数:
手动nop掉多余的db指令,让ida能够自动分析成代码
完成nop后,发现encrypt函数,在两端黑色代码中间插有一段红色代码
这时的处理方法是,在encrypt函数起始地址0x401500处,右键->undefine,变成黄色的数据,再按c转成代码,这时,encrypt函数起始地址与finally函数起始地址之间的代码全部为红色,再在encrypt函数起始地址0x401500处,右键->create function,平衡栈后,F5反汇编
这里encrypt函数反汇编后,好像没有用到未经变换的input,可能是patch代码的时候出错了,不过可以猜测为input和字符串“hahahaha_do_you_find_me?”异或,再与已知的unk_403040数组比较
写encrypt函数的逆运算脚本,得到部分flag,之所以是部分flag,是因为长度为24的unk_403040的最后5个元素为0,“d_me?”与0异或不变,或者v6=19说明了只异或了19次,而且提交失败
同encrypt函数的解析步骤,对finally函数,先nop掉多余的db指令,undefine掉黑色代码,转成数据,按c再转成代码,右键->create function,F5反汇编
没看懂这个函数,看了其他师傅的wp,说这里也是异或,能看到的5个值[37,116,112,38,58]是异或后要比较的值,由于input的最后一位一定是“}”,它异或一个值(“71”)后与58相等,而且它之前的四位也是和这个值(“71”)异或,结果是58之前的四个值
写finally函数的逆运算脚本得到后半部分的flag,替换掉“flag{d07abccf8a410cd_me?”的后5位,flag即为“flag{d07abccf8a410cb37a}”
[2019红帽杯]childRE
exe程序,运行后直接输入,无壳,ida分析
交叉引用字符串“flag{MD5(your input)}”来到sub_140001610函数
sub_140001610函数主要的逻辑为
读取输入,验证输入的长度是否为31,对输入进行位置变换处理,位置变换的结果放到v2(name),经调试可知,输入为abcdefghijklmnopqrstuvwxyz12345时,v2(name)的内容为pqhrsidtujvwkebxylz1mf23n45ogca
对v2(name)调用UnDecorateSymbolName函数处理,结果放到outputstring,验证outputstring的长度62及其内容
signed __int64 sub_7FF6DC281610()
{signed __int64 input_len; // rax_QWORD *v1; // raxconst CHAR *v2; // r11__int64 v3; // r10__int64 v4; // r9const CHAR *v5; // r10signed __int64 outputstring_len; // rcx__int64 v7; // raxsigned __int64 result; // raxunsigned int v9; // ecx__int64 v10; // r9int v11; // er10__int64 v12; // r8__int128 input; // [rsp+20h] [rbp-38h]__int128 v14; // [rsp+30h] [rbp-28h]input = 0i64;v14 = 0i64;sub_7FF6DC281080((__int64)"%s", &input); // 读取输入input_len = -1i64;do++input_len;while ( *((_BYTE *)&input + input_len) );if ( input_len != 31 ) // 输入的长度为31{while ( 1 )Sleep(0x3E8u);}v1 = sub_7FF6DC281280(&input); // 对input的处理,通过调试可知,从31行到41行,是对input的位置变换,变换后的结果放入v2,也就是name// 例如,输入abcdefghijklmnopqrstuvwxyz12345,name=“pqhrsidtujvwkebxylz1mf23n45ogca”v2 = name; // v2==nameif ( v1 ){sub_7FF6DC2815C0((unsigned __int8 *)v1[1]);sub_7FF6DC2815C0(*(unsigned __int8 **)(v3 + 16));v4 = dword_7FF6DC2857E0;v2[v4] = *v5;dword_7FF6DC2857E0 = v4 + 1;}UnDecorateSymbolName(v2, outputString, 0x100u, 0);// v2,也就是name,经过UnDecorateSymbolName函数处理,结果放到outputstring中outputstring_len = -1i64;do++outputstring_len;while ( outputString[outputstring_len] );if ( outputstring_len == 62 ) // outputstring的长度为62{v9 = 0;v10 = 0i64;do // do循环体在验证outputstring的内容{v11 = outputString[v10];v12 = v11 % 23;if ( table[v12] != *(_BYTE *)(v10 + 0x7FF6DC283478i64) )// (_@4620!08!6_0*0442!@186%%0@3=66!!974*3234=&0^3&1@=&0908!6_0*&_exit(v9);if ( table[v11 / 23] != *(_BYTE *)(v10 + 0x7FF6DC283438i64) )// 55565653255552225565565555243466334653663544426565555525555222_exit(v9 * v9);++v9;++v10;}while ( v9 < 0x3E );sub_7FF6DC281020("flag{MD5(your input)}\n", v11 / 23, v12, v10);result = 0i64;}else{v7 = sub_7FF6DC2818A0(std::cout);std::basic_ostream<char,std::char_traits<char>>::operator<<(v7, sub_7FF6DC281A60);result = 0xFFFFFFFFi64;}return result;
}
先写验证outputstring的长度62及其内容的逆运算脚本,得到outputstring,为一个未修饰的C++符号名
UnDecorateSymbolName函数反修饰指定已修饰的 C++ 符号名,参考:UnDecorateSymbolName
第1个参数为已修饰的 C++ 符号名,此名称能以始终为问号 (?) 的首字符鉴别,本题中为v2(name),第2个参数指向字符串缓冲区的指针,该缓冲区接收未修饰的名字,本题中为outputstring
可知需要对outputstring符号名修饰才能得到v2(name),参考:c/c++函数名修饰规则
或者通过写C++代码,调用__FUNCDNAME__宏(FUNCDNAME:只有在函数内部才有效,返回该函数经编译器修饰后的名字。如果编译器选项中设定了/EP或/P,则__FUNCDNAME__是未定义。)直接得到函数经编译器修饰后的符号名,需要注意的是函数所在的类,域,返回类型,函数名,参数类型都必须和已知完全相同
#include<iostream>
using namespace std;
class R0Pxx {//函数所在的类
public:R0Pxx() {//该类的构造函数unsigned char a;My_Aut0_PWN(&a);}private://函数属于私有域//函数的返回类型 函数名 以及参数类型char * My_Aut0_PWN(unsigned char*) {char * ret = NULL;//调用宏 输出该函数经编译器修饰后的符号名printf("%s", __FUNCDNAME__);return ret;}
};
int main() {new R0Pxx();getchar();return 0;
}
运行结果即为v2(name),注意是在x86架构下运行的结果
写逆位置变换脚本即可得到输入,对输入进行md5散列即可得到flag
[MRCTF2020]PixelShooter
apk文件,jadx-gui打开,什么都没发现
用Apktool Box反编译apk后,在PixelShooter->lib->armeabi-v7a目录下发现3个.so文件,依次分析,同样什么都没发现
突然想到这是个安卓unity游戏,而安卓unity游戏的核心逻辑一般位于assets\bin\Data\Managed\Assembly-CSharp.dll
用dnSpy打开,在类UIController的GameOver方法中找到flag
[ACTF新生赛2020]SoulLike
elf文件,无壳,ida分析
main函数,读取输入,验证输入的前5个字符是否为“actf{”,将输入{}内的字符放入v8,可知输入{}内的长度为12,调用sub_83A函数验证v8,且输入的最后一个字符为“}”
分析sub_83A函数,可以看到是对input{}内12个字符的超多异或运算
在sub_83A函数的最后,是input{}内的12个字符经过超多异或运算后的值与已知的v4到v15比较,因为会有类似input[3]^=input[2]的情况,所以不能通过调试得到每个字符最后等效的异或值是什么
不过当比较为不相同时,程序会打印输入是哪个位置的字符错了,于是可以写脚本爆破出flag
爆破脚本
#coding:utf-8
from itertools import *
import subprocessflag=""
t=""
for i in range(12):for j in range(32,126):flag ="actf{"+t+chr(j)+"}" p = subprocess.Popen(["./SoulLike"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)p.stdin.write(flag)p.stdin.close()out=p.stdout.read()p.stdout.close()if "#"+str(i) not in out:t+=chr(j)breakprint(flag)
运行结果