gcc -static xxx.c -o xxx
upx为压缩壳,将可执行文件进行压缩,当可执行文件过小时加壳会失败,所以此处采用了静态链接的方式进行编译;
附上设计的源码:
#include<stdio.h> #include<string.h> #include<windows.h> int main(){ int a,b,c,d; int i; char answer[27]; char seq1[]="are you a student at AEU?"; char seq2[]="enter 1 or 0:"; char seq3[]="welcome to the competition,do you want any hints?"; char seq4[]="do you think the examiner is handsome?"; char seq5[]="you are right!"; char seq6[]="I modified the spack,You should try to remove the pack manually"; char seq7[]="Listen!the flag is "; char seq00[]=" AEU{eeAmi3_"; char flag[27]="AEU{eeAmi3_6e74rt_handi43}"; char seq02[]="This is the first time for us to hold the competition,Looking forward to your performance\nHere's a present for you:"; char seq03[]="Never give up\nNever lose hope.\nAlways have faith,\nIt allows you to cope.\nTrying times will pass,\nAs they always do."; char seq04[]="Just have patience,\nYour dreams will come true. \nSo put on a smile,\nYou'll live through your pain.\nKnow it will pass, \nAnd strength you will gain"; for(i=0;i<strlen(seq1);i++){ printf("%c",seq1[i]); Sleep(90); } printf("\n"); puts("enter 1 or 0:"); scanf("%d",&a); if(a){ for(i=0;i<strlen(seq3);i++){ printf("%c",seq3[i]); Sleep(90); } printf("\n"); puts("enter 1 or 0:"); scanf("%d",&b); if(b){ for(i=0;i<strlen(seq4);i++){ printf("%c",seq4[i]); Sleep(90); } printf("\n"); puts("enter 1 or 0:"); scanf("%d",&c); if(c){ for(i=0;i<strlen(seq5);i++){ printf("%c",seq5[i]); Sleep(90); } printf("\n"); for(i=0;i<strlen(seq6);i++){ printf("%c",seq6[i]); Sleep(90); } printf("\n"); for(i=0;i<strlen(seq7);i++){ printf("%c",seq7[i]); Sleep(200); } for(i=0;i<strlen(seq00);i++){ printf("%c",seq00[i]); Sleep(500); } Sleep(6000); puts(" (*^_^*)"); puts("are you really want the flag?"); for(i=0;i<strlen(seq02);i++){ printf("%c",seq02[i]); Sleep(80); } printf("\n"); for(i=0;i<strlen(seq03);i++){ printf("%c",seq03[i]); Sleep(80); } printf("\n"); for(i=0;i<strlen(seq04);i++){ printf("%c",seq04[i]); Sleep(80); } printf("\n"); for(i=0;i<strlen(seq7);i++){ printf("%c",seq7[i]); Sleep(200); } for(i=0;i<strlen(flag);i++){ printf("%c",flag[i]); Sleep(900); } printf("\n"); puts("now,input your answer:"); scanf("%s",answer); for(i=0;i<strlen(flag);i++){ if(flag[i]=='e') flag[i]+=1; else if(flag[i]=='r') flag[i]-=1; } if(!strcmp(answer,flag)) puts("right!"); else puts("wrong,Is it really that simple?"); Sleep(6000); } else{ puts("You can't solve the problem"); } } else{ puts("I can't give you a hint"); } } else{ puts("sorry,identity failed"); } return 0; }
upx xxx.exe -o xxx.exe
使用upx工具完成加壳,正常情况下upx工具可以顺利完成脱壳
upx -d xxx.exe
但对壳进行修改后该工具便失效。
一,首先认识upx:
upx的作用是压缩程序代码,比如可执行文件中的123456用a代替,还会在程序的开头插入一段代码(用来解压缩);当执行该文件时插入的代码会起作用,将文件解压,但解压只能在内存中完成,采用静态的方式是没办法看到解压后的可执行代码的。所以通用方法是动态调试,一直追踪到解压后的可执行代码,完成转储后也就是脱壳成功。
二、修改upx
本题设计采用修改upx的标志:
修改为00 00 00 00
保存后新的文件在upx -d命令下回出错,此处不演示。设计意图在于让解题人手动脱壳,修改方式比较简单,辅助工具应该也能完成,没有做过实验,不在赘述。
upx放脱壳的几种方法总结:
1.修改区段名,查看一下加壳后的各个段:
修改掉区段的名字,另存后不影响执行,upx-d失败;另外的区段名同理。
2.修改特征码:
pe工具能够识别出文件被加upx是因为加壳后upx自身的特征码被识别,我们可以修改特征码达到使工具识别步出upx的目的;
特征码:60 BE ?? ?? ?? 00 8D BE ?? ?? ?? FF
(留坑)
3.添加区段:
顺便学习了关于PE的相关知识,汇总如下:
可执行文件的代码和数据分类存储在各个块之中,PE头与区段之间是块表(说明了各个块的相关信息)
VA:虚拟地址,即PE文件映射到内存后的地址(虚拟空间 )
RVA:相对虚拟地址,相对于PE载入地址的偏移。 VA=基址+相对虚拟地址
物理地址/文件偏移地址:RAW Offset,某个数据相对于文件头的偏移,,显示的地址就是物理地址
主要分析块表:块表由IMAGE_SECTION_HEADERS结构体来定义,原形如下:
比较晦涩,结合实例分析:
第一个块表标黑部分,40个字节,
55 50 58 30 00 00 00 00(8个字节):UPX0字符的十六进制,表示块名
00 70 00 00(4个字节):VirtualSize,表示实际上的区块大小(未对齐之前),与区段表中的VSize值刚好对应
00 10 00 00(4个字节):VA,即第一个块在内存中的映射地址,对应Voffset
00 00 00 00 (4个字节):该块在磁盘中占用的空间,对应RSize
00 02 00 00 (4个字节):该块在磁盘中的偏移,对应Roffset
后面连续三段4字节参见结构体定义,最后的80 00 00 E0为块的属性标志,定义了是否可读、可写、可执行。
重点区分下文件偏移地址和相对虚拟地址,两者都是相对于头的偏移;文件偏移地址是块在磁盘中相对于PE头的偏移,RVA则是PE加载映射到内存之后相对于头的偏移。
如图
,如图分析UPX1的RAWoffset为200,即在PE中地址200开始的地方,往后找大小E00,即为UPX1这个段的实际代码。
使用lordPE工具添加一个区段,可以使工具不能识别出upx壳。(留坑待补)
三、解题:
脱壳部分采用ESP定律,此处不演示
进入IDA分析:
对比源码,整体上结构非常简单,反汇编后略微晦涩,只需要能看懂栈的布局就能明白变量之间的关系;
逻辑就是比较str1与str2的值,v30[-95]实际上也是str2,原因如下:
v30为int型,占据4个字节,v30[-95]也就是从v30的地址往前推95*4=380(17Ch)个字节,在栈的布局上来看,v30在栈中的地址是esp+33C,往栈减小的位置推算,33c-17c=1c0,即v30[-95]实际上esp+1c0,对比栈的空间分布,也就是Str2.
其余的变量推算同理。必须要明确的是变量a为int型(占4字节),那么a[i]表示含义是基址加上i个int型的偏移,即4*i个字节。
(32位/64位环境下)
char型数据则占据1字节;double 8字节 ;short 占2字节
#以上参考:
1.从做题到出题再到做题三部曲-UPX - 先知社区 (aliyun.com)
2.《加密解密》