Work with register 1 这里还是建议先看视频Assembly Crash Course20220830
看题目要求是要把rdi
寄存器的值改为0x1337
,这个其实一条mov
指令就可以了,但是需要考虑的是怎么被/challenge/run
检测到寄存器的值是改变后的呢。
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
编译脚本 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 pwnimport globpwn.context.arch = "amd64" pwn.context.encoding = "latin" pwn.context.log_level = "DEBUG" assembly = """ lea rax, [rdi] and rax, rsi """ with pwn.process(glob.glob("/challenge/run" )) as target: target.send(pwn.asm(assembly)) pwn.info(target.readrepeat(2 ))
还有一种更适合拿来调试的 : 详细的使用方式见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, rax
对rax
寄存器清零。(对任何寄存器清零都可以这样 )
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
这里主要是指出了某些汇编语言中(特别是较老的指令集),可能无法直接将一个大的常数值赋给一个解引用的寄存器(即一个内存地址)。这主要是因为直接对内存操作的指令可能不支持大的立即数(即直接编码在指令中的常数值)。
为了解决这个问题,可以采取以下步骤:
设置寄存器: 首先,将大的常数值赋给一个寄存器。这通常通过 mov
指令来完成,因为 mov
指令通常能够处理立即数到寄存器的赋值。
赋值到解引用的寄存器: 然后,将包含常数值的寄存器赋给另一个寄存器,该寄存器表示一个内存地址的解引用。这样做通常涉及到对内存的写操作。
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
这里最开始在想pop
和push
是怎么知道哪块地址是栈的,然后使用mov rsp, address
这种形式(这个寄存器存的值代表的栈顶指针的地址 )。后面测试的时候发现不能使用mov
,去掉之后反应过来本题应该是给设置好了的,不需要自己设置,直接拿值进行处理就好了,pop
和push
后面一般要操作的值存放的位置。
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
的工作方式:
128位除数 :div
指令执行的是 64 位无符号整数的除法。不过,它不仅仅使用 rax
作为被除数。实际上,它将 rdx
和 rax
合并作为一个 128 位的整数来处理。这个 128 位整数是通过将 rdx
作为高 64 位,rax
作为低 64 位来构成的。
**需要清零 rdx
**:如果 rdx
寄存器中有非零值,那么 div
指令会将这个值作为被除数的一部分。这通常会导致不正确的结果,特别是在您只希望用 rax
中的值作为被除数时。因此,在执行 div
指令之前,通常需要将 rdx
清零,以确保被除数只包含 rax
中的值。
防止除法溢出 :如果 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
逻辑很简单,就是维护各种值写起来实在是太恶心了,本质逻辑就是利用桶排序来找到出现次数最多的字节。