当前位置:首页 > 奇闻趣事

ctf加载程序 CTF中32位程序调用64位代码的逆向方法

背景

在CTF,逆势变得越来越多变。也有过32位程序调用64位代码,一般静态分析和动态调试方法都会失败的情况,让人很大。今天,我们将通过两个案例来学习如何处理这种情况。

情形

这两个案例包括一个windows程序和一个linux ELF程序,这两个程序只覆盖了两个常见的平台(摘录代码:nxwx)

father and son (ELF),来源于2018年护网杯CTFGWoC (Windows),来源于2018年CNCERT CTF

基础知识

x64系统中的进程有两种工作模式:32位和64位。这两种工作模式的区别在于CS寄存器。32位模式下,CS = 0x23;在64位模式下,CS = 0x33。;

这两种工作模式可以切换,通常通过retf指令,一条retf指令相当于下面两条汇编指令

pop ip

pop cs

如果此时堆栈中有0x33,则将0x33弹出到CS寄存器中,实现32位程序切换到64位代码的过程。所以retf是识别32位程序调用64位代码的重要标志。

案例1:父亲和儿子

二进制文件父亲来自一个流量包的内容(这不是本文的重点),是一个32位的ELF程序

$文件父亲

父亲:ELF 32位LSB可执行,Intel 80386,版本1 (SYSV),动态链接,解释器/lib/ld-linux.so.2,对于GNU/Linux 2.6.32,BuildID[sha1]= 4351 DC 8 FDE 1b d 3404207 e 1540 b 84 E3 c 577 c 81521,剥离

程序分析

核心代码如下

int sub_8048527()

{

签名的int retaddr// [esp+2Ch] [ebp+4h]

int retaddr_4签名;// [esp+30h] [ebp+8h]

if ( mmap((void *)0x1337000,0x3000u,7,50,0,0)!= (void *)0x1337000)

{

puts("对不起");

退出(0);

}

if ( mmap((void *)0xDEAD000,0x3000u,7,50,0,0)!= (void *)0xDEAD000)

{

puts("对不起");

退出(0);

}

memcpy((void *)0xdade 000 & amp;unk_804A060,0x 834 u);

sub_80484EB(0xDEAD000,0x834,0x 33);// sub_80484EB(内容、长度、异或值)

retaddr = 0xDEAD000

retaddr _ 4 = 0x33

return MEMORY[0xdf 7d 000]();

}

用nmap创建两段RWX内存,将0x804A060的内容复制到其中一个RWX内存的0xDEAD000,用sub_80484EB函数异或恢复代码。

IDA的最后一部分没有被识别,所以用retf跳转到0xDEAD000来执行程序集。

。文本:08048629 C7 00 00 D0 EA 0D mov dword ptr[eax],0DEAD000h

。正文:0804862F 8D 45 E4丽艾[ebp+var_1C]

。文本:08048632 83 C0 24 add eax,24h

。正文:08048635 89 45 E0 mov [ebp+var_20],eax

。正文:08048638 8B 45 E0 mov eax,[ebp+var_20]

。正文:0804863 B C7 00 33 00 00 00 mov dword ptr[eax],33h

。文本:08048641 C9离开

。正文:08048642 CB retf

看到retf后,堆栈中有0x33,符合32位程序调用64位代码的模式。

执行分析

使用通用反向工具gdb在0x08048642处设置断点

gdb。/父亲

pwndbg>。b *0x08048642

0x8048642处的断点1

pwndbg>。展示建筑

目标体系结构是自动设置的(目前为i386)

pwndbg>。r

断点触发后,用ni单步执行指令执行下一步。可以看到指令已经跳转到0xDEAD000空,CS寄存器的值从0x23变为0x33,进入了空之间的64位代码。

但是,代码内容此时无法显示64位程序集

这时,如果继续用ni单步执行指令,会看到汇编指令不是一条一条执行的,而是分步执行的。这是因为gdb认为这段代码是32位的,而不是64位的,即使使用set architecture i386:x86-64命令,也会提示错误。

我也尝试过以下调试方法,都失败了。

IDA+linux_server(IDA32位版本)进行调试,效果同gdb,无法识别64位汇编代码,可以单步执行,汇编指令也是几步一跳。IDA64+linux_server64(IDA64位版本),程序无法引导起来。

那么如何动态调试呢?

动态调试

