AssembleCrash

starlitxiling Lv3

Work with register

1


这里还是建议先看视频Assembly Crash Course20220830

看题目要求是要把rdi寄存器的值改为0x1337,这个其实一条mov指令就可以了,但是需要考虑的是怎么被/challenge/run检测到寄存器的值是改变后的呢。

1
vim solve.s
1
2
.intel_syntax noprefix
mov rdi, 0x1337
1
2
3
4
5
gcc -nostdlib -o solve.elf solve.s
objdump -M intel -d solve.elf
objcopy --dump-section .text=solve.bin solve.elf
hd solve.bin
cat solve.bin | /challenge/run
  • objdump -M intel -d solve.elf:此命令反汇编 solve.elf 文件,并以 Intel 语法输出汇编代码,以便检查汇编代码是否正确编译。

  • objcopy --dump-section .text=solve.bin solve.elf:此命令从 solve.elf 中提取 .text 部分(包含可执行代码),并将其写入 solve.bin 文件。

编译脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash



input_string="$1"



as_command="as -o ${input_string}.o ${input_string}.s"

objcopy_command="objcopy -O binary --only-section=.text ./${input_string}.o ./${input_string}.bin"

run_command="cat ./${input_string}.bin | /challenge/run"



eval $as_command

eval $objcopy_command

eval $run_command

or

1
2
3
4
5
#!/bin/bash

name="$1"

as -o "${name}.o" "${name}.s" && objcopy -O binary --only-section=.text ./"${name}.o" ./"${name}.bin" && cat ./"${name}.bin" | /challenge/run

还有一种是jdin佬给出的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import pwn
import glob

pwn.context.arch = "amd64"
pwn.context.encoding = "latin"
pwn.context.log_level = "DEBUG"

# https://stackoverflow.com/questions/69599761/how-to-move-data-between-registers-without-mov-instruction

assembly = """
    lea rax, [rdi]
    and rax, rsi
"""
# print(pwn.asm(assembly))

with pwn.process(glob.glob("/challenge/run")) as target:
    # pwn.info(target.readrepeat(2))
# read one second
    target.send(pwn.asm(assembly))
    pwn.info(target.readrepeat(2))
#read one second

还有一种更适合拿来调试的
详细的使用方式见Assembly Crash Course - Connor - Live Session - 2022.09.19

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pwn



pwn.context.update(arch="arm64")

process = pwn.process("/challenge/run")

process.write(pwn.asm("""

"""
))



print(process.readalls())

3

1
2
3
4

.intel_syntax noprefix
add rdi,0x331337

4

1
2
3
4
.intel_syntax noprefix
imul rdi,rsi
add rdx,rdi
mov rax,rdx

5

1
2
3
4
.intel_syntax noprefix
mov rdx, 0x0
mov rax,rdi
div rsi

6

1
2
3
4
5
.intel_syntax noprefix
mov rdx,0x0
mov rax,rdi
idiv rsi
mov rax,rdx

8

1
2
3
.intel_syntax noprefix
mov al,dil
mov bx,si



Work with bit logic and operations

9

1
2
3
4
.intel_syntax noprefix
mov rax,rdi
shl rax,24
shr rax,56

10

1
2
3
4
5
6
.intel_syntax noprefix

and rdi,rsi
lea rax,[rdi]

; and rax,rdi

11

1
2
3
4
5
6
7
.intel_syntax noprefix

xor rax, rax
and rdi, 1
xor rax,rdi
xor rax, 1

这里有一个tips就是可以使用xor rax, raxrax寄存器清零。(对任何寄存器清零都可以这样)

Work with memory

14

1
2
3
4
5
6
7
.intel_syntax noprefix

mov rax,[0x404000]
mov rbx,rax
add rbx,0x1337
mov [0x404000],rbx

16

1
2
3
4
5
6
.intel_syntax noprefix

mov [0x404000], al
mov [0x404000], bx
mov [0x404000], ecx
mov [0x404000], rdx

17

1
2
3
4
5
6
.intel_syntax noprefix

mov rax, 0xdeadbeef00001337
mov [rdi], rax
mov rax, 0xc0ffee0000
mov [rsi], rax

