reverse复现之攻防世界新手区

[toc]

菜鸡最近开始学二进制了,先做几道简单的逆向题练练手(12道)

0x01 simple-unpack

看看有没有壳

image-20210108223404332

有壳,还提示我去用 upx -d 命令来脱壳

那我就恭敬不如从命

image-20210108223456948

然后再用ida打开看看

Shift + F12

image-20210108223602154

不要太简单

0x02 insanity

32位的,拖入IDA,Shift + F12

看到flag

0x03 python-trade

一个pyc文件

反编译了

image-20210108225147600

看看

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

分析一波

image-20210108232651938

在python3 和 python2 中,base64.b64decode返回的东西是不一样的,python2 ,返回的是str,而python3 返回的是bytes,一般为了操作上的方便,用python2会比较好

image-20210108232535640

写脚本,用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

image-20210108232909648

PE看一看,32位的程序,vsc++写的,没有壳

跑跑看

image-20210108233116816

IDA看看

读读程序(逆向分析)

image-20210108234538077

从终端输入v9

和 v5比较,目的是打印aFlag

看到会打印 aFlag ,推测存在程序里面

直接notepad++打开,关键字搜索

image-20210108235526990

找到flag

0x05 game

image-20210109000232500

32位的,没有壳

IDA看看

1
sub_45A7BE

这个函数是打印用的

找到可能打印flag的地方

image-20210114160545303

这里有打印东西的函数

image-20210114160614798

说明这个 v3所代表的字符串就会是我们的 flag

分析一下,v1到v59,和v60到v116这些变量是连着的,即应该用一个两个数组分开装

如果直接复制下来当做C直接跑,那么变量定义的时候就不会有这样连着的结构

image-20210114165040790

本来是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")

image-20210114171141654
1
zsctf{T9is_tOpic_1s_v5ry_int7resting_b6t_others_are_n0t}

0x06 getit

image-20210114171412002
  • elf文件格式

  • 64bit

IDA打开看看

image-20210114174546968

像极了flag,点开看看

image-20210114174626868

有一个全局变量t,t的内容为

1
0x53+harifCTF{????????????????????????????????}+0x00

看看t在哪里

image-20210114174905052

分析一下

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;
}
image-20210114182653821
1
SharifCTF{b70c59275fcfa8aebf2d5911223c6589}

0x07 Hello,CTF

image-20210114182930474
  • PE
  • 32bit
  • 没有壳
image-20210114183056163

跑一跑

用IDA打开,先看看String,没有flag,但是看到个success,猜测和flag有关

image-20210114183252904

定位一下看看

image-20210114183403848 image-20210114183420863

找到了对应的函数

分析一下

重要的代码,是一个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);
}
image-20210114185534729

要让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是我们输入的东西

image-20210114185731712
1
asc_408044 = "%x"

一共进行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
image-20210114213813815
1
CrackMeJustForFun

这个就是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;
}
image-20210114223116010 image-20210114223201111

flag 就是 c0ffee

0x09 logmein

基本信息

image-20210114223347082
  • elf
  • 64bit
  • 没有壳

跑一跑看

image-20210114223547117

猜测,输入的guess就是flag

IDA看一下

找成功标志

image-20210114223727163

找到对应的位置

image-20210114223815169

看看哪个函数调用了这个函数

先看看main

image-20210114224220820

分析一下

改写一下重要的代码

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);//len(s)>=17
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

image-20210114230914729
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;
}
image-20210114232134871
1
RC3-2016-XORISGUD

0x0a no-strings-attached

基本信息

image-20210115110405143
  • elf
  • 32bit
  • 没有壳

IDA打开看看

image-20210115110912605

没什么特殊的string

看看main函数

image-20210115151931993

setlocale调用了一个链接来的函数,一般和flag无关

banner,打印了一些字符,也没什么用

prompt_authentication 也是打印了一些字符,也没用

所以说,如果在这个程序里面有flag的踪迹,那必然是在 authenticate

image-20210115160218566

如果ws,即输入的东西,和s2相等,就打印一个东西

image-20210115160308344

不出所料,是 Success

所以思路就很明显了,就构造个东西,让它和s2相等就好

去看看s2是什么

s2 是 decrypt函数返回的东西,看看decrypt

image-20210115160606659

最后返回了个dest,那只要在运行的时候截住dest就可以了

用gdb打开

先看看所有函数

1
info functions
image-20210115163042700

我要的是 decrypt

decrypt 给反汇编了,看看

1
disassemble decrypt
image-20210115163157497

在ret的地方设个断点

image-20210115163310786

然后跑

1
r
image-20210115163337572

这时候dest的值肯定是在某个寄存器里面,看看寄存器

1
info r
image-20210115163454656

一般放在eax、ebx、ecx、edx中的一个里面

回去看看汇编,猜测很可能在eax里面,而且就内容的大小而言,eax的大小最大

看看eax里面的内容

因为dest是一个 wchar_t* ,wchar_t 是一个4byte的数据,所以其对应的字符串,应该是每4个byte为一个单位,每个单位作为一个字符

1
x/sw $eax
image-20210115164339106

得到flag

1
9447{you_are_an_international_mystery}

0x0b csaw2013reversing2

基本信息

image-20210115165002691
  • PE
  • 32bit
  • 没有壳

跑跑看

image-20210115165206486

没什么,不知道这段东西是不是flag

IDA看看

image-20210115165507817

点进去看看

是main函数

image-20210115170546444

跟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; // esi
unsigned int v3; // eax
unsigned int v4; // ecx
unsigned int result; // eax

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;
}
image-20210115171659131

a2 就是 lpMem,也就是heap的地址

sub_401000这个函数中,a2被操作了好多次,也就是在heap上操作了好多次,可以猜测,操作完后,heap上的值就与flag有关

只要把__debugbreak() 给无效化,调试的时候看看堆的值就能看到flag了

看看函数的 graph view

image-20210115213920819
1
int 3

正是停止debug的指令,那只要把这个指令改成 nop,然后再把 后面 jmp指令跳转到的 函数也改一下,就万事大吉了

image-20210115214420041

int 3 改成了 nop

loc_4010B9 正是显示heap的内容的函数,即不在debug模式下跳转到的地方

那么修改 jmp后的标号,跳转到这里

改完后的代码

image-20210115214818429 image-20210115214832160

修改完了,打个补丁

image-20210115215046453

然后在debug模式下运行一下就好

image-20210115215235814

跑出flag来

1
flag{reversing_is_not_that_hard!}

0x0c maze

看看基本信息

image-20210115215701998
  • elf
  • 64bit
  • 没壳

跑跑看

image-20210115220522054

要输入flag,看起来挺简单

IDA打开看看

image-20210115220711472

这些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; // rsi
signed __int64 v4; // rbx
signed int v5; // eax
char v6; // bp
char v7; // al
const char *v8; // rdi
__int64 v10; // [rsp+0h] [rbp-28h]
v10 = 0LL;
char s1[24] = "nctf{xxxxxxxxxxx}";
//len(s1)==24;s1={nctf***********}
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,

image-20210115225936798

这里因为不知名的原因 v7变为v6,v10变为v9,v5变为v4

每次操作完还有个条件要满足

image-20210115232635638
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看看

image-20210116004351312

还真像那么一回事

那么乘了8的变量也就是a3,对应的就是上下移动,而另外一个变量a2就是左右移动

a3对应低位,也就对应 .0,其中,. 是往上,0 是往下

a2对应高位,也就对应 Oo,其中,O 是往左,o 是往右

要走到 0x23的地方,只能途经0x20

image-20210116011758755
1
→↓→→↓↓←↓↓↓→→→→↑↑←←
image-20210116011900627
1
2
3
4
5
6
7
# -*- coding: UTF-8 -*-
s = "→↓→→↓↓←↓↓↓→→→→↑↑←←"
m = {'→':'o','↓':'0','↑':'.','←':'O'}
r = ''
for c in s:
r += m[c]
print("nctf{"+r+"}")

得到flag

1
nctf{o0oo00O000oooo..OO}