首先,我们的ShellCode代码需要自定位,因为我们的代码并不是一个完整的EXE可执行程序,他没有导入表无法定位到当前系统中每个函数的虚拟地址,所以我们直接获取到Kernel32.dll的基地址,里面的GetProcAddr这个函数,获取的方式有很多,第一种是暴力搜索,第二种通过遍历进程的TEB结构来实现,我们使用第二种方式尝试,一旦获取到该函数,就可以动态的调用任何想要的函数了。

获取DLL模块基地址

首先打开WinDbg加载符号链接文件,输入 srv*https://www.lyshark.cn/symbols

1.首先FS寄存器里面存储的是TEB结构,TEB是线程环境快,里面的PET。

TEB的偏移位置30h处,存放的是PEB线程环境快。

接着解析一下 dt _peb 0026b000 里面的0C字段是LDR,一个指向_PEB_LDR_DATA的结构数组。

PEB_LDR_DATA 结构体偏移位置为 0x1c 的地方存放着指向模块初始化链表的头指针 InInitializationOrderModuleList,该指针指向了一个双向链表。

模块初始化链表 InInitializationOrderModuleList 中按顺序存放着PE装入运行时初始化模块的信息,第一个链表节点是 ntdll.dll,第二个链表结点就是kernel32.dll可以先看看 InInitializationOrderModuleList 中的内容。

上图中的 004e3278 保存的是第一个链表节点的指针,通过dd 004e3278解析这个结点,可发现如下地址0x773a0000就是ntdll.dll的基地址,而 004e3b20 则是下一个模块的指针

继续跟随 004e3b20 跟进后的76a90000就是kernel32.dll的基地址,而下一个地址的指针则是004e3760以此类推来遍历。

最后我们通过!peb命令来验证一下,如下会发现第一个对上了,这里的kerlel32.dll其实是kernelbase.dll 这个dll是转向dll中转到kernel32.dll中,64位系统特有的。

通过上方的调试我们可得到公式,接着通过编写一段汇编代码来实现自动的遍历出 kernel32.dl 的基址。

include windows.inc
include kernel32.inc
includelib kerbcli.lib
assume fs:nothing .code
main PROC
xor eax,eax
xor edx,edx
mov eax,fs:[30h] ; 得到PEB结构地址
mov eax,[eax + 0ch] ; 得到PEB_LDR_DATA结构地址
mov esi,[eax + 1ch] ; 得到 InInitializationOrderModuleList
lodsd ; 得到KERNEL32.DLL所在LDR_MODULE结构的
mov eax,[eax] ; Windows 7 以上要将这里打开
mov edx,[eax + 8h] ; 得到BaseAddress,既Kernel32.dll基址
ret
main ENDP
END main

通过使用C语言也可以实现拿到Kernel32的基地址.

#include <windows.h>
#include <stdio.h> int main(int argc, char * argv[])
{
DWORD *PEB = NULL;
DWORD *Ldr = NULL;
DWORD *Init = NULL;
DWORD *Kernel32 = NULL; __asm
{
mov eax, fs:[0x30]
mov PEB,eax
}
printf("得到PEB指针 = %x \n", PEB); Ldr = *(DWORD **)((unsigned char *)PEB + 0x0c);
printf("得到LDR结构指针 = %x \n", Ldr); Init = *(DWORD **)((unsigned char *)Ldr + 0x1c);
printf("得到InInitializationOrderModuleList结构指针 = %x \n", Init); Kernel32 = *(DWORD **)((unsigned char *)Init + 0x08);
printf("得到Kernel32的基地址 = %x \n", Kernel32); system("pause");
return 0;
}

获得镜像基地址: 我们来扩展一个知识点,首先我们这次想要获得镜像基地址,如何解析结构?

首先镜像基地址,在PEB结构中,我们先来获取到其偏移地址。

此时我们知道TEB结构中 指向 PEB,则 0026b000

接着来解析TEB结构,只需要执行 dt _PEB 0026b000 即可得到该地址。

直接汇编实现,也非常简单,如下。

枚举进程模块

1.我们来拓展一个知识点,通过PEB/TEB找到自身进程的所有载入模块数据,首先获取 TEB,也就是线程环境块。在编程的时候,TEB 始终保存在寄存器 FS 中。

先来得到LDR结构:Ldr = *( ( DWORD ** )( ( unsigned char * )PEB + 0x0c ) );

先找到TEB

