REVERSE-PRACTICE-BUUCTF-20
- [SCTF2019]creakme
- [网鼎杯 2020 青龙组]bang
- [WUSTCTF2020]funnyre
- Dig the way
[SCTF2019]creakme
exe程序,运行后提示输入ticket,无壳,用ida分析
交叉引用字符串“please input your ticket:”来到sub_402540函数
分析sub_402320函数,在DebugBreak和return之间有一段代码引用
修改EIP从地址0x402412处开始执行,调试可知sub_402450函数是对.SCTF段数据的SMC
回到sub_402540函数,往下走
分析sub_4024A0函数,过掉两个反调试,执行SMC好的.SCTF段的代码,发现是将静态可见的字符串">pvfqYc,4tTc2UxRmlJ,sB{Fh4Ck2:CFOb4ErhtIcoLo"变成了动态可见的字符串"nKnbHsgqD3aNEB91jB3gEzAr+IklQwT1bSs3+bXpeuo="
回到sub_402540函数,往下走
获取输入input,拷贝input到Dst,sub_4020D0函数对Dst进行AES的CBC模式加密,密文存储在v2,令v4=v2,那段动态可见的字符串赋给v6,最后就是比较v4和v6
sub_4020D0->sub_401690
在sub_401690函数中发现两个字符串"sctfsctfsctfsctf"和"sycloversyclover",由于AES的CBC模式加密需要一个偏移量iv与第0块明文异或,于是可以确定iv=“sctfsctfsctfsctf”,key=“sycloversyclover”,参考:AES五种加密模式(CBC、ECB、CTR、OCF、CFB)
已知key,iv,cipher,写解AES.CBC脚本即可得到flag
from Crypto.Cipher import AES
import base64
key="sycloversyclover"
iv="sctfsctfsctfsctf"
cipher=base64.b64decode("nKnbHsgqD3aNEB91jB3gEzAr+IklQwT1bSs3+bXpeuo=")
aes=AES.new(key,AES.MODE_CBC,iv)
print(aes.decrypt(cipher))
#sctf{Ae3_C8c_I28_pKcs79ad4}
[网鼎杯 2020 青龙组]bang
apk文件,jadx-gui打开什么都没有,SecShell提示是加了壳
果然,这个apk加了梆梆的壳
使用frida脚本脱壳
脱壳后的dex文件再用jadx-gui分析,在com.example.how_debug.MainActivity类中找到flag
[WUSTCTF2020]funnyre
elf文件,运行后没有输入,无壳,ida分析
main函数没有被ida识别成函数,是因为有花指令
jz和jnz指令都会跳转到同一条(下一条)指令,把jz和jnz都nop掉
call后面的地址不存在,是因为在原本正确的指令字节基础上加了多余的字节,把call那条指令按d转成数据后,顺序地一个一个nop掉多余的字节,直到ida能够正确识别出指令
jz指令跳过了从地址0x400621开始的两个字节,也是直接nop掉jz
总共有4处类似这样的花指令,去除完全后,选中main函数的全部红色代码,按p创建函数,F5反汇编
main函数中,验证输入的长度是否为38,且有flag{}包住,对花括号内的32个字符做300多次运算,最后与已知的unk_4025C0比较,验证输入
angr参考:angr学习【一】
脚本参考:buuctf刷题记录25 [WUSTCTF2020]funnyre
调用angr框架写脚本即可得到flag
import angr
import claripyp=angr.Project('./attachment',load_options={"auto_load_libs": False})
f=p.factory
state = f.entry_state(addr=0x400605)#设置state开始运行时的地址
flag = claripy.BVS('flag',8*32)#要求的内容有32个,用BVS转成二进制给flag变量
state.memory.store(0x603055+0x300+5,flag)#因为程序没有输入,所以直接把字符串设置到内存
state.regs.rdx=0x603055+0x300
state.regs.rdi=0x603055+0x300+5#然后设置两个寄存器sm = p.factory.simulation_manager(state)#准备从state开始遍历路径print("ready")sm.explore(find=0x401DAE)#遍历到成功的地址if sm.found:print("sucess")x=sm.found[0].solver.eval(flag,cast_to=bytes)print(x)
else:print('error')
#ready
#sucess
#b'1dc20f6e3d497d15cef47d9a66d6f1af'
Dig the way
exe程序,运行后直接闪退,无壳,ida分析
栈溢出的题目
main函数的主要逻辑为
读取data文件到v7,执行func0,func1和func2函数,返回值分别赋给v9,v10和v11,如果v11为0,则调用get_key函数获得flag。但是对于func2函数,无论传入的参数为何值,其返回值永远为正,v11不可能为0,而对于func1函数,如果传入的参数合适,其返回值可以为0,于是便需要通过func0函数交换v15和v16所存储的函数指针,使得执行func1函数时,其返回值可以赋给v11
int __cdecl main(int argc, const char **argv, const char **envp)
{int result; // eaxint v4; // ebxsize_t v5; // eaxint v6; // ebxchar v7[20]; // [esp+1Ch] [ebp-48h]int v8; // [esp+30h] [ebp-34h]int v9; // [esp+34h] [ebp-30h]int v10; // [esp+38h] [ebp-2Ch]int v11; // [esp+3Ch] [ebp-28h]int v12; // [esp+40h] [ebp-24h]int v13; // [esp+44h] [ebp-20h]signed int (__cdecl *v14)(int, int, int); // [esp+48h] [ebp-1Ch]int (__cdecl *v15)(int, int, int); // [esp+4Ch] [ebp-18h]int (__cdecl *v16)(int, int, int); // [esp+50h] [ebp-14h]int v17; // [esp+54h] [ebp-10h]int v18; // [esp+58h] [ebp-Ch]FILE *v19; // [esp+5Ch] [ebp-8h]__main();v14 = func0; // 交换值v15 = func1; // 含abs的运算,返回值可正可负可零v16 = func2; // 含abs的运算,返回值永正v8 = 0;v9 = 1;v10 = 2;v11 = 3;v12 = 3;v13 = 4;v19 = fopen("data", "rb"); // 打开data文件流if ( !v19 )return -1;fseek(v19, 0, 2); // 指针指到文件流末尾v18 = ftell(v19); // 获取文件大小fseek(v19, 0, 0); // 指针指回文件开始v17 = ftell(v19);if ( v17 ){puts("something wrong");result = 0;}else{for ( i = 0; i < v18; ++i ){v4 = i;v7[v4] = fgetc(v19); // data文件的字节读到v7// 由于v7只是个20字节大小的数组,如果data文件中的字节数大于20,则会把v8~v13的值覆盖掉,即栈溢出}v5 = strlen(v7);if ( v5 <= v18 ){v18 = v11; // v18=v11,且v18在获取flag的函数get_key中用到i = 0;v17 = v13;while ( i <= 2 ){v6 = i + 1;*(&v8 + v6) = (*(&v14 + i))((int)&v8, v12, v13);// 执行func0,func1和func2,返回值分别赋给v9,v10,v11v12 = ++i; // v12和v13用i赋值v13 = i + 1;}if ( v11 ){result = -1;}else // v11为0时,执行get_key获得flag{get_key(v18, v17);system("PAUSE");result = 0;}}else{result = -1;}}return result;
}
由于程序读取data文件是从头到尾全部读取,而v7只有20个字节大小,如果data文件的字节数大于20,多出来的字节就会将v8,v9,v10等等这些变量覆盖
v12和v13被程序赋值为3和4,进入func0函数后并不能达到交换v15和v16的目的,因为此时func0函数中参与运算的变量为v11和v12,而不是v15和v16。于是便利用程序读data文件有可能覆盖v12和v13的漏洞,将v12和v13覆盖为7和8
v7有20个字节,v8~v11是4个int,也就是4x4=16个字节,于是data文件需要从第36个字节开始,将v12和v13覆盖为7和8,data为
发现exe程序还是运行后闪退,调试发现func1函数的返回值v11还是为正
仔细看调用func0~func2这部分代码,在v12和v13分别为7和8作为参数传入func0函数交换v15和v16后,v15执行func2函数,v16执行func1函数,而在循环中v12和v13由i赋值,当执行v16(func1)函数时,v12和v13的值分别为2和3
也就是说,func1函数执行时,实际上是abs(v10+v11)-abs(v11)-abs(v10)+2,v10等于2,要使返回的值为0,则需v11为-1,于是同样利用程序data读文件的漏洞将v11覆盖为-1,data为
再次运行exe程序,得到flag