这里主要是指出了某些汇编语言中(特别是较老的指令集),可能无法直接将一个大的常数值赋给一个解引用的寄存器(即一个内存地址)。这主要是因为直接对内存操作的指令可能不支持大的立即数(即直接编码在指令中的常数值)。

为了解决这个问题,可以采取以下步骤:

  1. 设置寄存器: 首先,将大的常数值赋给一个寄存器。这通常通过 mov 指令来完成,因为 mov 指令通常能够处理立即数到寄存器的赋值。

  2. 赋值到解引用的寄存器: 然后,将包含常数值的寄存器赋给另一个寄存器,该寄存器表示一个内存地址的解引用。这样做通常涉及到对内存的写操作。

18

1
2
3
4
5
6
.intel_syntax noprefix

mov rax, [rdi]
mov rbx, [rdi + 8]
add rax, rbx
mov [rsi], rax

这里是指出了可以通过偏移的方式去访问值,但是要注意偏移量是从0开始的。

Work with stack

19

1
2
3
4
5
.intel_syntax noprefix

pop rax
sub rax, rdi
push rax

这里最开始在想poppush是怎么知道哪块地址是栈的,然后使用mov rsp, address这种形式(这个寄存器存的值代表的栈顶指针的地址)。后面测试的时候发现不能使用mov,去掉之后反应过来本题应该是给设置好了的,不需要自己设置,直接拿值进行处理就好了,poppush后面一般要操作的值存放的位置。

20

1
2
3
4
5
6
.intel_syntax noprefix

push rdi
push rsi
pop rdi
pop rsi

21

1
2
3
4
5
6
7
8
.intel_syntax noprefix

mov rax, [rsp]
add rax, [rsp + 8]
add rax, [rsp + 16]
add rax, [rsp + 24]
shr rax, 2
push rax

Work with control flow manipulation

24

1
2
3
4
5
6
7
8
.intel_syntax noprefix

jmp .+0x53
.fill 0x51, 1, 0x92
;.=.+0x51 ;这条指令可以替代.fill
pop rdi
mov rax, 0x403000
jmp rax

这里题目说跳到当前位置后0x51bytes,但是并没有说明包不包括jmp指令,经过测试应该是包括的,所以我们要跳到0x53。然后为了pop rdi能够在跳转之后的地址中,所以要用fill去填充,不过这里要注意因为要跳0x53,所以要填充0x51(去掉jmp指令本身的2字节)。后面要注意的是不能直接jmp 0x403000,要通过寄存器来实现。

25

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
.intel_syntax noprefix

mov eax, [rdi]
cmp eax, 0x7f454c46
je match_1
cmp eax, 0x00005A4D
je match_2

mov eax, [rdi+4]
imul eax, [rdi+8]
imul eax, [rdi+12]
jmp end

match_1:
mov eax, [rdi+4]
add eax, [rdi+8]
add eax, [rdi+12]
jmp end

match_2:
mov eax, [rdi+4]
sub eax, [rdi+8]
sub eax, [rdi+12]


end:
mov eax, eax

这里就是正常按照题目逻辑写就好了,但是要注意使用eax而不是rax,似乎是因为使用rax它的高位也会被补齐,导致过不了检测,使用eax则之后填进低32位,高32位会自动清零。

26

1
2
3
4
5
6
7
8
.intel_syntax noprefix

cmp rdi, 0x3
ja be
jmp [rsi+rdi*8]
be:
jmp [rsi+0x20]

这里根据题目的示例构建jmp table表就好了,就是本题在执行的时候要等待200s之后才会给出flag,所以卡住了可能不是代码的问题。这里最后的jmp [rsi+0x20]也是根据题目中的示例表算出来的。

27

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.intel_syntax noprefix

xor rax, rax
mov rcx, rsi
mov rdx, rdi

loop_start:
cmp rcx, 0
je calculate_average
add rax, [rdx]
add rdx, 8
dec rcx
jmp loop_start

calculate_average:
xor rdx, rdx
div rsi