然后再找到PEB结构 偏移为 0x30 从该命令的输出可以看出,PEB 结构体的地址位于 TEB 结构体偏移0x30 的位置

找到了PEB也就可以找到_PEB_LDR_DATA结构 其位于 PEB 偏移 0c的位置上。

Ldr = *( ( DWORD ** )( ( unsigned char * )PEB + 0x0c ) );

从输出结果可以看出,LDR 在 PEB 结构体偏移的 0x0C 处,该地址保存的地址是 0x77bf0c40 通过该地址来解析 LDR 结构体。

WinDBG 输出如下内容:

Flink = *( ( DWORD ** )( ( unsigned char * )Ldr + 0x14 ) );

位于LDR偏移14的位置就是InLoadOrderModuleList其所指向的就是模块名称表。

现在来手动遍历第一条链表,输入命令 0x4e3370

链表偏移 0x18 的位置是模块的映射地址 ImageBase;

链表偏移 0x28 的位置是模块的路径及名称的地址;

链表偏移 0x30 的位置是模块名称的地址。

的确是模块的名称,遍历下一条链表的信息,004e3268 保存着下一个链表结构,依次遍历就是了。

我们找到下一个链表位置,然后同样的方法来验证一下。

没错了吧,下一个是 ntdll.dll

这个链表结构其实访问 InMemoryOrderModuleList 也可以得到,这两个都指向同一片区域 例如第二个 0x4e3378

解析一下看看 0x4e3378 一致。

第二个是ntdll.dll

上面介绍的结构,是微软保留结构,只能从网上找到一个结构定义,然后自行看着解析就好了。

