aiQG's Blog

Happy hacking. Happy developing.

aiQG's GitHub ⛓Links P
Other CTF Swift Developing&Reversing

2019杭电hgame 部分wp

27 January 2019

by aiQG_

Web

根据提示可以知道这和vim的缓存文件有关 vim会在编辑文件的时候生成一个.swp的缓存文件, 用于意外退出时恢复未保存的文件 目的很明确 下载这个文件 url后面加上/.index.html.swp下载.swp 用winhex打开即可找到flag

Re

#include <iostream>
#include <cctype>

// Orz... I haven't learnt C++ before.
// It seems like my brain was fxxked by these codes...

// Notice:
// 1. the answer is your input when nothing strange was printed
// 2. that is, wrong inputs will encounter with the part "[+.]"
// 3. [!!!] REMEMBER TO WRAP YOUR ANSWER WITH "hgame{" AND "}"
//    [!!!] BEFORE YOU SUBMITTED IT

// oyiadin, Jan 18, 2019
// enjoy it! ;)

namespace bf {

class Parser {
 public:
  Parser() = default;
  ~Parser() = default;
  void execute(const std::string &buf);

 protected:
  uint8_t data[100] = {0};
  int ptr = 0;
};


void Parser::execute(const std::string &buf) {
  for (auto i = buf.cbegin(); i != buf.cend(); ++i) {
    switch (*i) {
      case '>':
        ++ptr;
        break;
      case '<':
        --ptr;
        break;
      case '+':
        ++data[ptr];
        break;
      case '-':
        --data[ptr];
        break;
      case '.':
        putchar(data[ptr]);
        break;
      case ',':
        while ((data[ptr] = getchar()) == '\n') ;
        break;
      case '[':
        if (!data[ptr]) {
          while (*i++ != ']') continue;
          --i;
        }
        break;
      case ']':
        if (data[ptr]) {
          while (*(i-1) != '[') --i;
          --i;
        }
        break;
      default:
        break;
    }
  }
}

}


int main() {
  bf::Parser parser;
  parser.execute(",>++++++++++[<---------->-]<++[+.],>+++++++++[<--------->-]<-[+.],>+++++++[<------->-]<---[+.],>++++++[<------>-]<+++[+.],>++++++++[<---------->-]<++[+.],>++++++++++[<---------->-]<--[+.],>++++++++++[<-------->-]<-----[+.],>++++++++++[<---------->-]<+[+.],>+++++++++[<-------->-]<---[+.]");
}

有点像一个vm 直接编译可以运行 随便输入一点东西会返回一堆连续的字符 尝试将指令整理一下 可以发现当最后需要操作的字符为0x00时会停止当前打印字符串的循环 尝试让其只循环一次 构造出几个方程 解方程可得flag

,>++++++++++  [<---------->-]       <++			[+.]	x-10*10+2=0		b
,>+++++++++   [<--------->-]        <-			[+.] 	x-9*9-1=0		R
,>+++++++     [<------->-]          <---		[+.]	x-7*7-3=0		4
,>++++++      [<------>-]           <+++		[+.]	x-6*6+3=0		!
,>++++++++    [<---------->-]       <++			[+.]	x-8*10+2=0		N
,>++++++++++  [<---------->-]       <--			[+.]	x-100-2=0		f
,>++++++++++  [<-------->-]         <-----		[+.]	x-10*8-5=0		U
,>++++++++++  [<---------->-]       <+			[+.]	x-100+1=0		c
,>+++++++++   [<-------->-]			<---		[+.]	x-9*8-3=0		K

程序把输入分成了高低位两个数组 都是6 * 6的矩阵 低位和一个已知矩阵进行加运算 高位进行乘运算 最后进行明文对比 低位减回去 高位求个逆乘回去 然后重新组合一下即可求出flag

//ida里看到两个tls, 应该是反调试的, 但是似乎没起作用… 程序最后要满足一个strcmp 其中一个字符串来自程序最后0x20个字节 程序一共有两个输入 第一个是int 被传入了sub_401460 里面有两个 VirtualProtect 发现并不是SMC, 只是中间异或了401000某偏移的某一值 再往下程序fgets了一个长度为22的字符串 然后丢到sub_4014fa里 进入sub_4014fa看起来像一个base64编码的过程 shift+F12查找到加密表, 显然不是标准的base64

这里似乎编码后的字符串没有用到, 开始动态分析
这里运行程序程序随便输入一个(无符号整)数(不大于131072) 和一个长22到字符串, 发现程序会崩溃