为了正确执行64位指令,可以采用gdbserver+IDA64的调试模式。

Gdbserver启动程序并绑定到端口1234(使用本机ip而不是冒号前的ip)

gdbserver :1234。/父亲

用IDA64打开程序。此时用F5无法查看伪代码,但可以看到IDA64识别32位程序,汇编可以正常显示。

在0x8048642的retf处设置断点,并设置连接gdbserver的参数(如图)

单击绿色三角形按钮开始调试。F9运行一次后,到达断点。

再次按F7键输入64位代码。此时,EIP显示0xDEAD000已输入,但装配窗口中没有提示。即使使用g跳转到地址0xDEAD000也会提示错误。

这是因为当IDA和gdbserver连接时,内存没有及时刷新。您可以在“调试器”菜单中打开“手动内存区域”菜单项,然后右键单击“插入”以创建新的内存区域(每次启动调试时都应重复此操作)。

存储区的起始地址为0xDEAD000,结束地址可以默认设置。注意选择64位段。

然后用g指令跳转到内存0xDEAD000,此时显示二进制数据。

按C识别为汇编指令,IDA调试器可以正确识别64位汇编,按F8单步执行不需要几个步骤,可以正常调试。

注1: GDB服务器在一次调试后可能不会再次连接,因此需要终止并重新启动。

注2:有些ELF程序可能不需要增加手动内存区域的内存区域,可以通过IDA的编辑->段->来访问;更改段属性将内存修改为64位代码

静态分析

与动态调试方法相比,还需要静态分析方法的配合才能提高CTF的逆向效率。

这种情况下,采用异或混淆。因为混淆并不复杂,所以可以静态转储或动态恢复。本文在动态运行retf指令时,使用脚本Dump来存储内存。

静态主(空)

{

auto fp,begin,end,dexbyte

FP = fopen(" C:父64.mem "," WB ");

begin = 0xDEAD000

end = 0xDEB0000

for(dex byte = begin;dexbyte <。结束;dexbyte ++)

fputc(Byte(dexbyte),FP);

}

在“文件”菜单的“命令”菜单项中,选择IDC脚本,输入上述内容,然后单击“运行”按钮,将内存从0xDEAD000导出到c盘上的父64.mem文件中

将父64.mem拖入IDA64进行静态分析,由于ELF头缺失,IDA64会询问选择哪种格式,在此选择64位模式分析代码。

此时代码基址为0x0,所以编辑->:Segments->;Rebase Segment重新定义基址,并将其设置为0xDEAD000,以便动态调试和静态调试期间的程序集地址相同。

然后就可以高高兴兴的用F5生成C语言代码了。

反向裂化

因为本文重点是如何识别和分析32位程序调用的64位代码,所以案例的算法反长度会比较简短,有兴趣的朋友可以自行研究。

主进程sub_DEAD44B接收用户输入输出结果,判断输入格式是否为hwbctf{…}。

_ _ int 64 _ _ fast call sub _ DEAD 44B(_ int 64 a1)

{

int v1// eax

int v2// eax

char v4// [rsp+0h] [rbp-70h]

char v5// [rsp+10h] [rbp-60h]

char V6[16];// [rsp+20h] [rbp-50h]

char V7[19];// [rsp+30h] [rbp-40h]

char V8[13];// [rsp+50h] [rbp-20h]

int v9// [rsp+6Ch] [rbp-4h]

V8[0]= 123;

...

V8[12]= 18;

sub_DEAD011(0x12,v8,13);/// v[i] ^= 0x12恢复输入_代码:

sub _ DEAD 0D 7(V8);// strlen

sub _ dead 073();//写

sub_DEAD09C(22,v7,0);

sub _ DEAD 05B();//读取

if ( sub_DEAD0D7(v7))>18) //长度大于0x12

{

v9 = 0;

v1 = sub _ DEAD 105(V7);//检查输入[:6] == 'hwbctf '

v9+= v1;

v9 += v7[6]!= '{';//检查{}

v9 += v7[18]!= '}';

v2 = sub _ DEAD 16F(& amp;V7[7]);//解方程得到1 ' n0t 4n 5

v9+= v2;

if ( v9)

{

sub _ dead 011(0x 89 & amp;v4,12);//这里有赋值,f5不是从ida出来的

sub _ DEAD 0D 7(& amp;v4);

sub _ dead 073();

}

其他

{

sub _ dead 011(0xf 1 & amp;v5,11);

sub _ DEAD 0D 7(& amp;V5);

sub _ dead 073();

}

}