typedef struct _LDR_DATA_TABLE_ENTRY {
PVOID Reserved1[2];
LIST_ENTRY InMemoryOrderLinks;
PVOID Reserved2[2];
PVOID DllBase;
PVOID EntryPoint;
PVOID Reserved3;
UNICODE_STRING FullDllName;
BYTE Reserved4[8];
PVOID Reserved5[3];
union {
ULONG CheckSum;
PVOID Reserved6;
};
ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

Flink = *((DWORD **)((unsigned char *)Ldr + 0x14));

枚举模块的方法就是:得到TEB -> PEB ->LDR ->遍历。

#include <Windows.h>
#include <stdio.h> int main(int argc, char* argv[])
{
DWORD *PEB = NULL, *Ldr = NULL, *Flink = NULL, *p = NULL;
DWORD *BaseAddress = NULL, *FullDllName = NULL,*Ba = NULL; __asm
{
mov eax, fs:[0x30]
mov PEB, eax
} Ldr = *((DWORD **)((unsigned char *)PEB + 0x0c));
Flink = *((DWORD **)((unsigned char *)Ldr + 0x14));
p = Flink; p = *((DWORD **)p);
while (Flink != p)
{
BaseAddress = *((DWORD **)((unsigned char *)p + 0x10));
FullDllName = *((DWORD **)((unsigned char *)p + 0x20)); if (BaseAddress == 0)
break; printf("镜像基址 = %08x \n --> 模块路径 = %S \n", BaseAddress, (unsigned char *)FullDllName); p = *((DWORD **)p);
}
system("pause");
return 0;
}

上方的 0x10 与 0x20 对应的就是地址结构与路径名称。

BaseAddress = *((DWORD **)((unsigned char *)p + 0x10));

FullDllName = *((DWORD **)((unsigned char *)p + 0x20));

将0x10改为 0x24

或改为 0x28

换成0x18 和 0x28 运行看看,获取到的就是文件名称。

BaseAddress = *((DWORD **)((unsigned char *)p + 0x18));

FullDllName = *((DWORD **)((unsigned char *)p + 0x28));

对照解析结果,观察,就明白了。

进程模块隐藏

一维指针的骚操作:

#include <stdio.h>
#include <Windows.h> int main(int argc, char * argv[])
{
DWORD *PEB = (DWORD *)0x401000;
DWORD *LDR;
DWORD *BaseAddr = NULL; printf("PEB = %x \t &PEB = %x \n", PEB,&PEB);
LDR = (DWORD *)&PEB;
printf("PEB = %x \t LDR = %x PEB Value = %x \n", LDR,&LDR,*LDR);
printf("-------------------------------------------------------------- \n"); printf("(unsigned char *)PEB = %x \n", (unsigned char *)PEB);
printf("(unsigned char *)PEB + 0xc) = %x \n", (unsigned char *)PEB + 0xc);
printf("取出内存中的值: %x \n", (DWORD **)((unsigned char *)PEB + 0xc));
printf("取出其地址中的值:%x \n", *(DWORD **)((unsigned char *)PEB + 0xc)); printf("-------------------------------------------------------------- \n"); DWORD Hmodule = 0x401000; // 设置指针指向
BaseAddr = (DWORD *)0x401000; // 设置指针中的数值
*BaseAddr = 0x1000; if (BaseAddr == (DWORD *)Hmodule)
{
printf("BaseAddr = %x \t BaseAddr = %d \t Hmodule = %x \n", BaseAddr,*BaseAddr,Hmodule);
} printf("-------------------------------------------------------------- \n");
int Array[] = {1,2,3,4,5,6,7,8,9};
DWORD *Flink;
DWORD *ptr; Flink = *(DWORD **)((unsigned char *)Array);
ptr = Flink;
printf("%x \n", ptr); for (int x = 0; x < 9; x++)
{
printf("遍历元素: %d \n", *(DWORD **)((unsigned char *)Array + ( x*4 )));
} // 反向输出
for (int x = 0; x < 9; x++)
{
Flink = *(DWORD **)((unsigned char *)Array + (x * 4));
Link = *(DWORD **)((unsigned char *)Array + ((9 - x - 1) * 4));
printf("%d --> %d \n", Flink, Link);
} system("pause");
return 0;
}
#include <stdio.h>
#include <Windows.h> int main(int argc, char * argv[])
{
int Array[] = {1,2,3,4,5,6,7,8,9,10}; DWORD *PEB = (DWORD *)Array;
DWORD *Ldr = *((DWORD **)((DWORD *)Array + 1));
printf("Ldr = > %x Value = %d \n", &Ldr,Ldr); // 基本的取值
printf("PEB = %x &PEB = %x \n", PEB, &PEB);
Ldr = (DWORD *)&PEB;
printf("LDR = %x &LDR = %x *LDR = %x \n", Ldr, &Ldr, *Ldr);
printf("(unsigned char *)PEB = %x \n", (unsigned char *)PEB);
printf("(unsigned char *)PEB + 4 = %x \n", (unsigned char *)PEB + 4);
printf("取出第一个元素内存地址: %x \n", (DWORD **)((unsigned char *)PEB + 4));
printf("取出其中的值: %d \n", *(DWORD **)((unsigned char *)PEB + 4)); // 取值与替换值
DWORD ref = (DWORD)*((unsigned char *)Array + 4); // 取出第2个值
*((DWORD *)(Array + 1)) = 10; // 替换数组中第二个值
printf("取出的值: %d 替换后: %d \n", ref, *((DWORD *)(Array + 1))); // 正向遍历元素
for (int x = 0; x < 10; x++)
{
DWORD ref1 = *((DWORD *)((unsigned char *)Array + (x * 4)));
printf("正向元素输出: %d \n", ref1);
} // 反向输出元素
DWORD *Frist = NULL;
DWORD *Last = NULL;
for (int x = 0; x < 10; x++)
{
Frist = *(DWORD **)((DWORD *)Array + x);
Last = (DWORD *)((DWORD *)Array + (9 - x));
printf("反向输出: %d --> %d \n", Frist, *Last);
} system("pause");
return 0;
}

二维指针应用:

#include <stdio.h>
#include <Windows.h> int main(int argc, char * argv[])
{
DWORD Array[] = { 1, 2, 3, 4, 5 };
DWORD *ArrayPtr[] = { &Array[0], &Array[1], &Array[2], &Array[3], &Array[4] };
DWORD *PEB; // 这三种方式均可定位二级数组
PEB = *((DWORD **)((DWORD *)ArrayPtr + 1));
printf("%x %d \n", PEB,*PEB); PEB = *((DWORD **)((DWORD *)(ArrayPtr + 1)));
printf("%x %d \n", PEB, *PEB); PEB = *(DWORD **)((unsigned char *)(ArrayPtr) + (1*4));
printf("%x %d \n", PEB, *PEB); PEB = *(DWORD **)ArrayPtr;
printf("得到ArrayPtr地址: %x --> 得到Array元素地址: %x --> 得到元素值: %x \n", &PEB,PEB,*PEB); // 二级元素赋值操作
printf("得到第一个指针地址: %x \n", (DWORD)*(DWORD **)ArrayPtr);
printf("得到第二个指针地址: %x --> 数据: %d \n", (*((DWORD **)ArrayPtr) + 1), *(*((DWORD **)ArrayPtr) + 1)); printf("原始数据为: %x \n", *ArrayPtr[1]);
*((DWORD *)(ArrayPtr + 1)) = (DWORD)*(DWORD **)ArrayPtr;
printf("更改数据为: %x \n", *ArrayPtr[1]); printf("原始指针数据为: %x \n", *ArrayPtr[1]);
**((DWORD **)(ArrayPtr + 1)) = (DWORD)*(DWORD **)ArrayPtr;
printf("更改指针数据为: %x \n", *ArrayPtr[1]); for (int x = 0; x < 5; x++)
{
printf("地址: %x --> 数据: %d \n", (*((DWORD **)ArrayPtr) + x), *(*((DWORD **)ArrayPtr) + x));
} system("pause");
return 0;
}

三层指针遍历:

#include <stdio.h>
#include <Windows.h> int main(int argc, char * argv[])
{
DWORD Array[] = { 1, 2, 3, 4, 5 };
DWORD *ArrayPtr[] = { &Array[0], &Array[1], &Array[2], &Array[3], &Array[4] };
DWORD *ArrayPtrS[] = { ArrayPtr[0], ArrayPtr[1], ArrayPtr[2], ArrayPtr[3], ArrayPtr[4] }; // 输出三级指针中的数据
DWORD *PtrA = (DWORD *)((DWORD **)((DWORD ***)ArrayPtrS));
printf("获取到ArrayPtr[0]地址 = %x \t 获取到Array[0]地址 = %x \n", PtrA,*PtrA); DWORD *PtrB = (DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1)));
printf("获取到ArrayPtr[1]地址 = %x \t 获取到Array[1]地址 = %x \n", PtrB, *PtrB); DWORD PtrC = *((DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1))));
printf("获取到里面的数值: %d \n", *(DWORD *)PtrC); // 三级指针
// 遍历指针数据
printf("ArrayPtrS => ");
for (int x = 0; x < 5; x++)
printf("0x%x ", &ArrayPtrS[x]);
printf("\n");
printf("ArrayPtr => ");
for (int x = 0; x < 5; x++)
printf("0x%x ", ArrayPtr[x]);
printf("\n"); // 输出特定指针
DWORD **PtrAA = *((DWORD ***)(ArrayPtrS)+1);
printf("ArrayPtr[1] => %x \n", PtrAA); DWORD *PtrBB = ((DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1))));
printf("ArrayPtrS => %x \n", PtrBB); DWORD Ref = *((DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1))));
printf("%x \n", Ref); DWORD Ref = *(DWORD *) (*((DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1)))));
printf("原始数值 ArrayPtrS[1] = %x \n", Ref); system("pause");
return 0;
}

