[toc]
菜鸡最近开始学二进制了,先做几道简单的逆向题练练手(12道)
0x01 simple-unpack
看看有没有壳
有壳,还提示我去用 upx -d 命令来脱壳
那我就恭敬不如从命
然后再用ida打开看看
Shift + F12
不要太简单
0x02 insanity
32位的,拖入IDA,Shift + F12
看到flag
0x03 python-trade
一个pyc文件
反编译了
看看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import base64
def encode(message): s = '' for i in message: x = ord(i) ^ 32 x = x + 16 s += chr(x)
return base64.b64encode(s)
correct = 'XlNkVmtUI1MgXWBZXCFeKY+AaXNt' flag = '' print 'Input flag:' flag = raw_input() if encode(flag) == correct: print 'correct' else: print 'wrong'
|
猜测这个correct 与 flag有关,要让encode(flag) == correct
分析一波
在python3 和 python2 中,base64.b64decode返回的东西是不一样的,python2 ,返回的是str,而python3 返回的是bytes,一般为了操作上的方便,用python2会比较好
写脚本,用python2执行,得到flag
1 2 3 4 5 6 7 8 9 10 11 12 13
| import base64 def decode(c): s = "" c = base64.b64decode(c) print(type(c)) for i in c: a = ord(i) a = a - 16 a = a ^ 32 s += chr(a) return s
decode('XlNkVmtUI1MgXWBZXCFeKY+AaXNt')
|
0x04 re1
PE看一看,32位的程序,vsc++写的,没有壳
跑跑看
IDA看看
读读程序(逆向分析)
从终端输入v9
和 v5比较,目的是打印aFlag
看到会打印 aFlag
,推测存在程序里面
直接notepad++打开,关键字搜索
找到flag
0x05 game
32位的,没有壳
IDA看看
这个函数是打印用的
找到可能打印flag的地方
这里有打印东西的函数
说明这个 v3所代表的字符串就会是我们的 flag
分析一下,v1到v59,和v60到v116这些变量是连着的,即应该用一个两个数组分开装
如果直接复制下来当做C直接跑,那么变量定义的时候就不会有这样连着的结构
本来是v5的位置,装着v58
写个 python脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
| v60 = 18 v61 = 64 v62 = 98 v63 = 5 v64 = 2 v65 = 4 v66 = 6 v67 = 3 v68 = 6 v69 = 48 v70 = 49 v71 = 65 v72 = 32 v73 = 12 v74 = 48 v75 = 65 v76 = 31 v77 = 78 v78 = 62 v79 = 32 v80 = 49 v81 = 32 v82 = 1 v83 = 57 v84 = 96 v85 = 3 v86 = 21 v87 = 9 v88 = 4 v89 = 62 v90 = 3 v91 = 5 v92 = 4 v93 = 1 v94 = 2 v95 = 3 v96 = 44 v97 = 65 v98 = 78 v99 = 32 v100 = 16 v101 = 97 v102 = 54 v103 = 16 v104 = 44 v105 = 52 v106 = 32 v107 = 64 v108 = 89 v109 = 45 v110 = 32 v111 = 65 v112 = 15 v113 = 34 v114 = 18 v115 = 16 v116 = 0 v3 = 123 v4 = 32 v5 = 18 v6 = 98 v7 = 119 v8 = 108 v9 = 65 v10 = 41 v11 = 124 v12 = 80 v13 = 125 v14 = 38 v15 = 124 v16 = 111 v17 = 74 v18 = 49 v19 = 83 v20 = 108 v21 = 94 v22 = 108 v23 = 84 v24 = 6 v25 = 96 v26 = 83 v27 = 44 v28 = 121 v29 = 104 v30 = 110 v31 = 32 v32 = 95 v33 = 117 v34 = 101 v35 = 99 v36 = 123 v37 = 127 v38 = 119 v39 = 96 v40 = 48 v41 = 107 v42 = 71 v43 = 92 v44 = 29 v45 = 81 v46 = 107 v47 = 90 v48 = 85 v49 = 64 v50 = 12 v51 = 43 v52 = 76 v53 = 86 v54 = 13 v55 = 114 v56 = 1 v57 = 117 v58 = 126 v59 = 0 for i in range(56): locals()["v"+str(3 + i)] ^= locals()["v"+str(60 + i)] locals()["v"+str(3 + i)] ^= 0x13 print(chr(locals()["v"+str(3 + i)]),end="")
print("hello")
|
1
| zsctf{T9is_tOpic_1s_v5ry_int7resting_b6t_others_are_n0t}
|
0x06 getit
IDA打开看看
像极了flag,点开看看
有一个全局变量t,t的内容为
1
| 0x53+harifCTF{????????????????????????????????}+0x00
|
看看t在哪里
分析一下
1 2 3 4 5 6 7 8 9
| while ( (signed int)v5 < strlen(s) ) { if ( v5 & 1 ) v3 = 1; else v3 = -1; *(&t + (signed int)v5 + 10) = s[(signed int)v5] + v3; LODWORD(v5) = v5 + 1; }
|
这部分代码对t做了操作,估计操作完了t,t里面的值就是flag了
所以可以运行,然后操作完后看看t,也可以复制下来稍微改一改,然后把t跑出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include<stdio.h> int main(int argc, char const *argv[]) { char v3; int v5; char t[] = "SharifCTF{????????????????????????????????}"; char s[] = "c61b68366edeb7bdce3c6820314b7498"; v5 = 0; while ( v5 < 32 ) { if ( v5 & 1 ) v3 = 1; else v3 = -1;
*(t + v5 + 10) = s[(signed int)v5] + v3; v5 = v5 + 1; } printf("hello : %s\n",t); return 0; }
|
1
| SharifCTF{b70c59275fcfa8aebf2d5911223c6589}
|
0x07 Hello,CTF
跑一跑
用IDA打开,先看看String,没有flag,但是看到个success,猜测和flag有关
定位一下看看
找到了对应的函数
分析一下
重要的代码,是一个while循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| strcpy(&v13, "437261636b4d654a757374466f7246756e"); while ( 1 ) { memset(&v10, 0, 0x20u); v11 = 0; v12 = 0; sub_40134B(aPleaseInputYou, v6); scanf(aS, v9); if ( strlen(v9) > 0x11 ) break; v3 = 0; do { v4 = v9[v3]; if ( !v4 ) break; sprintf(&v8, asc_408044, v4); strcat(&v10, &v8); ++v3; } while ( v3 < 17 ); if ( !strcmp(&v10, &v13) ) sub_40134B(aSuccess, v7); else sub_40134B(aWrong, v7); }
|
要让v10和v13两个字符串相等,才能报成功
v13是这样的
1
| v13="437261636b4d654a757374466f7246756e"
|
一共34位
v10最开始的时候是个32byte的空字符串,经过一个do-while循环的处理后才去和v13作比较
do-while循环
1 2 3 4 5 6 7 8 9 10 11
| v3 = 0; do { v4 = v9[v3]; if ( !v4 ) break; sprintf(&v8, asc_408044, v4); strcat(&v10, &v8); ++v3; } while ( v3 < 17 );
|
v9是我们输入的东西
一共进行17回操作,每次取v9的一个字符,作为一个8bit的数字,转成一个十六进制数字,接入v10后面,而一个byte最多对应一个两位的十六进制数
v13有34位,刚好对应17个两位的十六进制数
写个脚本,进行转换
1 2 3 4 5 6 7
| v13="437261636b4d654a757374466f7246756e" r = "" for i in range(17): s = v13[2*i:2*i+2] s = chr(int(s,16)) r += s r
|
这个就是flag
0x08 open-source
是个c文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| #include <stdio.h> #include <string.h>
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; }
|
flag 就是 c0ffee
0x09 logmein
基本信息
跑一跑看
猜测,输入的guess就是flag
IDA看一下
找成功标志
找到对应的位置
看看哪个函数调用了这个函数
先看看main
分析一下
改写一下重要的代码
1 2 3 4 5 6 7 8 9 10 11 12 13
| void a() { strcpy(v8, ":\"AL_RT^L*.?+6/46"); v7 = 28537194573619560LL; v6 = 7; __isoc99_scanf("%32s", s); for ( i = 0; i < strlen(s); ++i ) { if ( s[i] != (char)(*((_BYTE *)&v7 + i % v6) ^ v8[i]) ) fail(); } succeed(); }
|
v8的长度是17,输入的s 长度要大于等于17,而i的取值为[0,len(s)],有一个v8[i]要取,所以len(s)<=17,因此,len(s)==17
v7是一个64bit,即8个byte的数据,v7的每个byte轮流和v8的各个字符进行 ^ 操作,最后得到数字化成char,连起来就是s应有的样子
看看v7各个byte
1
| v7 = [0x65,0x62,0x6d,0x61,0x72,0x61,0x68]
|
写c程序跑出来flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void decode(){ char v8[20] = ":\"AL_RT^L*.?+6/46"; long long v7 = 28537194573619560; char s[20] = {}; for(int i = 0; i < 17; i++){ s[i] = (char)(*((char*)&v7 + i % 7) ^ v8[i]); } printf("%s",s); } int main(int argc, char const *argv[]) { decode(); return 0; }
|
0x0a no-strings-attached
基本信息
IDA打开看看
没什么特殊的string
看看main函数
setlocale
调用了一个链接来的函数,一般和flag无关
banner
,打印了一些字符,也没什么用
prompt_authentication
也是打印了一些字符,也没用
所以说,如果在这个程序里面有flag的踪迹,那必然是在 authenticate
中
如果ws,即输入的东西,和s2相等,就打印一个东西
不出所料,是 Success
所以思路就很明显了,就构造个东西,让它和s2相等就好
去看看s2是什么
s2 是 decrypt函数返回的东西,看看decrypt
最后返回了个dest,那只要在运行的时候截住dest就可以了
用gdb打开
先看看所有函数
我要的是 decrypt
把 decrypt
给反汇编了,看看
在ret的地方设个断点
然后跑
这时候dest的值肯定是在某个寄存器里面,看看寄存器
一般放在eax、ebx、ecx、edx中的一个里面
回去看看汇编,猜测很可能在eax里面,而且就内容的大小而言,eax的大小最大
看看eax里面的内容
因为dest是一个 wchar_t* ,wchar_t 是一个4byte的数据,所以其对应的字符串,应该是每4个byte为一个单位,每个单位作为一个字符
得到flag
1
| 9447{you_are_an_international_mystery}
|
0x0b csaw2013reversing2
基本信息
跑跑看
没什么,不知道这段东西是不是flag
IDA看看
点进去看看
是main函数
跟heap有关的都没法看到源码,猜测跟flag有关的函数肯定是 sub_401000
而且只有debug的时候会调用,而一旦debug,就最终会breakdebug
分析一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| unsigned int __fastcall sub_401000(int a1, int a2) { int v2; unsigned int v3; unsigned int v4; unsigned int result;
v2 = dword_409B38; v3 = a2 + 1 + strlen((const char *)(a2 + 1)) + 1; v4 = 0; result = ((v3 - (a2 + 2)) >> 2) + 1; if ( result ) { do *(_DWORD *)(a2 + 4 * v4++) ^= v2; while ( v4 < result ); } return result; }
|
a2 就是 lpMem,也就是heap的地址
在 sub_401000
这个函数中,a2被操作了好多次,也就是在heap上操作了好多次,可以猜测,操作完后,heap上的值就与flag有关
只要把__debugbreak()
给无效化,调试的时候看看堆的值就能看到flag了
看看函数的 graph view
正是停止debug的指令,那只要把这个指令改成 nop
,然后再把 后面 jmp指令跳转到的 函数也改一下,就万事大吉了
把 int 3
改成了 nop
loc_4010B9
正是显示heap的内容的函数,即不在debug模式下跳转到的地方
那么修改 jmp后的标号,跳转到这里
改完后的代码
修改完了,打个补丁
然后在debug模式下运行一下就好
跑出flag来
1
| flag{reversing_is_not_that_hard!}
|
0x0c maze
看看基本信息
跑跑看
要输入flag,看起来挺简单
IDA打开看看
这些string,有点意思
跳转到main函数
改成好看一点的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| __int64 __fastcall main(__int64 a1, char **a2, char **a3) { const char *v3; signed __int64 v4; signed int v5; char v6; char v7; const char *v8; __int64 v10; v10 = 0LL; char s1[24] = "nctf{xxxxxxxxxxx}"; v4 = 5LL; while ( 1 ) { v5 = s1[v4]; v6 = 0; if ( v5 > 78 ) { if (v5 == 79 ) { v7 = sub_400650((char *)&v10 + 4, v3); } if ( v5 == 111 ) { v7 = sub_400660((char *)&v10 + 4, v3); } } else { if (v5 == 46 ) { v7 = sub_400670(&v10, v3); } if ( v5 == 48 ) { v7 = sub_400680(&v10, v3); } } v6 = v7; v3 = (const char *)HIDWORD(v10); if ( !(unsigned __int8)sub_400690(asc_601060, HIDWORD(v10), (unsigned int)v10) ) fail(); if ( ++v4 >= strlen(&s1) - 1 ) { if ( v6 ) break; else fail(); } } if ( asc_601060[8 * (signed int)v10 + SHIDWORD(v10)] != 35 ) fail(); succeed(); return 0LL; }
|
最后 v6 = v7,而v6不能等于0,所以v7肯定经过了处理,所以s1[5] 的值就是 46/79/111/48 中的一个,而s1[5]不仅决定了v7还决定了v10
这个while循环会一直进行,遍历{}中的每一个字符,而每一个字符的取值都只有4个可以选 46/79/111/48
,否则v7就为0,导致v6就为0,然后就会提前break,
这里因为不知名的原因 v7变为v6,v10变为v9,v5变为v4
每次操作完还有个条件要满足
1
| asc_601060 = ' ******* * **** * **** * *** *# *** *** *** *********'
|
有64位
a2 为 v9的高位,a3为v9的低位,a1为 asc_601060
的起始地址
最后还有个条件
而asc_601060[8 * (signed int)v10 + SHIDWORD(v10)]
要等于35,也就是 #
,而ord(' ') = 32
所以
1
| 8 * (signed int)v10 + SHIDWORD(v10) = 35
|
79(O)OPQ
v9高位–要大于0
111(o)opq
v9高位++要小于8
46(.)
v9低位–要大于0
48(0)012
v9低位++要小于8
题目里说到走迷宫,v10的位置也总是在变,会不会64位的asc_601060就是个迷宫,而#就是迷宫的出口,每次操作后v10的值要不然是 ‘ ‘ 要不然是 ‘#’,说明’ ‘ 是通道,而* 就自然是障碍物了
用gdb看看
还真像那么一回事
那么乘了8的变量也就是a3,对应的就是上下移动,而另外一个变量a2就是左右移动
a3对应低位,也就对应 .
和 0
,其中,.
是往上,0
是往下
a2对应高位,也就对应 O
和 o
,其中,O
是往左,o
是往右
要走到 0x23的地方,只能途经0x20
1 2 3 4 5 6 7
| s = "→↓→→↓↓←↓↓↓→→→→↑↑←←" m = {'→':'o','↓':'0','↑':'.','←':'O'} r = '' for c in s: r += m[c] print("nctf{"+r+"}")
|
得到flag
1
| nctf{o0oo00O000oooo..OO}
|