其他

{

V6[0]= 105;

...

V6[15]= 5;

sub_DEAD011(5,v6,16);//长度错误!!!

sub _ DEAD 0D 7(V6);

sub _ dead 073();

}

return sub _ dead 08B();

}

sub_DEAD16F函数有13个方程来判断输入

_ _ int 64 _ _ user call sub _ DEAD 16F @ & lt;rax>。(_ BYTE * a1 @ & ltrdi>。)

{

int v1// ST0C_4

v1 = ((a1[4] ^ a1[2] ^ a1[6])!= 119)

+ ((a1[1] ^ *a1 ^ a1[3])!= 54)

+ (a1[10] + a1[3]!= 85)

+ (a1[2] + a1[9]!= 219)

+ (a1[4] + a1[5]!= 158)

+ (a1[2] + a1[1] + a1[5]!= 196)

+ (a1[8] + a1[7] + a1[9]!= 194)

+ (a1[5] + a1[3] + a1[9]!= 190)

+ (a1[6] + a1[2] + a1[8]!= 277)

+ (a1[10] + a1[1] + a1[7]!= 124);

return (a1[5]!= 48) + (a1[10]!= 53) + ((a1[7] ^ a1[6] ^ a1[8])!= 96)+v1;

}

Z3能解决的标志是hwbctf{1'm n0t 4n5}

案例2: GWOC

GWoC是一个32位Windows程序

原程序中有很多花指令和反调试部分,0x90没有。附件提供了补丁代码

程序分析

将补丁程序拖入IDA32位,主要流程如下

int __cdecl main(int argc,const char **argv,const char **envp)

{

const char * v3// ST14_4

HANDLE v4// eax

HANDLE v5// eax

HANDLE v6// eax

HANDLE v7// eax

const char * v8// eax

const char * v9// edx

const char * v10// edx

const char * v11// edx

_ DWORD * v13// [esp+24h] [ebp-40h]

_ DWORD * v14// [esp+28h] [ebp-3Ch]

_ DWORD * v15// [esp+2Ch] [ebp-38h]

_ DWORD * lpParameter// [esp+30h] [ebp-34h]

BOOL Wow64Process// [esp+3Ch] [ebp-28h]

DWORD ThreadId// [esp+40h] [ebp-24h]

int v19// [esp+44h] [ebp-20h]

int v20// [esp+48h] [ebp-1Ch]

int v21// [esp+4Ch] [ebp-18h]

HANDLE Handles// [esp+50h] [ebp-14h]

HANDLE v23// [esp+54h] [ebp-10h]

HANDLE v24// [esp+58h] [ebp-Ch]

HANDLE v25// [esp+5Ch] [ebp-8h]

if(argc & lt;2) //程序判断是否有命令行参数

{

sub_C725E0("错误缺少参数!n ");

v3 = * argv

sub _ c 725 E0(" % s inputn ");

退出(0);

}

wow 64 process = 0;

iswow 64 process((HANDLE)0xfffffff & amp;wow 64 process);

if(!Wow64Process) //检查是否支持64位程序

{

sub_C725E0(“不支持系统!在64位Windows OSn上运行我");

退出(0);

}

if ( strlen(argv[1])!= 32 )

sub _ c 721 c 0();

sub _ c 721 E0();

v4 = GetProcessHeap();

lpParameter = HeapAlloc(v4,8u,0x 18u);

V5 = GetProcessHeap();

v15 = HeapAlloc(v5,8u,0x 18u);

V6 = GetProcessHeap();

v14 = HeapAlloc(v6,8u,0x 18u);

v 7 = GetProcessHeap();

v13 = HeapAlloc(v7,8u,0x 18u);

* lpParameter = 0xFAB//初始化多线程参数1

lpParameter[1]= 0;

v 8 = argv[1];

lpParameter[2]= *((_ DWORD *)V8+2);

lpParameter[3]= *((_ DWORD *)V8+3);

Handles = CreateThread(0,0,(LPthread _ START _ ROUTINE)START address,lpParameter,0,& ampThreadId);//开始多线程1

* v14 = 0xF0F0F0F0//初始化多线程参数2

v14[1]= 0xf0f 0f 0;

v 9 = argv[1];

v14[2]= *((_ DWORD *)v9+4);

v14[3]= *((_ DWORD *)v9+5);

v23 = CreateThread(0,0,(LPthread _ START _ ROUTINE)START address,v14,0,(LPDWORD)& amp;v19);//开始多线程2

* v13 = 0xF06B3430//初始化多线程参数3

V13[1]= 0x136 d 7374;

v10 = argv[1];

V13[2]= *(_ DWORD *)v10;

V13[3]= *((_ DWORD *)v10+1);

v24 = CreateThread(0,0,(LPthread _ START _ ROUTINE)START address,v13,0,(LPDWORD)& amp;v 20);//开始多线程3

* v15 = 0x43434343//初始化多线程参数4

v 15[1]= 0x 434343;

v11 = argv[1];

v15[2]= *((_ DWORD *)v11+6);

v15[3]= *((_ DWORD *)v11+7);

v25 = CreateThread(0,0,(LPthread _ START _ ROUTINE)START address,v15,0,(LPDWORD)& amp;v 21);//开始多线程4

waitformmultiplieobjects(4u,& amp句柄,1,0x ffffffff);//线程通过

if ( lpParameter[4]!= 0x7E352B1F || lpParameter[5]!= 0x9B04D2D3) //判断线程1的结果

sub _ c 721 c 0();

if ( v15[4]!= 0x4D95D40C || v15[5]!= 0xE14496F7) //判断线程4的结果

sub _ c 721 c 0();

if ( v14[4]!= 0x2E4CB743 || v14[5]!= 0xA51E28EE) //判断线程3的结果

sub _ c 721 c 0();

if ( v13[4]!= 1434694267 || v13[5]!= 1991371616) //判断线程2的结果

sub _ c 721 c 0();

sub _ c 71320();

返回0;

}

程序把32个字符的输入放入4个线程参数中,启动4个线程,每个线程调用同一个函数,但是参数不同。

。文本:00C71330DWORD _ stdcall _ start address(LPVOID LPthreadParameter)

。文本:00C71330开始地址进程远;数据XREF: _main+191↓o

。文本:00C71330_main+1EC↓o...

。文本:00C71330

。文本:00C71330 var_18 = dword ptr -18h

。文本:00C71330 var_4 = dword ptr -4

。文本:00c 71330 LPthreadParAmeter = dword ptr 0Ch

。文本:00C71330

。文本:00C71330推ebp

。文本:00C71331 mov ebp,esp

。文本:00C71333推送ecx

。文本:00C71334推ebx

。文本:00C71335按压esi

。文本:00C71336推送edi

。文本:00C71337 mov [ebp+var_4],0

。文本:00C7133E mov ecx,[ebp+8]

。文本:00C71341按33h

。文本:00C71343呼叫$+5

。文本:00C71348添加dword ptr [esp],5

。文本:00C7134C retf

。文本:00C7134C StartAddress endpsp-分析失败

。文本:00C7134C

。文本:00C7134D呼叫loc_C72067

...

。文本:00C72067 dec eax

。文本:00C72068 mov [esp+8],ecx

。文本:00C7206C dec eax

。文本:00C7206D sub esp,28小时

。文本:00C72070 dec eax

。正文:00C72071 mov eax,[esp+30h]

。文本:00C72075 dec eax

。文本:00C72076 mov ecx,97418529h

这里我们看到熟悉的push 33h和retf,这是输入64位代码的特点。输入了loc_C72067地址,无法正确识别64位汇编指令。

静态分析

因为这种情况下的代码可以静态的转储出来,所以我们先做静态分析。

使用案例1中的Dump方法,拖到IDA64分析中,可以恢复代码,但是会出现一些内存引用错误,这是因为缺少上下文内存。

虽然也可以分析,但在这种情况下,我们可以尝试用更优雅的方式。

在010Editor中,使用PE模板打开exe文件,偏移量约为0x118,将32位的0x10b修改为64位的0x20b。

然后放入IDA64分析,Rebase Segment为0。再看看原来的loc_C72067(重换基后的0x2067)。这时F5也可以识别一些功能,可以按照sub_1C57和sub_1437的分析。

签名_ _ int 64 _ _ fast call sub _ 2067(_ QWORD * a1)

{

签名_ _ int64 v1// rax

无符号_ _ int64 v2// rax

signed __int64结果;// rax

_ QWORD * v4// [rsp+30h] [rbp+8h]

v4 = a1

v1 = * a1 ^ 0x 1234567897418529 i64;

if ( v1 == 0xE2C4A68867B175D9i64)

a1[2]= sub _ 1C 57(a1[1]);

if ( *v4 == 0xFABi64)

v4[2]= sub _ 1437(v4[1]);

v2 = * v4 % 0x11111111111111ui64

if ( v2 == 0x10101010101010i64)

v4[2]= sub _ 1C 37(v4[1]);

结果= * v4 & amp0x111000111000111i64

if(结果== 0x101000010000010i64)

{

result = sub _ 1F 77(v4[1]);

v4[2] =结果;

}

返回结果;

}

四个线程都调用这个函数,但是由于输入参数不同,会选择不同的函数调用。

例如,*lpParameter = 0xFAB在这里对应* v4 = = 0xFABi64的判断,所以这部分输入调用sub_1437函数,v4[1]是实际输入字符串中的第8到第15个字符,即input[8:16]。

进入分析sub_1437,发现是流加密,根据F5的结果反向比较复杂,还需要根据动态运行结果反向。

sbox[0] =...

...

sbox[254]= 0xf 9u;

sbox[255]= 0xf 8u;

LOBYTE(V7)= 0;

memset(& amp;v7 + 1,0,sizeof(_ int 64));

for(I = 0;i <。8;++i)

*(& amp;v 7+I)= *(amp;v 9+I);

v3 = v7

for(j = 0;j <。256;++j)

{

V8 = sbox[(sbox[j %-8+248]+v3)];

v1 = *(& amp;v 7+(j+1)% 0xffffff8)+v 8;

v3 =(v1 & gt;>。7)| 2 * v1;

*(& amp;V7+(j+1)%-8)= v3;

}

返回V7;

动态调试

在WIndows下,执行retf指令后,IDA32、IDA64和Ollydbg调试器无法正常运行。在master的指导下,windbg被用作动态调试工具。

用Windbg 64位打开目标程序文件->打开可执行文件,注意输入命令行参数

在视图菜单中打开反汇编、寄存器、内存和命令窗口。布局如下

首先,我们需要在retf处设置断点。怎么才能设置断点?在IDA中,rebase段为0后,可以看到retf的地址是0x134c,所以在windbg的反汇编窗口输入GWoC+0x134c,确定也是retf,按F9设置断点。

按F5执行到断点,然后按F8进入执行。此时,CS寄存器可以看到它已变为0x33,并进入64位代码块

这时候给我们要调试的sub_1437函数添加断点,在反汇编窗口输入GWoC+0x1437,按F9添加断点,然后F5运行到断点,就可以高高兴兴的开始调试了。

IDA还有链接Windbg的功能。而本文采用的IDA 7.0版本,未能连接windbg进行调试。IDA只能用于静态分析,Windbg用于动态调试,双方逆向组合。

反向裂化

以输入1234567890123456789012479012479012479012为例

算法1

输入参数:0xfab输入内容:input[8:16] = “90123456”调用函数:sub_1437算法内容:流式加密,根据结果逆推即可逆向结果:F

算法2

输入参数:0xf0f0f0输入内容:input[16:24] = “78901234”调用函数:sub_1C57算法内容:低4位和高4位分开运算,多次位移和异或运算,可用暴力破解逆向结果:Cq!9x9zc

算法3

输入参数:0x136D7374F06B3430输入内容:input[:8] = “12345678”调用函数:sub_1F77算法内容:低4位和高4位分开进行快速幂取模操作,就是RSA,分解因数解密RSA即可逆向结果:flag{RpC

算法4

输入参数:0x43434343434343输入内容:input[24:] = “56789012”调用函数:sub_1C37算法内容:输入异或0x9C70A3C478EF826A,根据结果异或即可逆向结果:fVz5354}

所有字符串拼接在一起得到flag{RpCF!9x9zcfVz5354}

总结

本文通过windows和linux的案例,梳理了32位程序调用64位代码的识别方法、静态分析和动态调试技巧。

识别方法

retf是切换32位和64位的关键指令。retf前有push 0x33(33h)类似的指令。 push 33h

添加dword ptr [esp],5

返回指令

或者

mov dword ptr [eax],33h

离开

返回指令

retf后CS寄存器从0x23变为0x33。程序中可能有进行支持64位的检查,如GWoC。当一块可执行的内存,调试时无法识别汇编或者几步一跳时,有可能在是执行64位的代码。32位代码调用函数的方式和64位代码有差异,32位程序大多通过入栈方式传参,64位程序一般用寄存器传参。32位和64位的syscall的含义和参数有所不同。

静态分析

修改PE/ELF头位64位,让IDA64识别其中64位的部分代码。静态/动态dump出内存中的64位代码片段,拖入IDA64分析代码。有时候可以通过IDA中Change Segment Attributes 设置为64位,进行汇编分析。使用Rebase Segment对齐基地址方便进行静结合分析

动态调试

Linux ELF程序可使用gdbserver和IDA64的组合进行调试。Windows程序使用Windbg进行动态调试,使用IDA64进行静态分析,动静结合逆向。

1.《ctf加载程序 CTF中32位程序调用64位代码的逆向方法》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。

2.《ctf加载程序 CTF中32位程序调用64位代码的逆向方法》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。

3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/guonei/1192252.html

上一篇

欧冠小组抽签揭晓 具体分组情况是什么?

下一篇

杨紫总裁造型 杨紫总裁什么样什么剧里的造型(图)

dictate 微软发力语音输入应用 Dictate支持语音指令和翻译

  • dictate 微软发力语音输入应用 Dictate支持语音指令和翻译
  • dictate 微软发力语音输入应用 Dictate支持语音指令和翻译
  • dictate 微软发力语音输入应用 Dictate支持语音指令和翻译

华为手机正在运行的程序怎么关闭 为什么总说华为手机越用越卡?那是一些这些激活后的设置你一个都没关!

  • 华为手机正在运行的程序怎么关闭 为什么总说华为手机越用越卡?那是一些这些激活后的设置你一个都没关!
  • 华为手机正在运行的程序怎么关闭 为什么总说华为手机越用越卡?那是一些这些激活后的设置你一个都没关!
  • 华为手机正在运行的程序怎么关闭 为什么总说华为手机越用越卡?那是一些这些激活后的设置你一个都没关!

数字广大 广大经典诵读小程序正式上线!

  • 数字广大 广大经典诵读小程序正式上线!
  • 数字广大 广大经典诵读小程序正式上线!
  • 数字广大 广大经典诵读小程序正式上线!
湖北招生考试杂志 2018年《湖北招生考试》大厚本实用指南(附一批院校代码+页码表)

湖北招生考试杂志 2018年《湖北招生考试》大厚本实用指南(附一批院校代码+页码表)

很期待,一直等到6月20日,每年有一次高考的家长要志愿填“大厚书”! 注意: 1.“大厚本”是指每年志愿填报高考必备的一套参考书,主要包括各批次高校的招生计划,里面有志愿填报必须使用的院校和专业的代码,这是必须的! 2.湖北高考考生填写《招生考...

0x000007 蓝屏代码0x000007b如何解决

  • 0x000007 蓝屏代码0x000007b如何解决
  • 0x000007 蓝屏代码0x000007b如何解决
  • 0x000007 蓝屏代码0x000007b如何解决

蓝屏代码0x000007b 蓝屏代码0x000007b如何解决

  • 蓝屏代码0x000007b 蓝屏代码0x000007b如何解决
  • 蓝屏代码0x000007b 蓝屏代码0x000007b如何解决
  • 蓝屏代码0x000007b 蓝屏代码0x000007b如何解决
群接龙 群接龙小程序上线6个月,用户突破1000万,日交易额破100万

群接龙 群接龙小程序上线6个月,用户突破1000万,日交易额破100万

群杰龙小程序是电子商务、商家、用户提供微信社区营销的免费工具。自2018年上线以来,日访问量突破10万,营销活动接近13万,用户访问量突破1000万。  1.报名接龙,适合各种活动类型报名,自动分类统计,简单高效; 2、团购接龙,专门满足团购活...

微盛 微盛小程序可视化操作手册

微盛 微盛小程序可视化操作手册

自从微信小程序推出以来,开发小程序的浪潮从未停止过。对于大多数商家来说,他们没有专业发展能力。此时使用第三方小程序开发公司的可视化拖放平台已经成为一种经济实用的选择,但是具体的操作流程并不了解。下面的小系列以微生小程序为例介绍如何操作可视化。...