赞
踩
前言
这篇文章是我做完攻防世界练习区的题目后对这十道题做的总结。emmm,其实这几道题我几个月前就做完了,这几天又回去试着再做一遍发现还是有一些地方存在疑惑,并且速度也没有太大的提升。所以决定把之前做过的题目整理一下都写在博客里,便于以后复习顺便把一些我没搞懂的地方也记录在这里,希望以后可以解决。也希望能有大佬有缘看到我这个小菜鸟的文章,可以帮我指点迷津。
这一篇只写攻防世界新手区的几道题,之后我还会把BUUCTF做的题也做个总结,写一篇博客,希望可以把这个习惯保持下去。说了这么多废话,下面开始正文。
int main(int argc, char *argv[]) {
if (argc != 4) {
printf(“what?\n”);
exit(1);
}
unsigned int first = atoi(argv[1]); if (first != 0xcafe) { printf("you are wrong, sorry.\n"); exit(2); } unsigned int second = atoi(argv[2]); if (second % 5 == 3 || second % 17 != 8) { printf("ha, you won't get it!\n"); exit(3); } if (strcmp("h4cky0u", argv[3])) { printf("so close, dude!\n"); exit(4); } printf("Brr wrrr grr\n"); unsigned int hash = first * 31337 + (second % 17) * 11 + strlen(argv[3]) - 1615810207; printf("Get your key: "); printf("%x\n", hash); return 0;
}`
由最后的两个printf语句可以看出flag就是hash,由%x可以看出是十六进制表示。
所以向上看对hash进行运算的只有
unsigned int hash = first * 31337 + (second % 17) * 11 + strlen(argv[3]) - 1615810207;
可知hash的值与first,second,argv[3]的值有关,从上面的3个if语句可以判断出:当if()里的条件为真时,都会提示错误并退出程序,所以3个if()里的条件都为假,所以可以推测出下列条件;first=0xcafe,second % 5 != 3&&second % 17 == 8(最小的second是25),argv[3]=h4cky0u(所以strlen(argv[3])=7)。将上述条件带入计算hash的那段代码可以得出hash,下面是我写的代码`#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int first=0xcafe;
int second=25;
int a=7;
int hash=0;
hash=first * 31337 + (second % 17) * 11 + 7 - 1615810207;
printf("%x",hash);
return 0;
}`
结果是
所以flag就是c0ffee。
第一题拿下!
可以看出加了UPX这一类壳。所谓加壳就是为了隐藏程序真正的OEP(入口点),所以为了正常地打开程序使程序正常运行,我们必须脱壳。我选择的是用upx脱壳。
脱壳成功!关于upx脱壳我之前看到一篇博客讲得挺详细的,我把超链接放在下面。
使用upx脱壳工具脱壳
脱壳结束后再把脱壳后的文件放在exeinfope里看一下。
可以看到现在已经没有UPX壳了,之后把文件拉到IDA里打开。
按F5反汇编后发现flag相当可疑,双击flag追踪过去看到
发现了flag{Upx_1s_n0t_a_d3liv3r_c0mp4ny}。
第二题拿下!
发现没壳,而且是64位的文件,所以放到IDA64里静态分析。ctrl+f搜索main函数,再按F5反汇编,如图
双击sub_4007F0看到
说明输入正确的flag会让程序输出上述printf的一句话并退出。
再双击sub_4007C0看到
所以当输入错误的flag时会使程序输出上述一句话并退出。
所以不能让程序运行sub_4007C0这个函数,所以*s[i] = (char)(*((_BYTE )&v7 + i % v6) ^ v8[i])
必须成立。去上面的代码找到v7,v6,v8[]
看出v6=7 , v8[]=:"AL_RT^L*.?+6/46 , v7=28537194573619560LL,将28537194573619560LL转换成字符,如图
因为IDA是小段法存储,所以真正的v7应该是ebmarah倒过来变成harambe。下面是我写的解题代码
#include <stdio.h> #include <stdlib.h> #include <string.h> int main() { char v8[50]=":\"AL_RT^L*.?+6/46"; char v7[50]="harambe"; int v6 =7; char flag[50]; for ( int i = 0; i < strlen(v8); ++i ) { flag[i] = (char)(*((unsigned char*)&v7 + i % v6) ^ v8[i]) ; } printf("%s",flag); return 0; }
结果为
第三题拿下!
看出文件无壳且是32位的,用IDA打开
发现整段代码没有什么运算,感觉&strs有点可疑,双击进去
发现9447{This_is_a_flag}…感觉简单得有点过分
又去字符串窗口看了一下
flag确实就是这个了(难道这才是第一题?)
无壳是用python写的。发现IDA打开之后无法反汇编,所以当时我去找了一些博客,发现是要用python反汇编,这里我用的是在线Python反汇编,超链接我放在下面。
在线Python反编译工具
推测出当flag在encode函数中加密后与correct相等就成功。
由这一段看出将correct通过base64解码后,经过xor异或可以再得出原来的flag。
下面是我写的解题代码
import base64
correct = 'XlNkVmtUI1MgXWBZXCFeKY+AaXNt'
the_flag = base64.b64decode(correct)
flag=''
for i in the_flag:
x =(ord(chr(i))-16) ^ 32
flag+=chr(x)
print(flag)
得出结果nctf{d3c0mpil1n9_PyC}
第五题拿下!
无壳且是32位,拉到IDA里反汇编
双击aFlag_0进去
发现aFlag_0意思是flag错误,双击unk_413E90进去
发现显示的是flag get,所以正确的flag会使程序进行unk_413E90
所以if()里的条件要为假才可以不进行aFlag_0,所以v3要为0
因为v3是由 *v3 = strcmp((const char )&v5, &v9) 决定的,当v5=v9时v3才=0,发现v9由 **_mm_storeu_si128((__m128i )&v5, _mm_loadu_si128((const __m128i )&xmmword_413E34))决定,
发现xmmword_413E34有点可疑,双击进去
猜测v9就等于xmmword的值,将3074656D30633165577B465443545544h转换成字符
因为IDA是小端法,所以这一串字符应该反过来输出,即为DUTCTF{We1c0met0}
一看就知道这就是flag
第六题拿下!
发现无壳且是32位的exe文件,发现是exe可执行文件,直接双击打开
这一看我就乐了,经典点灯小游戏,简单地说就是12345678分别控制1234578这8盏灯及其相邻的灯的灭亮(1和8也算作相邻的),了解过这个游戏的都知道直接按12345678的顺序点一遍就可以全部点亮了。全点亮后如下图
……发现flag真的出来了。
下面放在IDA里看一下,发现下面这一段很明显是判断灯是否全亮的
双击sub_457AB4()进去,最后发现一段循环异或,猜测这就是flag的算法
写一段代码把v2到v58,v59到v115的值分别放在2个数组里,再进行异或运算,最后输出结果。
#include<stdio.h> #include<string.h> #include<stdlib.h> int main() { int a[57]={123,32,18,98,119,108,65,41,124,80,125,38,124,111,74,49,83,108,94,108,84,6,96,83,44,121,104,110,32,95,117,101,99,123,127,119,96,48,107,71,92,29,81,107,90,85,64,12,43,76,86,13,114,1,117,126,0}; int b[57]={18,64,98,5,2,4,6,3,6,48,49,65,32,12,48,65,31,78,62,32,49,32,1,57,96,3,21,9,4,62,3,5,4,1,2,3,44,65,78,32,16,97,54,16,44,52,32,64,89,45,32,65,15,34,18,16,0}; for (int i = 0; i < 57; ++i ) { a[i] ^= b[i]; a[i] ^= 0x13u; printf("%c",a[i]); } return 0; }
(把这些数据拿到数组里真是太麻烦了,本人太菜,一个个复制粘贴过去的,有大佬知道什么快速的方法希望能告诉我 (~ ̄▽ ̄)~)
有个地方我想说一下,本来我的代码是把printf放到for循环之外的,我打算一次性输出整个字符串,但是控制台老是显示NULL,我到现在也不清楚是为什么,最后只能一个一个字符输出了,有大佬知道为什么的话希望能告知 o(@)o。
最后结果如下图
第七题拿下!
发现无壳且是32位的exe文件,啧啧,一看又是exe我想不会又可以不写代码直接玩出flag吧,结果我双击进去发现只有一句话:please input your serial:……emmm还是老老实实的IDA打开吧。ctrl+F查找main之后按F5反汇编,然后直接去找判断成功的代码如下
看出v3要大于等于17,并且v10与v13要相同才可以进行aSuccess。继续往上看
要输入v9,并且v9的长度要小于等于17,然后在do循环里把v9[v3]赋给v4,发现sprintf很像printf,猜测是输出函数,感觉asc_408044可疑,双击进去发现
有%x表示十六进制,猜测sprintf就是把v4用十六进制表示,再往上看
发现v13=437261636b4d654a757374466f7246756e,所以flag应该就是把437261636b4d654a757374466f7246756e用十六进制表示出来,为了方便我就直接用在线转换工具把它转换成16进制了,结果如下。
flag就是CrackMeJustForFun
第八题拿下!
发现无壳且是32位的,拉到IDA里去反汇编,发现main函数反汇编后只有这些东西
发现authenticate是证实的意思,所以猜测flag与authenticate有关系,双击进去看见
发现最后的if-else与判断胜负的函数很像,双击unk_8048B44进去
发现有Success,估计正确的flag就可以进入unk_8048B44
发现wcscmp和strcmp很像估计作用也是一样的,就是当ws=s2的时候就可以进入if了,再往上看发现decrypt函数,decrypt是翻译的意思,猜测是加密函数,双击进去。
发现这段代码先把s的值复制给dest,然后将dest的每个值减去a2的每个值,然后返回值是dest,从原函数可知dest的值就是之前的s2。接下来就是要找出传入的s和a2的值,即原来的s和dword_8048A90,双击原来的dword_8048A90进去,看见了s和dword_8048A90的值,分别选中它们的值后按Shift+E,选择C无符号字符数组(十进制),将值复制下来准备写解题代码时用。记得最后的4个0不要复制,因为字符串默认结尾为0。
下面是我写的解题代码。
#include<stdio.h> #include<string.h> #include<stdlib.h> int main() { int a2[]={1,20,0,0,2,20,0,0,3,20,0,0,4,20,0,0,5,20,0,0}; int s[]={58,20,0,0,54,20,0,0,55,20, 0,0,59,20,0,0,128,20,0,0, 122,20,0,0,113,20,0,0,120,20, 0,0,99,20,0,0,102,20,0,0, 115,20,0,0,103,20,0,0,98,20, 0,0,101,20,0,0,115,20,0,0, 96,20,0,0,107,20,0,0,113,20, 0,0,120,20,0,0,106,20,0,0, 115,20,0,0,112,20,0,0,100,20, 0,0,120,20,0,0,110,20,0,0, 112,20,0,0,112,20,0,0,100,20, 0,0,112,20,0,0,100,20,0,0, 110,20,0,0,123,20,0,0,118,20, 0,0,120,20,0,0,106,20,0,0, 115,20,0,0,123,20,0,0,128,20, 0,0,0,0,0,0,83,0,0,0, 117,0,0,0,99,0,0,0,99,0, 0,0,101,0,0,0,115,0,0,0, 115,0,0,0,33,0,0,0,32,0, 0,0,87,0,0,0,101,0,0,0, 108,0,0,0,99,0,0,0,111,0, 0,0,109,0,0,0,101,0,0,0, 32,0,0,0,98,0,0,0,97,0, 0,0,99,0,0,0,107,0,0,0, 33,0,0,0,10,0,0,0}; int x=0,i=0,j=0; while ( i < 156 ){ x=s[i++]-a2[j++%20];//写EXP的要点是把字符都转换成整数,并且最后一个一个地将整数换成字符输出 ,之前也提过了,为了加深我自己的印象,又啰嗦了一遍 // if(x>32){ printf("%c",x); // } } // 这里我把if(x>32)给注释掉了,如果直接用这段代码,会发现结果的字符之间会有很多的空格,为了消除这些空格,我设置了x>32这个条件,因为空格的ASCII代码对应的十进制是32 return 0; }
最后删去空格的结果如下
第九题拿下!
发现无壳且是64位的文件,根据题目描述这应该是一道迷宫题。关于迷宫题,我当时做完这道题之后专门去找了一些资料学习,其中有一篇博客讲的很好,由题目也有解析,我把超链接和网址放在下面。
逆向迷宫题总结
网址:https://blog.csdn.net/weixin_50549897/article/details/110633105
用IDA打开文件,ctrl+F查找main函数,F5反汇编之后,看了看发现有4个if语句,因为是迷宫题,所以推测这四个if就是用来判断上下左右的,把if()里的数字都换成字符,发现4个方向键是 ‘O’ , ‘o’ , ‘.’ , ‘0’ 。发现四个if语句最后都会到LABEL_14,一直追踪下去,发现如下代码
发现**if ( asc_601060[8 * (signed int)v10 + SHIDWORD(v10)] != ‘#’ )**应该就是判断是否到达终点的函数,双击asc_601060进去发现如下图
数了数空格,*,#一共有64个字符,推测应该是8x8的迷宫
四个if语句进行的函数代码如下
bool __fastcall sub_400650(_DWORD *a1)//对应O { int v1; // eax v1 = (*a1)--; return v1 > 0; } bool __fastcall sub_400660(int *a1)//对应o { int v1; // eax v1 = *a1 + 1; *a1 = v1; return v1 < 8; } bool __fastcall sub_400670(_DWORD *a1)//对应. { int v1; // eax v1 = (*a1)--; return v1 > 0; } bool __fastcall sub_400680(int *a1)//对应0 { int v1; // eax v1 = *a1 + 1; *a1 = v1; return v1 < 8; }
可以看出上下和左右应该分别由 ‘O’,‘o’ 和 ‘.’ ,'0’控制,并且发现有>0和<8来控制边界所以可以确定这个迷宫就是8x8的迷宫了。
因为O和o传入的参数是+1的,4个方向函数传入的参数都是4个字节的,所以O和o应该是高位4字节,而.和0应该是低位4字节,推测高位决定行低位决定列,所以推测O和o应该决定左右,.和0应该决定上下,所以上下左右分别为 ‘.’ , ‘0’ , ‘O’,‘o’ .方向搞定。。再往上看发现如下代码
puts("Input flag:");
scanf("%s", &s1, 0LL);
if ( strlen(&s1) != 24 || strncmp(&s1, "nctf{", 5uLL) || *(&byte_6010BF + 24) != '}' )
可以看出输入的s1就是flag,并且flag的长度等于24,并且开头前5个字符就是 “nctf{”,并且结尾的字符是 ‘}’。所以能够操作的步数是并且只能是24-6=18步,下面是我写的打印迷宫的代码。
#include <stdio.h> #include <stdlib.h> #include <string.h> int main() { char v[100]=" ******* * **** * **** * *** *# *** *** *** *********"; printf("%d\n",strlen(v)); for(int i=0;i<64;i++) { printf("%c",v[i]); if((i+1)%8==0) { printf("\n"); } } return 0; }
打印出来为
其中*为墙壁,空格为可走的路径。走几次迷宫就可以判断出来走法了,走法为:右下右右下下左下下下右右右右上上左左,所以flag=nctf{o0oo00O000oooo…OO}。
第十题拿下!
1.脱壳方法与技术
目前脱壳我只会用upx脱壳,但我之前在找资料的时候看到很多大佬都是用的ollydbg动态调试来脱壳的,之后我肯定是要重点学习一下的。
2.相关汇编基础打牢
没有系统地专门学习过汇编,只是在网上找资料学会了一些皮毛,对于堆栈的理解还是不够深刻,之后也要找时间打打牢。
3.做题做题再做题
果然做题才是积累经验的好方法,之后题目肯定不能断
4.博客继续写
这次回顾攻防世界的题目让我再次对自己的记性产生了怀疑,果然好记性不如烂笔头,之后每做完一些题我都要写一下博客。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。