动态分析看看程序崩溃在了哪里
od依靠字符串定位到main函数 第一次同样随便输入一个数
往下步过到0x4018de的地方发现一个无条件跳转, 直接跳到了0x4018f5的call sub_4014fa //进入了base编码的函数
显然函数没有传参因此导致了崩溃
我们重新设置一下EIP, 程序成功得到了编码后的字符串, 并与程序二进制文件最后0x20个字符进行strcmp

这里先将已知的字符串xor回来
```C++
	for ( int i = 32; i>0; --i )
	{
		s[i] ^= s[i-1];
	}

再对换表base解码//工具: RandomBase64 可以自定义密码表 得到flag前半部分 输入的整数要把0x4018df处的0x15修改成0x05 整数»3后是偏移0x8df, 1 « (整数 & 7)是拿来和0x15异或的 所以得到整数是(0x8df«3) + 4 //0x15^0x5== 0x10 ==1«4 得到整数0x46fc == 18172

又是一个brainfxxk 足足上千个符号, 显然不可能一个个看懂的//当然也不可能个个都有意义 稍微读几个, 发现>和<, +和- 是互逆的操作, 所以删掉所有的<> >< +- -+这几个组合, 基本上化简了很多 运行程序发现程序需要输入很多的字符, 然后会有提示信息”wrong answer!” or “congratulations!” 在switch下个断点, 发现程序没有执行case '.': putchar(*ptr);这一句, 说明还有花代码 但是程序获取输入可以确定是 ‘ , ‘ 号, 但是一搜索发现足足有上千个//显然不会每个都执行 再观察最后输出反馈信息的语句, 发现判断了data一个标识位是否有值 在最后的default: putchar(*i);下个断点, 观察data数组 可以看见判断了data[0] 如果有值就输出”wrong answer!”, 并且data[0]的值可能会随不同的输入变大或变小//能猜到如果输入正确则data[0]不加1///构造hgame开头的字符串可以验证 下断点稍微跟程序跑一下, 发现每一次输入主要依靠,>++… 在前面加个换行整理一下, 一共72行//正好就是data[0]的最大值, 也是输入的字符串长度 //其中[-]这个结构是用来归零循环标志位的 把没有进入的循环删除掉 再稍微整理一下得到

>
,>++++++++		[<------------->-]<//h
,>++++++++		[<------------>-]<-------//g
,>++++++++		[<------------>-]<-//a
,>++++++		[<------------------>-]<-//m
,>++++++		[<---------------->-]<-----//e
,>++++++++		[<--------------->-]<---//{
,>++++++++++	[<---------->-]//d
,>++++++++++	[<---------->-]<--//f
,>++++++++++	[<----->-]<---//5
,>+++++++		[<-------->-]<-//9
,>++++++		[<----------------->-]<//f
,>+++++++		[<------->-]<------//7
,>+++++++		[<-------------->-]<//b
,>+++++			[<------------------->-]<--//a
,>++++++++++	[<---------->-]<//d
,>++++++		[<-------->-]<---//3
......

得到flag

misc

得到一个数据包 筛选出http 找到包含zip的包, 选定media type的位置, 点击file->export selected Packet Bytes, 在弹出的框中将文件保存成二进制文件

一个password.txt和一个带密码的压缩包 掩码爆破hgame????????得到压缩包密码 解压出一张图片 binwalk可以发现有个压缩包 提取出来是伪加密 改一下标识位 得到.docx 把docx解压找到word文件夹 有个文件里有flag

Crypto

一个py脚本 flag和 poem.txt 未知 提示OTP(一次性密码) 题中多次用一个随机密码按位异或明文 因此密文相互异或就可以干掉一次性密码 然后分析出明文 git上找到脚本

#!/usr/bin/python
## OTP - Recovering the private key from a set of messages that were encrypted w/ the same private key (Many time pad attack) - crypto100-many_time_secret @ alexctf 2017
# Original code by jwomers: https://github.com/Jwomers/many-time-pad-attack/blob/master/attack.py)

import string
import collections
import sets, sys

# 11 unknown ciphertexts (in hex format), all encrpyted with the same key
c1='daaa4b4e8c996dc786889cd63bc4df4d1e7dc6f3f0b7a0b61ad48811f6f7c9bfabd7083c53ba54'
c2='c5a342468c8c7a88999a9dd623c0cc4b0f7c829acaf8f3ac13c78300b3b1c7a3ef8e193840bb'
c3='dda342458c897a8285df879e3285ce511e7c8d9afff9b7ff15de8a16b394c7bdab920e7946a05e9941d8308e'
c4='d9b05b4cd5ce7c8f938bd39e24d0df191d7694dfeaf8bfbb56e28900e1b8dff1bb985c2d5aa154'
c5='d9aa4b00c88b7fc79d99d38223c08d54146b88d3f0f0f38c03df8d52f0bfc1bda3d7133712a55e9948c32c8a'
c6='c4b60e46c9827cc79e9698936bd1c55c5b6e87c8f0febdb856fe8052e4bfc9a5efbe5c3f57ad4b9944de34'
c7='d9aa5700da817f94d29e81936bc4c1555b7b94d5f5f2bdff37df8252ffbecfb9bbd7152a12bc4fc00ad7229090'
c8='c4e24645cd9c28939a86d3982ac8c819086989d1fbf9f39e18d5c601fbb6dab4ef9e12795bbc549959d9229090'
c9='d9aa4b598c80698a97df879e2ec08d5b1e7f89c8fbb7beba56f0c619fdb2c4bdef8313795fa149dc0ad4228f'
c10='cce25d48d98a6c8280df909926c0de19143983c8befab6ff21d99f52e4b2daa5ef83143647e854d60ad5269c87'
c11='d9aa4b598c85668885df9d993f85e419107783cdbee3bbba1391b11afcf7c3bfaa805c2d5aad42995ede2cdd82977244'
ciphers = [c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11]
# The target ciphertext we want to crack

# XORs two string
def strxor(a, b):     # xor two strings (trims the longer input)
    return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)])

def target_fix(target_cipher):
    print '-------begin-------'

    # To store the final key
    final_key = [None]*150
    # To store the positions we know are broken
    known_key_positions = set()

    # For each ciphertext
    for current_index, ciphertext in enumerate(ciphers):
        counter = collections.Counter()
        # for each other ciphertext
        for index, ciphertext2 in enumerate(ciphers):
            if current_index != index: # don't xor a ciphertext with itself
                for indexOfChar, char in enumerate(strxor(ciphertext.decode('hex'), ciphertext2.decode('hex'))): # Xor the two ciphertexts
                    # If a character in the xored result is a alphanumeric character, it means there was probably a space character in one of the plaintexts (we don't know which one)
                    if char in string.printable and char.isalpha(): counter[indexOfChar] += 1 # Increment the counter at this index
        knownSpaceIndexes = []

        # Loop through all positions where a space character was possible in the current_index cipher
        for ind, val in counter.items():
            # If a space was found at least 7 times at this index out of the 9 possible XORS, then the space character was likely from the current_index cipher!
            if val >= 7: knownSpaceIndexes.append(ind)
        #print knownSpaceIndexes # Shows all the positions where we now know the key!

        # Now Xor the current_index with spaces, and at the knownSpaceIndexes positions we get the key back!
        xor_with_spaces = strxor(ciphertext.decode('hex'),' '*150)
        for index in knownSpaceIndexes:
            # Store the key's value at the correct position
            final_key[index] = xor_with_spaces[index].encode('hex')
            # Record that we known the key at this position
            known_key_positions.add(index)

    # Construct a hex key from the currently known key, adding in '00' hex chars where we do not know (to make a complete hex string)
    final_key_hex = ''.join([val if val is not None else '00' for val in final_key])
    # Xor the currently known key with the target cipher
    output = strxor(target_cipher.decode('hex'),final_key_hex.decode('hex'))

    print "Fix this sentence:"
    print ''.join([char if index in known_key_positions else '*' for index, char in enumerate(output)])+"\n"

    # WAIT.. MANUAL STEP HERE 
    # This output are printing a * if that character is not known yet
    # fix the missing characters like this: "Let*M**k*ow if *o{*a" = "cure, Let Me know if you a"
    # if is too hard, change the target_cipher to another one and try again
    # and we have our key to fix the entire text!

    #sys.exit(0) #comment and continue if u got a good key

  

    print '------end------'
for i in ciphers:
    target_fix(i)

这里只选用了11行密文进行修复 可以发现其中

-------begin-------
Fix this sentence:
*hen*we two parted In silence *n* tear*

------end------

能基本识别出正常含义 谷歌这句话 发现是一首由乔治·戈登·拜伦创作的诗歌《When We Two Parted》中的第一句话

When we two parted in silence and tears

那么一条明文出来了 把这句话和第一条密文按位异或 就可以推出随机的密钥了

最后拿密钥按位异或被加密的flag 得到flag

CTF

return

email:nrxxmzlrovqw4z3fgeztcncaozuxaltroexgg33n

桂ICP备18011144号-1 (点击访问安全的工信部网站)