少说多听
level1
Set a register(设置一个寄存器)
题目描述中提到要让rdi的值为0x1337,直接输入mov rdi, 0x1337
回显需要输入bin二进制文件
使用python的pwntools进行构造,context.arch必须设置,否则asm不会解析汇编为当前架构
运行代码
from pwn import *
context.arch='amd64'
p=process('/challenge/run')
p.recvline()
p.send(asm('mov rdi,0x1337'))
print(p.readallS())
level2
Set multiple registers(设置多个寄存器)
使用三行代码分别赋值给rax,r12,rsp
from pwn import *
context.arch='amd64'
p=process('/challenge/run')
p.recvline()
p.send(asm('''
mov rax, 0x1337
mov r12, 0xCAFED00D1337BEEF
mov rsp, 0x31337'''))
print(p.recvallS())
level3
Addition (加法)
题目描述要求add 0x331337 to rdi就是add rdi,0x331337
from pwn import *
context.arch = 'amd64'
p = process('/challenge/run')
p.recvline()
p.send(asm('''
add rdi, 0x331337'''))
print(p.recvallS())
level4
Multiplication (乘法)
前面这些是教如何编译汇编,把二进制程序输入到程序中,我们不用这么麻烦,直接使用python的pwntools库,来执行代码
题目让我们实现mx+b,就是m乘x加b,结果存在rax里
使用imul进行乘法运算,imul和mul区别一个有符号,一个无符号,imul rdi, rsi
等同于rdi = rdi * rsi
由于题目给了值,所以只需要运算即可
from pwn import *
context.arch='amd64'
p=process('/challenge/run')
p.recvline()
p.send(asm('''
xor rax,rax
imul rdi,rsi
mov rax,rdi
add rax,rdx'''))
print(p.recvallS())
level5
Division (除法)
div rsi代表 rax = rax / rsi,如果rax和rsi都为64bit,则余数rdx为0,如果rax为128bit,则存在rdx余数,本题distance为64bit
题目要求speed = distance / time,速度=路程/时间,其中路程和时间题里已经给了
在使用未初始化的rax或rdx时,记得xor rax, rax
初始化寄存器,否则数据会错。
from pwn import *
context.arch='amd64'
p=process('/challenge/run')
p.recvline()
p.send(asm('''
xor rax,rax
mov rax,rdi
div rsi
'''))
print(p.recvallS())
level6
Modulus (模数)
模是除法中的余,10 / 3 = 3 …… 1,就是10 % 3 = 1,1是模数,在大多数编程语言中%为模运算
题目要求我们求模rax = rax / <reg> ...... rdx
由于x86汇编没有求模指令,使用div除法求余数
from pwn import *
context.arch='amd64'
p=process('/challenge/run')
p.recvline()
p.send(asm('''
xor rax, rax
mov rax, rdi
div rsi
xor rax, rax
mov rax, rdx
'''))
print(p.recvallS())
在x86架构汇编语言中,rax寄存器通常是函数的返回值,所以把rdx放到rax里
level7
Register sizes (寄存器大小)
题目要求仅使用mov指令实现模运算,模除256,相当于只保留64位寄存器的低8位,模除65536,相当于只保留寄存器的16位
常用寄存器
x64调用约定 | 64位寄存器 | 32位寄存器 | 16位寄存器 | 8位寄存器(低) | 8位寄存器(高) |
函数返回值 | rax | eax | ax | al | al |
rbx | ebx | bx | bl | bl | |
第四个参数 | rcx | ecb | cx | cl | cl |
第三个参数 | rdx | edx | dx | dl | dl |
第一个参数 | rdi | edi | di | dil | |
第二个参数 | rsi | esi | si | sil | |
下一条指令的地址 | rip | eip | ip | ||
栈底指针 | rbp | ebp | bp | bpl | |
栈顶指针 | rsp | esp | sp | spl | |
第五个参数 | r8 | r8d | r8w | r8b | |
第六个参数 | r9 | r9d | r9w | r9b |
from pwn import *
context.arch='amd64'
p=process('/challenge/run')
p.recvline()
p.send(asm('''
mov ah, 0x42
'''))
print(p.recvallS())
level8
Register sizes for modulus(模数的寄存器大小)
题目要我们求模,但是限制我们只能使用mov指令,不过只要除数是2的幂数,例如2^n,模数就是低n位
from pwn import *
context.arch='amd64'
p=process('/challenge/run')
p.recvline()
p.send(asm('''
mov al, dil
mov bx, si
'''.strip()))
print(p.recvallS())
level9
Bitwise shift (位移)
shl rax, 1
意思是rax左移1位
例如rax = 10010001 01011001 11001100 10100101 11001010 00111100 10101010 01011010
结果rax = 00100010 10110011 10011001 01001011 10010100 01111001 01010100 10110100
最高位遗弃,最低位置零。shl rax, rbx
意思是rax左移rbx的值,rbx为8则左移8位
题目只能用shl(左移)shr(右移)mov
题目要求让rax的值为B4,就要让rdi先左移3字节(24位),把B4移到最左边,再让rdi右移7字节(56位),把B4移到最右边,这样rdi的值就为B4了,直接赋值给rax就行
1byte(字节) = 8bits(比特/位)4位二进制 = 1位十六进制
from pwn import *
context.arch='amd64'
p=process('/challenge/run')
p.recvline()
p.send(asm('''
shl rdi,24
shr rdi,56
mov rax,rdi
'''.strip()))
print(p.recvallS())
level10
Bitwise and (位与)
与运算 全1为1,有0出0
或运算 有1出1,全0出0
异或运算 相异为1,相同为0
例:rax = 10101010
rbx = 00110011
and rax, rbx
; rax = 00100010
题目限制不能用mov,可以用or来赋值,先把rax清零,再把rax和任意值或运算,就能把任意值赋值给rax
from pwn import *
context.arch='amd64'
p=process('/challenge/run')
p.recvline()
p.send(asm('''
and rdi, rsi
xor rax, rax
or rax, rdi
'''))
print(p.recvallS())
level11
Bitwise logic (位逻辑运算)
只能使用and, or, xor来判断和赋值
我们可以对x和1进行与运算,如果x为奇数则结果为1,如果x为偶数则结果为0,再把结果和1进行异或,最终结果为奇数则为0,为偶数则为1
from pwn import *
context.arch='amd64'
p=process('/challenge/run')
p.recvline()
p.send(asm('''
xor rax, rax
and rdi, 1
xor rdi, 1
or rax, rdi
'''))
p.interactive()
level12
Memory reads(内存读取)
0x404000是值,[0x404000]是0x404000地址里的值
题目要求我们把0x404000地址里的值赋值给rax
from pwn import *
context.arch='amd64'
p=process('/challenge/run')
p.recvline()
p.send(asm('''
xor rax, rax
mov rax, [0x404000]
'''))
print(p.recvallS())
level13
Memory writes(内存写入)
题目要求把rax里的值放到0x404000中,也就是[0x404000],很简单,构造代码
from pwn import *
context.arch='amd64'
p=process('/challenge/run')
p.recvline()
p.send(asm('''
xor rdi, rdi
mov rdi, 0x404000
mov [rdi], rax
'''))
print(p.recvallS())
level14
Memory reads and writes (内存读取和写入)
题目要求我们把0x404000地址里的值赋值给rax,再把0x404000地址里的值增加0x1337
不能直接mov [0x404000], 0x1337
,地址里的值不能用立即数赋值,可以通过寄存器赋值,例如mov [0x404000], rbx
更多请自行搜索 汇编的寻址模式和过程调用约定
from pwn import *
context.arch='amd64'
p=process('/challenge/run')
p.recvline()
p.send(asm('''
mov rax, [0x404000]
mov rbx, [0x404000]
add rbx, 0x1337
mov [0x404000], rbx
'''))
print(p.recvallS())
level15
Read one size data(读取特定大小的数据)
四字 = 8字节 = 64bits
双字 = 4字节 = 32bits
字 = 2字节 = 16bits
1字节 = 8bits
rax = 四字
eax = 双字
ax = 字
al = 字节
题目要求rax的值为0x404000地址里的字节值,直接mov al, [0x404000]
,更改操作数的大小就行
from pwn import *
context.arch='amd64'
p=process('/challenge/run')
p.recvline()
p.send(asm('''
xor rax, rax
mov al, [0x404000]
'''))
print(p.recvallS())
level16
Read multiple data sizes(读取多种数据大小)
就像上一题说的那样,依次按大小赋值
from pwn import *
context.arch='amd64'
p=process('/challenge/run')
p.send(asm('''
mov al,[0x404000]
mov bx,[0x404000]
mov ecx,[0x404000]
mov rdx,[0x404000]'''))
print(p.recvallS())
level17
Dynamic address memory writes(动态地址内存写入)
数据在内存中是以小端序(little endian)存储的
不过这题没啥用
直接赋值就行了
from pwn import *
context.arch='amd64'
p=process('/challenge/run')
p.recvline()
p.send(asm('''
mov rax,0xdeadbeef00001337
mov [rdi],rax
mov rax,0xc0ffee0000
mov [rsi],rax'''))
print(p.recvallS())
level18
Consecutive memory reads(连续内存读取)从这里开始vscode换成深色模式了
题目要求我们从rdi读取两个相邻的四字数据并相加,存到rsi的值里
from pwn import *
context.arch='amd64'
p=process('/challenge/run')
p.recvline()
p.send(asm('''
mov rax,[rdi]
add rax,[rdi+8]
mov [rsi],eax'''))
print(p.recvallS())
先读取rdi,在读取rdi+8,正好是两个相邻的四字
level19
The stack(栈)
寄存器是有限的,当要存储的数据量超过了cpu自带的寄存器的数量,就要使用栈来存储数据。
任何程序都是由函数组成的,栈则是函数运行时临时使用的一块内存。
rsp是栈顶指针寄存器,总是指向新入栈的数据地址,rbp是栈(底/基)指针寄存器,一般保持不变,由于栈则是从高位地址向地位地址分配的,所以新入栈的数据地址更低。
在x86_64汇编中push rdi
是先把rsp值-8,再把rdi的值存入栈中,pop rdi
意思是把当前栈顶指针指向的值存入rdi,再把rsp值+8。
题目要求我们取出栈顶的值,减去rdi的值,然后再放回去
from pwn import *
context.arch='amd64'
p=process('/challenge/run')
p.recvline()
p.send(asm('''
pop rax
sub rax, rdi
push rax'''))
print(p.recvallS())
level20
Swap register values with the stack(与栈交换寄存器值)
这块就让我们使用先进后出(LIFO)的属性来调换两个寄存器的值,很简单,先push rdi rsi再pop rdi rsi
from pwn import *
context.arch='amd64'
p=process('/challenge/run')
p.recvline()
p.send(asm('''
push rdi
push rsi
pop rdi
pop rsi'''))
print(p.recvallS())
level21
Memory reads and writes with the stack(通过栈读取写入内存)
题里要求我们不使用pop指令使用rsp读取栈内4个四字的值,并求他们的平均数。
from pwn import *
context.arch='amd64'
p=process('/challenge/run')
p.recvline()
p.send(asm('''
mov rax, [rsp]
add rax, [rsp+8]
add rax, [rsp+16]
add rax, [rsp+24]
mov rdi, 4
div rdi
push rax'''))
print(p.recvallS())
使用rsp读取最后入栈的4个值,求平均数
level22
Absolute jump(绝对跳转)
x86_64用法 jmp <register>,jmp后不能直接用立即数,必须用寄存器
from pwn import *
context.arch='amd64'
p=process('/challenge/run')
p.recvline()
p.send(asm('''
mov rsi, 0x403000
jmp rsi'''))
print(p.recvallS())
level23
Relative jump(相对跳转)
题目要求相对跳转到当前地址+0x51字节的位置,我们设置一个label标签定位地址,然后再前面填充0x51字节的nop,最后跳转到标签,就成功相对当前地址跳转0x51字节。
from pwn import *
context.arch='amd64'
p=process('/challenge/run')
p.send(asm(f'''
jmp address
.rept 0x51
nop
.endr
address:
mov rax, 0x1
'''))
print(p.readallS())
level24
Control flow(控制流)
和上一关差不多,不过是两种跳转合一起了
from pwn import *
context.arch = 'amd64'
p = process('/challenge/run')
p.send(asm('''
jmp start
.rept 0x51
nop
.endr
start:
pop rdi
mov rsi, 0x403000
jmp rsi
'''))
print(p.recvallS())
level25
Conditional branches(条件分支)
cmp比较,相等ZF为1否则ZF为0,je是ZF为1时跳转,jne则相反。
from pwn import *
context.update(arch="amd64")
p = process("/challenge/run")
p.write(asm("""
mov eax, [rdi]
mov ebx, [rdi + 4]
mov ecx, [rdi + 8]
mov edx, [rdi + 12]
cmp eax, 0x7f454c46
je con1
cmp eax, 0x00005A4D
je con2
imul ebx, ecx
imul ebx, edx
jmp done
con1:
add ebx, ecx
add ebx, edx
jmp done
con2:
sub ebx, ecx
sub ebx, edx
done:
mov eax, ebx"""))
print(p.readallS())
level26
Jump tables(跳转表)
使用跳转表实现switch-case-defaut功能
先判断default情况,如果rdi>=4,则跳转到最后一种情况,剩下的情况直接根据rdi偏移跳转
from pwn import *
context.arch = 'amd64'
p = process('/challenge/run')
p.send(asm('''
cmp rdi, 4
jae default
jmp [rsi + rdi * 8]
jmp end
default:
jmp [rsi + 4 * 8]
end:
nop
'''))
print(p.recvallS())