这两道题目是我朋友发给我的,题目比较简单,但是考察了二进制汇编基础。
个人觉得基础很重要,所以花了些时间来认真看这道题目,巩固自己的基础。
如果是在线下,没有GPT的帮助,此时就需要选手汇编底子,对题目进行细致的分析。
如果学习汇编有题目带着学,耐心分析,相信收获会很大的,有助于理解汇编操作的逻辑和方法。
开始今天的题目讲解,题目会打包上传
如果有错误,还请指出哈哈
原题目我就不粘出来了哈,附件里面有的,这里粘一下我注释分析后的
题目给的是AT&T风格的x86汇编,那么和我们常见的intel语法有什么不同呢?
(建议自己先看一下原题目,手动分析一下,分析完尝试自己解密,看是否能得到正确的flag)
有了上面的基础之后,开始看题目:
这题有点绕的地方是[rbp-4]、eax和ecx之间的关系
这里给出完整的解题wp,crypto密文呢是题目附件给出了
这里简单的介绍一下ARM汇编,来自个人笔记
ARM汇编是一种面向ARM架构的低级编程语言,用于编写与硬件紧密相关的程序。以下是一些关于ARM汇编的特点:
给出注释后的题目汇编
写出对应的解密脚本
可以看到,这两道题目都判断了奇偶性,不像之前就是简单的异或或者相加相减,这样通过奇偶性判断,更加考察选手对汇编流程的理解。
```@定义了一些辅助信息,比如:文件名,段名称
.
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
:
```@定义了一些辅助信息,比如:文件名,段名称
.
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
:
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
;
}
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
;
}
@ 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,
add r7, sp,
.LBB2:
movs r3,
str
r3, [r7,
b .L2 @无条件跳转到标签.L2处。
.L5: @这是一个标签,标识一个循环的开始(为什么是循环,后面分析即可)
ldr r3, [r7,
and
r3, r3,
cmp
r3,
bne .L3
@从帧指针偏移为
4
的位置加载一个值到r3寄存器,并将其与
1
进行按位与运算。然后,将结果与
0
进行比较,如果不相等,跳转到标签.L3处。
movw r3,
movt r3,
ldr r2, [r7,
add r3, r3, r2 @将r2寄存器的值加到r3寄存器中,修正flag的地址。
ldrb r3, [r3] @ zero_extendqisi2 从r3寄存器中的地址处加载一个值到r3寄存器,并进行零扩展。这个值即为flag的值。
eor r3, r3,
uxtb r3, r3 @将r3寄存器中的值零扩展为一个字节。
mov r1, r3 @将r3寄存器中的值复制到r1寄存器,作为printf函数的第二个参数。
movw r0,
movt r0,
bl printf @调用函数
b .L4 @无条件跳转
.L3: @flag[i] 为奇数 ,不操作
movw r3,
movt r3,
ldr r2, [r7,
add r3, r3, r2 @这里的作用,就是取下标 flag[i] 的意思
ldrb r3, [r3] @ zero_extendqisi2
mov r1, r3 @
movw r0,
movt r0,
bl printf @调用函数
.L4: @从帧指针偏移为
4
的位置加载一个值到r3寄存器,并将其加
1
。然后将结果保存回帧指针偏移为
4
的位置
ldr r3, [r7,
adds r3, r3,
str
r3, [r7,
.L2: @从帧指针偏移为
4
的位置加载一个值到r3寄存器,并将其与
26
进行比较。如果r3小于等于
26
,则跳转到标签.L5处,否则继续执行。
ldr r3, [r7,
cmp
r3,
ble .L5
.LBE2: @将
0
赋值给r3寄存器,将其作为返回值存储到r0寄存器,增加帧指针的值,并将栈指针设置为帧指针
movs r3,
mov r0, r3
adds r7, r7,
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
@ ARM gcc
11.1
(linux)
.Ltext0:
flag:
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!