这里要先看how-to-make-a-loop-in-x86-assembly-language
然后将给的值移到对应的寄存器中然后进行计算。计算平均值的时候要注意要对rdx进行清零,这里是因为x86-64架构中div的工作方式:

  1. 128位除数div 指令执行的是 64 位无符号整数的除法。不过,它不仅仅使用 rax 作为被除数。实际上,它将 rdxrax 合并作为一个 128 位的整数来处理。这个 128 位整数是通过将 rdx 作为高 64 位,rax 作为低 64 位来构成的。

  2. **需要清零 rdx**:如果 rdx 寄存器中有非零值,那么 div 指令会将这个值作为被除数的一部分。这通常会导致不正确的结果,特别是在您只希望用 rax 中的值作为被除数时。因此,在执行 div 指令之前,通常需要将 rdx 清零,以确保被除数只包含 rax 中的值。

  3. 防止除法溢出:如果 rdx 中包含非零值,那么合并后的 128 位整数可能会非常大,导致除法操作溢出。这种溢出会触发运行时错误。通过清零 rdx,您可以避免这种情况,确保除法操作安全地进行。

28


错误代码:

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
.intel_syntax noprefix

mov rax, 0
mov rcx, 10


loop_start:
test rdi, rdi
jz set_rax_to_zero

cmp byte ptr [rdi], 0
jne increment_counter

inc rdi
dec rcx
jnz loop_start

increment_counter:
inc rax
jmp next_byte

next_byte:
inc rdi
dec rcx
jnz loop_start

set_rax_to_zero:
xor rax, rax
inc rdi
dec rcx
jmp loop_start

正确代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.intel_syntax noprefix

mov rax, 0
cmp rdi, 0
je done

loop:
cmp byte ptr [rdi], 0
je done
add rax, 1
add rdi, 1
jmp loop
done:
nop

本题的相关video
这里要明确一下题目的意思,一开始是误解了意思一直写的是题目中的那个while循环,但是不是,本题的名字为Implementing strlen。就是从给的地址开始一直数数, 如果遇到rdi中存储的地址的指向的值(dereference)为0(类似strlen函数是遇到\0),则结束循环,然后返回计数。明确了这个之后这道题就很好完成了,代码如上所示。不过要记得事先比较一下rdi是否为0(当然你可以尝试不加这个去执行然后用自己的方式去debug试试,会学到一些东西的呢!!!)

Work with functions

29

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
.intel_syntax noprefix

mov rax, 0
cmp rdi, 0
je done

loop:
mov rbx, 0
mov bl, [rdi]
cmp bl, 0
je done

cmp bl, 0x5a
jg greater

push rdi
push rax
mov rdi, 0
mov dil, bl
mov r10, 0x403000
call r10
mov bl, al
pop rax
pop rdi
mov [rdi], bl
add rax, 1

greater:
add rdi, 1
jmp loop

done:
int3
ret

相关video
这里主要是延续了上一个题的循环,然后再加上了函数调用。关于函数调用,我们需要知道一些东西:1.函数从rdi中拿第一个参数,从rsi中拿第二个参数。2.函数的返回值存在rax中


所以这题的难点就在于处理函数的参数,因为有的值我们是放在rdi、rax中,这里我们采用的是通过寄存器加栈的方式去先存储值,等调用函数结束后再将要放在rdi、rax中的值放回去。解决了这个这题也就解决了。

30

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
.intel_syntax noprefix

push 0
mov rbp, rsp

mov rax, -1
sub rsi, 1
sub rsp, rsi
loop1:
add rax, 1
cmp rax, rsi
jg next
nop
mov rcx, 0
mov cl, [rdi+rax]
mov r11, rbp
sub r11, rcx
mov dl, [r11]
add dl, 1
mov [r11], dl
jmp loop1
nop

next:
mov rax, 0
mov rbx, rax
mov rcx, rax
mov ax, -1

loop2:
add ax, 1
cmp ax, 0xff
jg return
nop
mov r11, rbp
sub r11, rax
mov dl, [r11]
cmp dl, bl
jle loop2
nop
mov bl, dl
mov cl, al
jmp loop2
nop

return:
mov rax, rcx
mov rsp, rbp
pop rbx
ret

逻辑很简单,就是维护各种值写起来实在是太恶心了,本质逻辑就是利用桶排序来找到出现次数最多的字节。

  • Title: AssembleCrash
  • Author: starlitxiling
  • Created at : 2023-11-10 19:20:02
  • Updated at : 2024-09-14 22:05:29
  • Link: http://starlitxiling.github.io/2023/11/10/AssembleCrash/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments