【CTF对抗-由两道CTF汇编题目来学习ARM和X86汇编】此文章归类为:CTF对抗。
这两道题目是我朋友发给我的,题目比较简单,但是考察了二进制汇编基础。
个人觉得基础很重要,所以花了些时间来认真看这道题目,巩固自己的基础。
如果是在线下,没有GPT的帮助,此时就需要选手汇编底子,对题目进行细致的分析。
如果学习汇编有题目带着学,耐心分析,相信收获会很大的,有助于理解汇编操作的逻辑和方法。
开始今天的题目讲解,题目会打包上传
如果有错误,还请指出哈哈
原题目我就不粘出来了哈,附件里面有的,这里粘一下我注释分析后的
题目给的是AT&T风格的x86汇编,那么和我们常见的intel语法有什么不同呢?
movl %eax, %ebx
表示将eax
寄存器中的值移动到ebx
寄存器中,而在Intel语法中,相应的指令为mov ebx, eax
。%
开头,而在Intel语法中没有前缀。例如,在AT&T语法中,%eax
表示EAX寄存器,而在Intel语法中,相应的寄存器名称为EAX
。$
前缀,而内存地址使用方括号[]
包围。例如,movl $0x123, %eax
表示将立即数0x123
移动到eax
寄存器中,而movl (%ebx), %eax
表示将ebx
寄存器指向的内存地址中的数据移动到eax
寄存器中。movsx
或movzx
指令来进行。例如,在AT&T语法中,movb -1(%eax), %bl
表示将eax-1
地址处的字节符号扩展后移动到bl
寄存器中,而在Intel语法中,相应的指令应为movsx bl, byte ptr [eax-1]
。(建议自己先看一下原题目,手动分析一下,分析完尝试自己解密,看是否能得到正确的flag)
有了上面的基础之后,开始看题目:
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
|
```@定义了一些辅助信息,比如:文件名,段名称
.
file
"main.c"
.text
.section .rodata
.align
32
.
type
encode, @
object
.size encode,
39
encode:
.string
"**************************************"
.text
.globl main @将main标记为全局可见的。
.
type
main, @function @声明main是一个函数。
main:
.LFB0:
.cfi_startproc @CFI (Call Frame Information)指令,用于生成调试信息。
endbr64 @对应特定的处理器指令,提供分支目标地址的溢出检测。
pushq
%
rbp
.cfi_def_cfa_offset
16
.cfi_offset
6
,
-
16
movq
%
rsp,
%
rbp
.cfi_def_cfa_register
6
subq $
16
,
%
rsp @rsp
=
rsp
-
16
@堆栈操作指令,用于保存和恢复寄存器的值。 上面除去CFI指令后是经典的开辟栈空间指令
movl $
0
,
-
4
(
%
rbp) @ [rbp
-
4
]
=
0
jmp .L2 @无条件跳转到L2处开始执行
@L5
由下文的分析可知道,这是个循环
.L5:
addl $
1
,
-
4
(
%
rbp) @[rbp
-
4
]
+
+
.L2:
movl
-
4
(
%
rbp),
%
eax @eax
=
[rbp
-
4
]
cltq @将eax寄存器的低
32
位符号扩展为
64
位,结果存储在rax寄存器中(在x86
-
64
中,eax是低
32
位的rax)。
leaq encode(
%
rip),
%
rdx @rdx
=
encode[rip] 就是将flag传给rdx
movzbl (
%
rax,
%
rdx),
%
eax @从以rax
+
rdx为基址的内存地址中读取一个字节,并将其零扩展为
32
位。然后将结果存储在eax寄存器中
movsbl
%
al,
%
ecx @ ECX
=
EAX
movl
-
4
(
%
rbp),
%
eax @ eax
=
[rbp
-
4
]
andl $
1
,
%
eax @ 判断奇偶性
testl
%
eax,
%
eax
je .L3 @ 若为偶数,则跳转到L3
movl
-
4
(
%
rbp),
%
eax @ eax
=
[rbp
-
4
]
subl $
1
,
%
eax @ eax
=
eax
-
1
cltq
leaq encode(
%
rip),
%
rdx @rdx
=
encode[rip]
movzbl (
%
rax,
%
rdx),
%
eax @eax
=
[rax
+
rdx] rax相当于下标i
movsbl
%
al,
%
eax
jmp .L4
.L3:
movl
-
4
(
%
rbp),
%
eax @eax
=
[rbp
-
4
]
.L4:
xorl
%
ecx,
%
eax @eax
=
eax ^ ecx
movl
%
eax,
%
edi @edi
=
eax
call putchar@PLT @putchar
movl
-
4
(
%
rbp),
%
eax @eax
=
[rbp
-
4
]
cltq
cmpq $
37
,
%
rax @
cmp
(
37
,rax)
jbe .L5 @rax<
37
则跳转到L5 ,继续循环
movl $
0
,
%
eax @eax
=
0
leave
.cfi_def_cfa
7
,
8
ret
.cfi_endproc
@一些调试信息
.LFE0:
.size main, .
-
main
.ident
"GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
.section .note.GNU
-
stack,"",@progbits
.section .note.gnu.
property
,
"a"
.align
8
.
long
1f
-
0f
.
long
4f
-
1f
.
long
5
0
:
.string
"GNU"
1
:
.align
8
.
long
0xc0000002
.
long
3f
-
2f
2
:
.
long
0x3
3
:
.align
8
4
:
|
这题有点绕的地方是[rbp-4]、eax和ecx之间的关系
这里给出完整的解题wp,crypto密文呢是题目附件给出了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int
main() {
char crypto[]
=
{
0x66
,
0x0a
,
0x63
,
0x06
,
0x7f
,
0x1e
,
0x37
,
0x00
,
0x38
,
0x03
,
0x6f
,
0x04
,
0x6e
,
0x56
,
0x3d
,
0x55
,
0x22
,
0x06
,
0x26
,
0x51
,
0x72
,
0x04
,
0x21
,
0x03
,
0x21
,
0x01
,
0x7c
,
0x05
,
0x2b
,
0x0e
,
0x7c
,
0x50
,
0x17
,
0x56
,
0x10
,
0x0b
,
0x16
,
0x4f
,
0x26
};
for
(size_t i
=
0
; i <
39
; i
+
+
)
{
if
(i
%
2
=
=
0
)
/
/
偶数
{
printf(
"%c"
, crypto[i] ^i);
}
else
{
char tmp
=
i;
crypto[i
-
1
]
=
crypto[i
-
1
] ^ (i
-
1
);
printf(
"%c"
, crypto[i
-
1
] ^ crypto[i]);
}
}
return
0
;
}
|
给出注释后的题目汇编
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
|
@ ARM gcc
11.1
(linux)
.Ltext0:
flag:
.ascii
"***************************\000"
.LC0:
.ascii
"%02x\000"
main:
.LFB0:
@ 这是注释,表示该函数不接受任何参数,使用一个
8
字节大小的栈帧,并且需要保存现场数据。
@ args
=
0
, pretend
=
0
, frame
=
8
@ frame_needed
=
1
, uses_anonymous_args
=
0
push {r7, lr} @保存r7和lr寄存器的值到栈中,以便后续恢复现场使用。
sub sp, sp,
#8 @在栈上分配8个字节的空间。
add r7, sp,
#0 @将栈指针保存到r7寄存器中,形成一个帧指针。
.LBB2:
movs r3,
#0
str
r3, [r7,
#4] @将0赋值给r3寄存器,并将其存储到帧指针偏移为4的位置。
b .L2 @无条件跳转到标签.L2处。
.L5: @这是一个标签,标识一个循环的开始(为什么是循环,后面分析即可)
ldr r3, [r7,
#4]
and
r3, r3,
#1
cmp
r3,
#0
bne .L3
@从帧指针偏移为
4
的位置加载一个值到r3寄存器,并将其与
1
进行按位与运算。然后,将结果与
0
进行比较,如果不相等,跳转到标签.L3处。
movw r3,
#:lower16:flag @将全局变量flag的低16位地址加载到r3寄存器。
movt r3,
#:upper16:flag @将全局变量flag的高16位地址加载到r3寄存器的高16位。
ldr r2, [r7,
#4] @从帧指针偏移为4的位置加载一个值到r2寄存器。这个值可能是一个偏移量,用于对r3寄存器中的地址进行修正。
add r3, r3, r2 @将r2寄存器的值加到r3寄存器中,修正flag的地址。
ldrb r3, [r3] @ zero_extendqisi2 从r3寄存器中的地址处加载一个值到r3寄存器,并进行零扩展。这个值即为flag的值。
eor r3, r3,
#57 @将r3寄存器的值与57进行按位异或操作。
uxtb r3, r3 @将r3寄存器中的值零扩展为一个字节。
mov r1, r3 @将r3寄存器中的值复制到r1寄存器,作为printf函数的第二个参数。
movw r0,
#:lower16:.LC0 @相同的道理,加载字符串
movt r0,
#:upper16:.LC0
bl printf @调用函数
b .L4 @无条件跳转
.L3: @flag[i] 为奇数 ,不操作
movw r3,
#:lower16:flag
movt r3,
#:upper16:flag
ldr r2, [r7,
#4]
add r3, r3, r2 @这里的作用,就是取下标 flag[i] 的意思
ldrb r3, [r3] @ zero_extendqisi2
mov r1, r3 @
movw r0,
#:lower16:.LC0
movt r0,
#:upper16:.LC0
bl printf @调用函数
.L4: @从帧指针偏移为
4
的位置加载一个值到r3寄存器,并将其加
1
。然后将结果保存回帧指针偏移为
4
的位置
ldr r3, [r7,
#4]
adds r3, r3,
#1
str
r3, [r7,
#4]
.L2: @从帧指针偏移为
4
的位置加载一个值到r3寄存器,并将其与
26
进行比较。如果r3小于等于
26
,则跳转到标签.L5处,否则继续执行。
ldr r3, [r7,
#4]
cmp
r3,
#26
ble .L5
.LBE2: @将
0
赋值给r3寄存器,将其作为返回值存储到r0寄存器,增加帧指针的值,并将栈指针设置为帧指针
movs r3,
#0
mov r0, r3
adds r7, r7,
#8
mov sp, r7
@ sp needed
pop {r7, pc}
.LFE0:
.Letext0:
.Ldebug_info0:
.Ldebug_abbrev0:
.Ldebug_line0:
.LASF6:
.LASF10:
.LASF0:
.LASF11:
.LASF3:
.LASF7:
.LASF9:
.LASF1:
.LASF15:
.LASF8:
.LASF12:
.LASF2:
.LASF14:
.LASF5:
.LASF13:
.LASF4:
@
7d416a436d4642315c740c64095f7872745f6d720d315769574744
|
写出对应的解密脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#include <stdio.h>
char flag[]
=
{
0x7d
,
0x41
,
0x6a
,
0x43
,
0x6d
,
0x46
,
0x42
,
0x31
,
0x5c
,
0x74
,
0x0c
,
0x64
,
0x09
,
0x5f
,
0x78
,
0x72
,
0x74
,
0x5f
,
0x6d
,
0x72
,
0x0d
,
0x31
,
0x57
,
0x69
,
0x57
,
0x47
,
0x44
};
/
/
这里的字符串内容应该与实际代码中的 flag 相同
int
main() {
int
i
=
0
;
while
(i <
=
26
) {
if
((i &
1
) !
=
0
) {
/
/
如果 i 是奇数则直接输出
printf(
"%c"
, flag[i]);
}
else
{
printf(
"%c"
, flag[i] ^
57
);
}
i
+
+
;
}
return
0
;
}
|
可以看到,这两道题目都判断了奇偶性,不像之前就是简单的异或或者相加相减,这样通过奇偶性判断,更加考察选手对汇编流程的理解。
更多【CTF对抗-由两道CTF汇编题目来学习ARM和X86汇编】相关视频教程:www.yxfzedu.com