9. 程序跳转指令: level 22 ~ level 24
# jmp 跳转绝对地址需要把地址放入寄存器接上jmp命令。 mov r12,0x403000 jmp r12 ---------------- CODE ---------------- 0x400071: mov r12, 0x403000 0x400078: jmp r12 --------------------------------------
间接跳转到所在地址后面0x51地址,并在该地址将 rax 赋值为 0x1。
汇编代码文件后缀:.S .asm; 后期编译会进行预处理,.s文件属于中间文件,不进行预处理。
.global _start # 头文件----- _start: # ----------- .intel_syntax noprefix # ----- _start: jmp next .rept 0x51 nop .endr next: mov rax,1
as -o asm.o asm.S && objcopy -O binary --only-section=.text ./asm.o ./asm.bin && cat ./asm.bin | /challenge/run
.global _start _start: .intel_syntax noprefix _start: jmp next .rept 0x51 nop .endr next: mov rdi,[rsp] # 注意 mov rdi,rsp 传递的是rsp地址而不是该地址保存的值 mov rax,0x403000 # 因为 rsp作为指针,保存一个地址,地址保存数据 jmp rax
10. 分支循环语句: level 25~level 28
Using the above knowledge, implement the following: if [x] is 0x7f454c46: y = [x+4] + [x+8] + [x+12] else if [x] is 0x00005A4D: y = [x+4] - [x+8] - [x+12] else: y = [x+4] * [x+8] * [x+12] where: x = rdi, y = rax.
mov eax,[rdi] # 如果想使用 rax,rbx,rcx。可以试试xor清零 cmp eax,0x7f454c46 je one nop mov eax,[rdi] cmp eax,0x00005A4D je two nop # nop貌似不必要 mov eax,[rdi+4] imul eax,[rdi+8] imul eax,[rdi+12] jmp done # 使用je/jg等跳转进入分支语句之后, # 注意执行完分支语句会按顺序向下执行,注意不要混淆执行流!!! one: # 在结尾放一个 done分支,每个if分支执行完之后跳转到后面可以避免 mov eax,[rdi+4] add eax,[rdi+8] add eax,[rdi+12] jmp done two: mov eax,[rdi+4] sub eax,[rdi+8] sub eax,[rdi+12] done: and eax,eax
mov ebx,[rdi+4] mov ecx,[rdi+8] mov edx,[rdi+12] mov eax,[rdi] cmp eax,0x7f454c46 je con1 nop mov eax,[rdi] cmp eax,0x00005A4D je con2 nop imul ebx,ecx imul ebx,edx jmp done nop con1: add ebx,ecx add ebx,edx jmp done nop con2: sub ebx,ecx sub ebx,edx done: mov eax,ebx
level 26:基地址跳转,指令优化 (⭐)
In the above example, the jump table could look like: [0x1337] = address of do_thing_0 [0x1337+0x8] = address of do_thing_1 [0x1337+0x10] = address of do_thing_2 [0x1337+0x18] = address of do_default_thing Using the jump table, we can greatly reduce the amount of cmps we use. Now all we need to check is if `number` is greater than 2. If it is, always do: jmp [0x1337+0x18] Otherwise: jmp [jump_table_address + number * 8] Using the above knowledge, implement the following logic: if rdi is 0: jmp 0x403019 else if rdi is 1: jmp 0x4030c9 else if rdi is 2: jmp 0x4031d2 else if rdi is 3: jmp 0x403287 else: jmp 0x403378 Please do the above with the following constraints: Assume rdi will NOT be negative Use no more than 1 cmp instruction Use no more than 3 jumps (of any variant) We will provide you with the number to 'switch' on in rdi. We will provide you with a jump table base address in rsi. Here is an example table: [0x40405b] = 0x403019 (addrs will change) [0x404063] = 0x4030c9 [0x40406b] = 0x4031d2 # == [0x404073] = 0x403287 [0x40407b] = 0x403378 # ==
# rsi 作为基地址0x40405b,[rsi]= 0x403019 mov rax,rdi and rax,0xfffffffffffffffc # 最低两位全0;作用,清空最低两位数据 # 若 rdi == 0~3,得到0,执行跳转 je nomal # je指令,隐式比较 rax 与 0,若 rdi>=4,不执行跳转 nop jmp [rsi+32] # >=4: 0x40407b-0x40405b == 0x20 nop nomal: # 0: 0x40405b-0x40405b == 0x0 == 0x0*8 jmp [rsi+rdi*8] # 1: 0x404073-0x40405b == 0x8 == 0x1*8 nop # 2: 0x40405b-0x40405b == 0x10 == 0x2*8 # 3: 0x404073-0x40405b == 0x18 == 0x3*8
As an example, a for-loop can be used to compute the sum of the numbers 1 to n: sum = 0 i = 1 while i <= n: sum += i i += 1 Please compute the average of n consecutive quad words, where: rdi = memory address of the 1st quad word rsi = n (amount to loop for) rax = average computed We will now set the following in preparation for your code: [0x4042f0:0x404498] = {n qwords]} rdi = 0x4042f0 rsi = 53
xor rax,rax mov rbx,0 one: cmp rbx,rsi jle loop nop jmp done nop loop: add rax,[rdi+rbx*0x8] add rbx,0x1 jmp one done: div rsi
As an example, say we had a location in memory with adjacent numbers and we wanted to get the average of all the numbers until we find one bigger or equal to 0xff: average = 0 i = 0 while x[i] < 0xff: average += x[i] i += 1 average /= i Using the above knowledge, please perform the following: Count the consecutive non-zero bytes in a contiguous region of memory, where: rdi = memory address of the 1st byte rax = number of consecutive non-zero bytes Additionally, if rdi = 0, then set rax = 0 (we will check)! An example test-case, let: rdi = 0x1000 [0x1000] = 0x41 [0x1001] = 0x42 [0x1002] = 0x43 [0x1003] = 0x00 then: rax = 3 should be set We will now run multiple tests on your code, here is an example run: (data) [0x404000] = {10 random bytes}, rdi = 0x404000
xor rax,rax # 校验 rdi 是否为0【可能不必要】 cmp rdi,0x0 je done nop mov rbx,0x0 loop: mov rcx,[rdi+rbx] cmp rcx,0x0 je done nop add rax,0x1 add rbx,0x1 jmp loop nop done: and rax,rax
11. 程序栈调用:level 29~level 30
level 29(⭐)
Functions use the instructions "call" and "ret". The "call" instruction pushes the memory address of the next instruction onto the stack and then jumps to the value stored in the first argument. Let's use the following instructions as an example: 0x1021 mov rax, 0x400000 0x1028 call rax 0x102a mov [rsi], rax 1. call pushes 0x102a, the address of the next instruction, onto the stack. 2. call jumps to 0x400000, the value stored in rax. The "ret" instruction is the opposite of "call". ret pops the top value off of the stack and jumps to it. Let's use the following instructions and stack as an example: Stack ADDR VALUE 0x103f mov rax, rdx RSP + 0x8 0xdeadbeef 0x1042 ret RSP + 0x0 0x0000102a Here, ret will jump to 0x102a Please implement the following logic: str_lower(src_addr): i = 0 if src_addr != 0: while [src_addr] != 0x00: if [src_addr] <= 0x5a: [src_addr] = foo([src_addr]) i += 1 src_addr += 1 return i foo is provided at 0x403000. foo takes a single argument as a value and returns a value. All functions (foo and str_lower) must follow the Linux amd64 calling convention (also known as System V AMD64 ABI): https://en.wikipedia.org/wiki/X86_calling_conventions#System_V_AMD64_ABI Therefore, your function str_lower should look for src_addr in rdi and place the function return in rax. An important note is that src_addr is an address in memory (where the string is located) and [src_addr] refers to the byte that exists at src_addr. Therefore, the function foo accepts a byte as its first argument and returns a byte. We will now run multiple tests on your code, here is an example run: (data) [0x404000] = {10 random bytes}, rdi = 0x404000
mov rax,0 mov rsi,rdi cmp rsi,0 je done nop loop: mov bl,[rsi] cmp bl,0 je done nop cmp bl,90 ja next nop mov dil,bl ;将地址发给rdi,因为foo函数的参数从rdi引用 mov rdx,rax ;调用函数前保存rax的值 mov rcx,0x403000 call rcx mov [rsi],al mov rax,rdx ;使用之后再赋回rax的值,当然也可以直接使用其他寄存器,在函数最后返回时赋给rax add rax,1 next: add rsi,1 jmp loop nop done: ret
level 30(⭐)
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
参考wp
pwncollege通关笔记:3.Assembly Refresher(从0开始学习pwn) - FreeBuf网络安全行业门户