ACTF2022-dropper re
DROPPER
一个upx壳的程序
总结: 一个用数组表示的大整数 是个int[512] 前500个是数据部分 a[500]是数据长度 用0-128表示ascii码 自定义的加减乘除)
参考:https://ppppz.net/2022/06/29/ACTF2022-dropper/ pz师傅的blog
http://www.ctfiot.com/46538.html 官方wp 似乎
动调dump
先慢慢分析 拖进x64dbg中跑一下 发现运行后输入flag直接提示wrong
停不到相关判断代码
发现api https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject
每次会跑到别的进程里面去 应该是新的进程来进行flag输入和检测部分 我们尝试直接dump
我们跳转到[RBP+68]
去内存布局中选中所在区域
右键将内存转存到文件
010打开 搜索MZ头 把MZ头前面没用的删掉
修复好后可以正常运行
分析dump.exe
简单分析找到main函数 (C480函数)( 字符串搜索或者 函数调用啥的都能简单找到
int __cdecl main(int argc, const char **argv, const char **envp)
{
char *v3; // rdi
__int64 i; // rcx
__int64 CopyInput; // rax
void *base_input; // rax
__int64 key; // rax
__int64 v8; // rax
size_t v9; // r8
__int64 v10; // rax
__int64 v11; // rax
int v12; // edi
char v14[32]; // [rsp+0h] [rbp-20h] BYREF
char v15; // [rsp+20h] [rbp+0h] BYREF
char input[72]; // [rsp+28h] [rbp+8h] BYREF
char Src[2040]; // [rsp+70h] [rbp+50h] BYREF
void (__fastcall ***v18)(_QWORD, __int64); // [rsp+868h] [rbp+848h]
__int64 v19; // [rsp+888h] [rbp+868h]
char v20[3576]; // [rsp+8B0h] [rbp+890h] BYREF
char Block[64]; // [rsp+16A8h] [rbp+1688h] BYREF
char v22[64]; // [rsp+16E8h] [rbp+16C8h] BYREF
char *v23; // [rsp+1728h] [rbp+1708h]
void (__fastcall ***v24)(_QWORD, _QWORD); // [rsp+1748h] [rbp+1728h]
void *v25; // [rsp+1768h] [rbp+1748h]
char v26; // [rsp+1788h] [rbp+1768h] BYREF
char *v27; // [rsp+1F78h] [rbp+1F58h]
char v28[64]; // [rsp+1F98h] [rbp+1F78h] BYREF
__int64 v29; // [rsp+1FD8h] [rbp+1FB8h]
void *v30; // [rsp+1FF8h] [rbp+1FD8h]
char v31[64]; // [rsp+2018h] [rbp+1FF8h] BYREF
char v32[64]; // [rsp+2058h] [rbp+2038h] BYREF
void (__fastcall ***v33)(_QWORD, _QWORD); // [rsp+2098h] [rbp+2078h]
int v34; // [rsp+20B4h] [rbp+2094h]
int v35; // [rsp+20D4h] [rbp+20B4h]
__int64 t; // [rsp+20E8h] [rbp+20C8h]
__int64 v37; // [rsp+20F0h] [rbp+20D0h]
void (__fastcall ***v38)(_QWORD, _QWORD); // [rsp+20F8h] [rbp+20D8h]
v3 = &v15;
for ( i = 0x6BEi64; i; --i )
{
*v3 = 0xCCCCCCCC;
v3 += 4;
}
v35 = 0;
thread_what(&unk_7FF7462B60F2);
t = Reverse(Block, &unk_7FF7462AE168, 5i64); // Reverse 通过对a2参数取反来隐藏一些数据 动调就可以无视了)
v37 = t;
printf(std::cout, t);
free_(Block); // 似乎是始放内存 栈啊什么的函数 无影响
sub_7FF7462917DF(input);
get(std::cin, input);
t = strlen_0(input); // 多次动调发现t和input长度一样
CopyInput = strcopy(input); // 复制字符串
base_input = Base64encode(CopyInput, t); // 通过base64码表 和动调知道是base64
copy_base_to_input(input, base_input); // 就是把base后的字符串放进input里面
v23 = v22;
t = E1(v22, input); // 这里t是**input
E2(Src, t);
v25 = malloc_0(0x7E0ui64);
if ( v25 )
{
v27 = &v26;
t = Reverse(v28, dword_7FF7462AF300, 360i64);
v37 = t;
v35 |= 1u;
key = strcopy(t);
v8 = str2int(v27, key); // 如key为'123456789'
// 经过函数转换为[6789,1234](十进制)存到v26中
v38 = sub_7FF746291433(v25, v8); // 一个虚表函数 但这里指向的不是真正的虚表
}
else
{
v38 = 0i64;
}
v24 = v38;
v18 = v38;
if ( (v35 & 1) != 0 )
{
v35 &= ~1u;
free_(v28);
}
sub_7FF746291226(v18); // 触发一个异常处理 替换了虚表函数
v30 = malloc_0(0x7D4ui64);
if ( v30 )
t = memcpy_0(v30, Src, v9);
else
t = 0i64;
v29 = t;
v19 = t;
(**v18)(v18, t); // 触发虚表函数
sub(v19, v20, (v18 + 1));
if ( check(v20) ) // check函数 要求a[0]为0 a[500]为1
{
t = Reverse(v31, &unk_7FF7462AF910, 4i64);
v37 = t;
LODWORD(v10) = printf(std::cout, t);
std::ostream::operator<<(v10, sub_7FF74629105A);
free_(v31);
}
else
{
t = Reverse(v32, &unk_7FF7462AF920, 5i64);
v37 = t;
LODWORD(v11) = printf(std::cout, t);
std::ostream::operator<<(v11, sub_7FF74629105A);
free_(v32);
}
v33 = v18;
if ( v18 )
t = sub_7FF746291316(v33, 1i64);
else
t = 0i64;
v34 = 0;
free_(input);
v12 = v34;
sub_7FF74629169F(v14, &unk_7FF7462A93D0);
return v12;
}
除0异常时 交给程序处理即可
一路调进虚表函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
char *v3; // rdi
__int64 i; // rcx
__int64 CopyInput; // rax
void *base_input; // rax
const char *key; // rax
_DWORD *v8; // rax
size_t v9; // r8
__int64 v10; // rax
__int64 v11; // rax
int v12; // edi
char v14[32]; // [rsp+0h] [rbp-20h] BYREF
char v15; // [rsp+20h] [rbp+0h] BYREF
char input[72]; // [rsp+28h] [rbp+8h] BYREF
char Src[2040]; // [rsp+70h] [rbp+50h] BYREF
void (__fastcall ***v18)(_QWORD, __int64); // [rsp+868h] [rbp+848h]
__int64 v19; // [rsp+888h] [rbp+868h]
char v20[3576]; // [rsp+8B0h] [rbp+890h] BYREF
char Block[64]; // [rsp+16A8h] [rbp+1688h] BYREF
char v22[64]; // [rsp+16E8h] [rbp+16C8h] BYREF
char *v23; // [rsp+1728h] [rbp+1708h]
void (__fastcall ***v24)(_QWORD, _QWORD); // [rsp+1748h] [rbp+1728h]
void *v25; // [rsp+1768h] [rbp+1748h]
char v26; // [rsp+1788h] [rbp+1768h] BYREF
char *v27; // [rsp+1F78h] [rbp+1F58h]
char v28[64]; // [rsp+1F98h] [rbp+1F78h] BYREF
__int64 v29; // [rsp+1FD8h] [rbp+1FB8h]
void *v30; // [rsp+1FF8h] [rbp+1FD8h]
char v31[64]; // [rsp+2018h] [rbp+1FF8h] BYREF
char v32[64]; // [rsp+2058h] [rbp+2038h] BYREF
void (__fastcall ***v33)(_QWORD, _QWORD); // [rsp+2098h] [rbp+2078h]
int v34; // [rsp+20B4h] [rbp+2094h]
int v35; // [rsp+20D4h] [rbp+20B4h]
__int64 t; // [rsp+20E8h] [rbp+20C8h]
__int64 v37; // [rsp+20F0h] [rbp+20D0h]
void (__fastcall ***v38)(_QWORD, _QWORD); // [rsp+20F8h] [rbp+20D8h]
v3 = &v15;
for ( i = 0x6BEi64; i; --i )
{
*v3 = 0xCCCCCCCC;
v3 += 4;
}
v35 = 0;
thread_what(&unk_7FF7462B60F2);
t = Reverse(Block, &unk_7FF7462AE168, 5i64); // Reverse 通过对a2参数取反来隐藏一些数据 动调就可以无视了)
v37 = t;
printf(std::cout, t);
free_(Block); // 似乎是始放内存 栈啊什么的函数 无影响
sub_7FF7462917DF(input);
get(std::cin, input);
t = strlen_0(input); // 多次动调发现t和input长度一样
CopyInput = strcopy(input); // 复制字符串
base_input = Base64encode(CopyInput, t); // 通过base64码表 和动调知道是base64
copy_base_to_input(input, base_input); // 就是把base后的字符串放进input里面
v23 = v22;
t = memcpppyy(v22, input); // 这里t是**input
E2(Src, t);
v25 = malloc_0(0x7E0ui64);
if ( v25 )
{
v27 = &v26;
t = Reverse(v28, dword_7FF7462AF300, 360i64);
v37 = t;
v35 |= 1u;
key = strcopy(t);
v8 = str2int(v27, key); // 如key为'123456789'
// 经过函数转换为[6789,1234](十进制)存到v26中
v38 = sub_7FF746291433(v25, v8); // 一个虚表函数 但这里指向的不是真正的虚表
}
else
{
v38 = 0i64;
}
v24 = v38;
v18 = v38;
if ( (v35 & 1) != 0 )
{
v35 &= ~1u;
free_(v28);
}
sub_7FF746291226(v18); // 触发一个异常处理 替换了虚表函数
v30 = malloc_0(0x7D4ui64);
if ( v30 )
t = memcpy_0(v30, Src, v9);
else
t = 0i64;
v29 = t;
v19 = t;
(**v18)(v18, t); // 触发虚表函数
sub(v19, v20, (v18 + 1));
if ( check(v20) ) // check函数 要求a[0]为0 a[500]为1
{
t = Reverse(v31, &unk_7FF7462AF910, 4i64);
v37 = t;
LODWORD(v10) = printf(std::cout, t);
std::ostream::operator<<(v10, sub_7FF74629105A);
free_(v31);
}
else
{
t = Reverse(v32, &unk_7FF7462AF920, 5i64);
v37 = t;
LODWORD(v11) = printf(std::cout, t);
std::ostream::operator<<(v11, sub_7FF74629105A);
free_(v32);
}
v33 = v18;
if ( v18 )
t = sub_7FF746291316(v33, 1i64);
else
t = 0i64;
v34 = 0;
free_(input);
v12 = v34;
sub_7FF74629169F(v14, &unk_7FF7462A93D0);
return v12;
}
在每个str2int函数前断下 并把a2参数拿出来
(在这一步看很多师傅的wp选择 这个函数执行完毕后拿出a1参数再解)))
但其实解完就是这个数)))
keys = [64584540291872516627894939590684951703479643371381420434698676192916126802789388,
11783410410469738048283152171898507679537812634841032055361622989575562121323526,
55440851777679184418972581091796582321001517732868509947716453414109025036506793,
17867047589171477574847737912328753108849304549280205992204587760361310317983607,
7537302706582391238853817483600228733479333152488218477840149847189049516952787,
80793226935699295824618519685638809874579343342564712419235587177713165502121664,
14385283226689171523445844388769467232023411467394422980403729848631619308579599,
55079029772840138145785005601340325789675668817561045403173659223377346727295749,
71119332457202863671922045224905384620742912949065190274173724688764272313900465,
57705573952449699620072104055030025886984180500734382250587152417040141679598894]
下方部分函数简单分析发现就是对数组的加减乘除
这个虚表函数的逻辑即为
t+=key1
t*=key2
t-=key3
t+=key4
t*=key5
t-=key6
t+=key7
t-=key8
t+=key9
t-=key10
回到main函数 还有一个sub 直接dump拿出a3就是最后一个key
(
实际上这个key早就在这被处理了)))) 所以dump上面的字符串也可 还省一步解密)
key11=834572051814337070469744559761199605121805728622619480039894407167152612470842477813941120780374570205930952883661000998715107231695919001238818879944773516507366865633886966330912156402063735306303966193481658066437563587241718036562480496368592194719092339868512773222711600878782903109949779245500098606570248830570792028831133949440164219842871034275938433
keys = [0,
64584540291872516627894939590684951703479643371381420434698676192916126802789388,
11783410410469738048283152171898507679537812634841032055361622989575562121323526,
55440851777679184418972581091796582321001517732868509947716453414109025036506793,
17867047589171477574847737912328753108849304549280205992204587760361310317983607,
7537302706582391238853817483600228733479333152488218477840149847189049516952787,
80793226935699295824618519685638809874579343342564712419235587177713165502121664,
14385283226689171523445844388769467232023411467394422980403729848631619308579599,
55079029772840138145785005601340325789675668817561045403173659223377346727295749,
71119332457202863671922045224905384620742912949065190274173724688764272313900465,
57705573952449699620072104055030025886984180500734382250587152417040141679598894]
key11 = 834572051814337070469744559761199605121805728622619480039894407167152612470842477813941120780374570205930952883661000998715107231695919001238818879944773516507366865633886966330912156402063735306303966193481658066437563587241718036562480496368592194719092339868512773222711600878782903109949779245500098606570248830570792028831133949440164219842871034275938433
t = 0
t += key11
t += keys[10]
t -= keys[9]
t += keys[8]
t -= keys[7]
t += keys[6]
t //= keys[5]
t -= keys[4]
t += keys[3]
t //= keys[2]
t -= keys[1]
print(t)
解出这个虚表函数加密前的值
最后观测main函数中的E2函数 发现是用128来存储ascii码的
所以
import base64
out = 9396732749587903779982032223783704408363403648379534949744333952324860296757610447170361685759182747971458838316594664099958866750753425972637962724687099230310084662432348188439040968731452657169050321
out = '0' + bin(out)[2:]
flag = b""
for i in range(len(out), 0, -7):
flag += int(out[i-7:i],2).to_bytes(1,'big')
print(base64.b64decode(flag))
ACTF{dr0pp3r_1s_v3ry_int3r3st1ng_1d7a90a63039831c7fcaa53b766d5b2d!!!!!}