模块隐藏方法:

#include <Windows.h>
#include <stdio.h> int main(int argc, char* argv[])
{
DWORD *PEB = NULL,*Ldr = NULL,*Flink = NULL,*p = NULL,
*BaseAddress = NULL,*FullDllName = NULL;
__asm
{
mov eax, fs:[0x30]
mov PEB, eax
}
HMODULE hMod = GetModuleHandle("kernel32.dll");
Ldr = *((DWORD **)((unsigned char *)PEB + 0x0c));
Flink = *((DWORD **)((unsigned char *)Ldr + 0x0c));
p = Flink;
do
{
BaseAddress = *((DWORD **)((unsigned char *)p + 0x18));
FullDllName = *((DWORD **)((unsigned char *)p + 0x28)); if (BaseAddress == (DWORD *)hMod)
{
**((DWORD **)(p + 1)) = (DWORD)*((DWORD **)p);
*(*((DWORD **)p) + 1) = (DWORD)*((DWORD **)(p + 1));
break;
}
p = *((DWORD **)p);
} while (Flink != p);
return 0;
}

循环枚举Kernel32.dll 中模块地址

#include <stdio.h>
#include <Windows.h> int main(int argc, char * argv[])
{
int a;
__asm
{
mov ebx, dword ptr fs : [0x30]
mov ecx, dword ptr[ebx + 0xc]
mov ecx, dword ptr[ecx + 0x1c]
mov ecx, [ecx]
mov edx, [ecx + 0x8]; kernelbase.dll mov eax, [edx+0x3c]
mov ecx, [edx + eax + 0x78]
add ecx,edx
mov ebx, [ecx+0x20]
add ebx,edx
xor edi,edi
s1:
inc edi
mov esi, [ebx+edi*4]
add esi,edx cmp esi,edx
je no
loop s1
no:
xor eax,eax
}
system("pause");
return 0;
}

生成shellcode并自动提取:

#include <stdio.h>
#include <Windows.h> int main(int argc, char * argv[])
{ DWORD Start, End, Len;
goto GetShellCode;
__asm
{
ShellCodeStart:
mov ebx, dword ptr fs : [0x30]
mov ecx, dword ptr[ebx + 0xc]
mov ecx, dword ptr[ecx + 0x1c]
mov ecx, [ecx]
mov edx, [ecx + 0x8]; kernelbase.dll mov eax, [edx + 0x3c]
mov ecx, [edx + eax + 0x78]
add ecx, edx
mov ebx, [ecx + 0x20]
add ebx, edx
xor edi, edi
s1 :
inc edi
mov esi, [ebx + edi * 4]
add esi, edx cmp esi, edx
je no
loop s1
no :
xor eax, eax
ShellCodeEnd:
} GetShellCode:
__asm
{
mov Start, offset ShellCodeStart
mov End, offset ShellCodeEnd
} Len = End - Start;
unsigned char *newBuffer = new unsigned char[Len + 1024]; memset(newBuffer, 0, Len + 1024);
memcpy(newBuffer, (unsigned char *)Start, Len); FILE *fp = fopen("c://shellcode.txt", "wb+"); //fwrite(newBuffer, Len, 1, fp);
//_fcloseall(); fwrite("unsigned char Buffer[] = {", 22, 1, fp);
for (int x = 0; x < Len; x++)
{
if (x % 16 == 0)
fwrite("\r\n", 2, 1, fp);
fprintf(fp, "0x%02x,", newBuffer[x]);
}
fwrite("\n};", 2, 1, fp);
_fcloseall(); system("pause");
return 0;
}

运行后自动生成shellcode.txt文本。

通过上方代码生成二进制shellcode.bin文件,然后将其动态读入内存,并执行即可.

#include <stdio.h>
#include <Windows.h> int main(int argc, char * argv[])
{
HANDLE fp;
unsigned char * fBuffer;
DWORD fSize, dwSize; fp = CreateFile(L"c://shellcode.bin", GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); fSize = GetFileSize(fp, 0);
fBuffer = (unsigned char *)VirtualAlloc(NULL, fSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
ReadFile(fp, fBuffer, fSize, &dwSize, 0);
CloseHandle(fp); __asm
{
mov eax,fBuffer
push eax
ret
int 3
}
return 0;
}

ShellCode注入进程:

#include <stdio.h>
#include <windows.h> unsigned char ShellCode[] = "shellcode代码"; BOOL InjectShellCode(int Pid)
{
HANDLE Handle, remoteThread;
PVOID remoteBuffer; Handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid); remoteBuffer = VirtualAllocEx(Handle, NULL, sizeof(ShellCode), (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
WriteProcessMemory(Handle, remoteBuffer, ShellCode, sizeof(ShellCode), NULL);
remoteThread = CreateRemoteThread(Handle, NULL, 0, (LPTHREAD_START_ROUTINE)remoteBuffer, NULL, 0, NULL);
CloseHandle(Handle);
} int main(int argc, char *argv[])
{
InjectShellCode(1024);
return 0;
}

C/C++ 通用ShellCode的编写与调用的更多相关文章

  1. ShellCode的编写入门

    上次学习了下堆喷漏洞的原理,虽说之前有学习过缓冲区溢出的原理,但还没了解过堆喷这个概念,于是趁此机会学习了,顺便复习了缓冲区溢出这块知识,之前由于各种原因对Shellcode的编写只是了解个大概,并没 ...

  2. Linux下shellcode的编写

    Linux下shellcode的编写 来源  https://xz.aliyun.com/t/2052 EdvisonV / 2018-02-14 22:00:42 / 浏览数 6638 技术文章 技 ...

  3. ShellCode的几种调用方法

    ShellCode是一种漏洞代码,中文名也叫填充数据,一般是用C语言或者汇编编写.在研究的过程中,自己也学到了一些东西,发现其中也有许多坑,所以贴出来,如果大家有碰到的,可以参考一下. 以启动电脑上的 ...

  4. DLL编写与调用全解

    DLL编写与调用全解 DELPHI学习   2008-12-23 22:52   阅读8   评论0   字号: 大  中  小 第一章 为什么要使用动态链接库(DLL) top 提起DLL您一定不会 ...

  5. EC笔记,第二部分:5.了解C++默默编写并调用哪些函数

    5.了解C++默默编写并调用哪些函数 1.C++空类 C++会为一个空类建立以下函数 (1).默认构造函数 (2).默认拷贝构造函数 (3).析构函数 (4).赋值运算符(如果成员包含引用类型或con ...

  6. Effective C++ 之 Item 5:了解C++默默编写并调用哪些函数

    Effective C++ chapter 2. 构造 / 析构 / 赋值运算 (Constructors, Destructors, and Assignment Operators) Item 5 ...

  7. delphi 基础之三 编写和调用dll文件

    delphi 编写和调用dll文件   Windows 的执行文件可以划分为两种形式程序和动态连接库 (DLLs).一般程序运行是用.EXE文件,但应用程序有时也可以调用存储在DLL的函数. 在如下几 ...

  8. VB.NET中的DLL编写和调用的最简单示例

    DLL(动态链接库)是一个很有用的东西,在开发大项目的时候显得非常重要,因为多人合作开发时,可以给每个人分配一个任务,用DLL完成,最后组合起来,就不会出现互相冲突的问题.这里给出最简单的DLL编写与 ...

  9. 编写并提取通用 ShellCode

    简易 ShellCode 虽然可以正常被执行,但是还存在很多的问题,因为上次所编写的 ShellCode 采用了硬编址的方式来调用相应API函数的,那么就会存在一个很大的缺陷,如果操作系统的版本不统一 ...

  10. 通用shellcode

    所有 win_32 程序都会加载 ntdll.dll 和 kernel32.dll 这两个最基础的动态链接库.如果想要 在 win_32 平台下定位 kernel32.dll 中的 API 地址,可以 ...

随机推荐

  1. 认证,权限,频率源码分析 自定义频率类 SimpleRateThrottle缓存频率类 基于APIView编写分页

    目录 昨日回顾 三种位置的token获取 三种权限校验方式 原生django的cookie+session认证底层原理 断点调试使用 认证,权限,频率源码分析(了解) 权限源码分析 认证源码分析 频率 ...

  2. 使用BAPI_NETWORK_COMP_*实现生产订单组件的增删改查

    1.文档说明 对于生产订单组件的增删改有多种办法,比较常用的有使用内部函数CO_XT_COMPONENT_*,有改造BAPI_ALM_ORDER_MAINTAIN来实现,各有千秋. 本文档介绍,通过P ...

  3. 如何用 7 分钟击破 Serverless 落地难点?

    当前,Serverless 覆盖的技术场景正在不断变广.Serverless 已在微服务.在线应用.事件驱动.任务处理等众多场景被验证且广泛应用 .当你想要部署一个网站时,需要自己购买服务器并花费时间 ...

  4. freeswitch的2833和inband对接方案

    概述 freeswitch支持三种模式的DTMF传输方式,分别时inband.INFO.2833. 在传统的PSTN网络中,所有的DTMF码都是inband模式,所以VOIP网络和PSTN网络对接中, ...

  5. 异步httpClient(Async HttpClient)

    一.简介 二.mvn依赖 三.客户端 3.1 官网实例 3.2. 根据官方文档的介绍,简单封装了一个异步HttpClient工具类 3.3 基本原理 四.参考文档 一.简介 HttpClient提供了 ...

  6. C#对象二进制序列化优化:位域技术实现极限压缩

    目录 1. 引言 2. 优化过程 2.1. 进程对象定义与初步分析 2.2. 排除Json序列化 2.3. 使用BinaryWriter进行二进制序列化 2.4. 数据类型调整 2.5. 再次数据类型 ...

  7. org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException: Input length = 2

    1.报错 在运行SpringBoot项目时遇到报错: 17:44:47.558 [main] ERROR org.springframework.boot.SpringApplication -- A ...

  8. MPC 是下一代私钥安全的7大原因

    PrimiHub一款由密码学专家团队打造的开源隐私计算平台,专注于分享数据安全.密码学.联邦学习.同态加密等隐私计算领域的技术和内容. 多重签名钱包与单一密钥钱包相比,因其提升了资产安全性,如今已成为 ...

  9. Mybatis @Insert插入数据返回自增的主键id

    mapper层 @Insert("insert into t_user (username,password,valid,create_time) values (#{username},# ...

  10. MySQL重建表统计信息

    MySQL重建表统计信息 背景 最近一段时间遇到了一些性能问题 发现很多其实都是由于 数据库的索引/统计信息不准确导致的问题. Oracle和SQLServer都遇到了很多类似的问题. 我这边联想到 ...