百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

技术干货 l 第五空间线上赛中的两道逆向题解

toyiye 2024-05-19 19:34 10 浏览 0 评论


看到题目文件:



从图标来看明显是python打包成的exe程序,使用pyinstxtractor.py来对这个exe程序解包。

python pyinstxtractor.py main.exe

然后在目录下得到一个main.exe_extracted文件夹,找到其中与程序同名的main文件。因为解包得到的核心pyc文件是去除了文件头的,所以还要找到目录下的struct文件来得到文件头。



从struct得到文件头:55 0D 0D 0A 00 00 00 00 70 79 69 30 10 01 00 00 将其填充到main文件的开头16字节。对上面填充好的main文件重命名为main.pyc文件,用uncompyle6进行反编译:

uncompyle6 -o main.py main.pyc

打开main.py文件:

# uncompyle6 version 3.7.4
# Python bytecode 3.8 (3413)
# Decompiled from: Python 3.8.6 (tags/v3.8.6:db45529, Sep 23 2020, 15:52:53) [MSC v.1927 64 bit (AMD64)]
# Embedded file name: main.py
# Compiled at: 1995-09-28 00:18:56
# Size of source mod 2**32: 272 bytes
import brainfuck
brainfuck.main_check()  

到之前解包的目录下找到brainfuck.cp38-win_amd64.pyd,原来这个题给的核心部分在pyd文件,这类似于winodws下的动态链接库。

将该pyd文件与main.py文件放在同一目录下然后执行main.py,随便输入后反馈nonono


接着ida分析该pyd文件,正如模块名字,从其中找到了brainfuck代码。



用idapython导出该brainfuck代码:

from ida_bytes import *

addr = 0x18000B740
a = ""
while addr < 0x18000E8D8:
    a += chr(get_byte(addr))
    addr += 1
f = open("code.txt", "w")
f.write(a)
f.close()
print('*'*100)

再简单的按照brainfuck代码的运算来解析成C代码:

#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <string.h> 

using namespace std;

string translate(char c)
{
    switch (c)
    {
    case '>':
        return "p++";
    case '<':
        return "p--";
    case '+':
        return "*p = *p + 1";
    case '-':
        return "*p = *p - 1";
    case '.':
        return "cout<<char(*p)";
    case ',':
        return "*p=getchar()";
    case '[':
        return "while(*p){";
    case ']':
        return "}";
    default:
        return "";
    }
}


int main()
{
    FILE *fp = fopen("code.txt", "rb");
    FILE *fp1 = fopen("ans.txt", "wb");

    char c;
    while ((c = fgetc(fp)) != EOF) 
    {
        fputs(translate(c).c_str(), fp1);
        if (c != '[')
            fputs(";\n", fp1);
    }
    return 0;
} 

得到1w多行指针运算代码,这也是brainfuck代码的特性,维护几个变量做加减法运算完成程序所有的功能。将得到的C代码处理一下后编译成exe程序:

#include <stdio.h>
#include <string.h> 
#include <iostream>
    
using namespace std; 
    
char a[1000];
char *p = a;
    
int main(void)
{
    p++;
    p++;
    p++;
    p++;
    p++;
    p++;
    p++;
    p++;
    p++;
    p++;
    p++;
    ...
    ...
    ...
    
    p--;
    p--;
    p--;
    p--;
    p--;
    
}

在ida中调试编译得到exe程序。技巧就看它维护几个变量的内存值的变化吧。不断调试可以知道这个程序在比较2个值是否相同用的减法,就是对要比较的2个数依次做减法,看最后他们是否同时为0,若是则相等,否则反之。

还是调试的时候看内存,得到以下信息:首先将输入存入程序中的一块内存区域,然后依次判断开始的几个字符是否是flag{和偏移+0x25的位置是否是}(如下图的内存区域)


如果上面比较成功的话就开始对flag{}中的32字节开始进行运算。

发现第一个字节0x61变成0x50,其实就是当前字节和后一个字节异或运算的结果(0x61^0x31)。


继续调试发现这一串密文除了最后一个都变成了0x50



所以从我的输入与密文结果可以得出加密逻辑:flag[i] ^= flag[i+1],且在附近的区域找到密文:


最后异或回去得到flag:

>>> s = [0x53, 0x0F, 0x5A, 0x54, 0x50, 0x55, 0x03, 0x02, 0x00, 0x07, 0x56, 0x07, 0x07, 0x5B, 0x09, 0x00, 0x50, 0x05, 0x02, 0x03, 0x5D, 0x5C, 0x50, 0x51, 0x52, 0x54, 0x5A, 0x5F, 0x02, 0x57, 0x07, 0x34]
>>> for i in range(31):
...     s[30-i] ^= s[31-i]
...
>>> bytes(s)
b'd78b6f30225cdc811adfe8d4e7c9fd34'

从题目名称看是使用uni-app这个前端框架。jeb中看了看并没有发现什么关键点,想到题目的提示:JS不只能写网页哦!然后从\assets\appsUNI14D1880\www目录下找到了很多js文件。接着安装好app运行来搜集一下app的字符串信息,发现有Please input...Try again,再使用notepad++的文件夹搜索功能来搜索上面得到的字符串信息,开始的Please input...并没有搜到,但搜到了Try again,也通过这找到关键js文件:app-service.js



定位到app-service.js文件中的关键点:输入先与108异或后再经过f["encrypt"]加密,最后与p密文对比。


来看到加密函数:就是一个异或运算,关键就是获取这个_keystream


找到_keystream生成的地方:从[1634760805, 857760878, 2036477234, 1797285236]定位到这其实是一个chacha20序列密码。


从github找到一份python实现的chacha20,适当的修改后再修改key,Nonce及position为js文件中的。(python实现chacha20的key是32字节,iv为8字节,position为0;js中的key同样为32字节但iv为12字节,position为1。这个从对比参数的填充很容易发现)

key:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]

iv:[0, 0, 0, 0, 0, 0, 0, 74, 0, 0, 0, 0]

position:1

python生成chacha20异或序列代码:

import struct

def yield_chacha20_xor_stream(key, iv, position=0):
  """Generate the xor stream with the ChaCha20 cipher."""
  if not isinstance(position, int):
    raise TypeError
  if position & ~0xffffffff:
    raise ValueError('Position is not uint32.')
  if not isinstance(key, bytes):
    raise TypeError
  if not isinstance(iv, bytes):
    raise TypeError
  if len(key) != 32:
    raise ValueError
  if len(iv) != 12:
    raise ValueError

  def rotate(v, c):
    return ((v << c) & 0xffffffff) | v >> (32 - c)

  def quarter_round(x, a, b, c, d):
    x[a] = (x[a] + x[b]) & 0xffffffff
    x[d] = rotate(x[d] ^ x[a], 16)
    x[c] = (x[c] + x[d]) & 0xffffffff
    x[b] = rotate(x[b] ^ x[c], 12)
    x[a] = (x[a] + x[b]) & 0xffffffff
    x[d] = rotate(x[d] ^ x[a], 8)
    x[c] = (x[c] + x[d]) & 0xffffffff
    x[b] = rotate(x[b] ^ x[c], 7)

  ctx = [0] * 16
  ctx[:4] = (1634760805, 857760878, 2036477234, 1797285236)
  ctx[4 : 12] = struct.unpack('<8L', key)
  ctx[12] = position
  ctx[13 : 16] = struct.unpack('<3L', iv)
  while 1:
    x = list(ctx)
    for i in range(10):
      quarter_round(x, 0, 4,  8, 12)
      quarter_round(x, 1, 5,  9, 13)
      quarter_round(x, 2, 6, 10, 14)
      quarter_round(x, 3, 7, 11, 15)
      quarter_round(x, 0, 5, 10, 15)
      quarter_round(x, 1, 6, 11, 12)
      quarter_round(x, 2, 7,  8, 13)
      quarter_round(x, 3, 4,  9, 14)
    for c in struct.pack('<16L', *(
        (x[i] + ctx[i]) & 0xffffffff for i in range(16))):
      yield c
    ctx[12] = (ctx[12] + 1) & 0xffffffff
    if ctx[12] == 0:
      ctx[13] = (ctx[13] + 1) & 0xffffffff


def chacha20_encrypt(data, key, iv=None, position=1):
  """Encrypt (or decrypt) with the ChaCha20 cipher."""
  if not isinstance(data, bytes):
    raise TypeError
  if iv is None:
    iv = b'\0' * 8
  if isinstance(key, bytes):
    if not key:
      raise ValueError('Key is empty.')
    if len(key) < 32:
      # TODO(pts): Do key derivation with PBKDF2 or something similar.
      key = (key * (32 // len(key) + 1))[:32]
    if len(key) > 32:
      raise ValueError('Key too long.')

  return yield_chacha20_xor_stream(key, iv, position)



def run_tests():
    import binascii
    uh = lambda x: binascii.unhexlify(bytes(x, 'ascii'))
    key = bytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31])
    iv = bytes([0, 0, 0, 0, 0, 0, 0, 74, 0, 0, 0, 0])
    ans = chacha20_encrypt(b'\0' * 32, key, iv)
    for i, data in enumerate(ans):
        if i == 38:
            break
        print(data, end = ', ')

if __name__ == "__main__":
  run_tests()

34, 79, 81, 243, 64, 27, 217, 225, 47, 222, 39, 111, 184, 99, 29, 237, 140, 19, 31, 130, 61, 44, 6, 226, 126, 79, 202, 236, 158, 243, 207, 120, 138, 59, 10, 163, 114, 96

异或密文与108后得到flag:

>>> p
[34, 69, 86, 242, 93, 72, 134, 226, 42, 138, 112, 56, 189, 53, 77, 178, 223, 76, 78, 221, 63, 40, 86, 231, 121, 29, 154, 189, 204, 243, 205, 44, 141, 100, 13, 164, 35, 123]
>>> a = [34, 79, 81, 243, 64, 27, 217, 225, 47, 222, 39, 111, 184, 99, 29, 237, 140, 19, 31, 130, 61, 44, 6, 226, 126, 79, 202, 236, 158, 243, 207, 120, 138, 59, 10, 163, 114, 96]
>>> ans = [p[i]^a[i]^102 for i in range(38)]
>>> bytes(ans)
b'flag{59ec211c0695979db6ca4674fd2a9aa7}'

最后说一下如何直接使用给到的js代码生成chacha20的异或序列。对生成密钥序列的代码稍微改一下:

//2.js
var r = function (t, e, n) {
    this._chacha = function () {
        var t = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            e = 0,
            n = 0
        for (e = 0; e < 16; e++) t[e] = this._param[e]
        for (e = 0; e < this._rounds; e += 2) {
            this._quarterround(t, 0, 4, 8, 12), this._quarterround(t, 1, 5, 9, 13)
            this._quarterround(t, 2, 6, 10, 14)
            this._quarterround(t, 3, 7, 11, 15)
            this._quarterround(t, 0, 5, 10, 15)
            this._quarterround(t, 1, 6, 11, 12)
            this._quarterround(t, 2, 7, 8, 13)
            this._quarterround(t, 3, 4, 9, 14)
        }
        for (e = 0; e < 16; e++) {
            t[e] += this._param[e]
            this._keystream[n++] = 255 & t[e]
            this._keystream[n++] = (t[e] >>> 8) & 255
            this._keystream[n++] = (t[e] >>> 16) & 255
            this._keystream[n++] = (t[e] >>> 24) & 255
        }
    }

    this._quarterround = function (t, e, n, r, o) {
        t[o] = this._rotl(t[o] ^ (t[e] += t[n]), 16)
        t[n] = this._rotl(t[n] ^ (t[r] += t[o]), 12)
        t[o] = this._rotl(t[o] ^ (t[e] += t[n]), 8)
        t[n] = this._rotl(t[n] ^ (t[r] += t[o]), 7)
        t[e] >>>= 0
        t[n] >>>= 0
        t[r] >>>= 0
        t[o] >>>= 0
    }

    this._get32 = function (t, e) {
        return t[e++] ^ (t[e++] << 8) ^ (t[e++] << 16) ^ (t[e] << 24)
    }
    
    this._rotl = function (t, e) {
        return (t << e) | (t >>> (32 - e))
    }
    this.encrypt = function (t) {
        return this._update(t)
    }
    this.decrypt = function (t) {
        return this._update(t)
    }

    this._update = function (t) {
        if (!(t instanceof Uint8Array) || 0 === t.length)
            throw new Error(
                'Data should be type of bytes (Uint8Array) and not empty!'
            )
        for (var e = new Uint8Array(t.length), n = 0; n < t.length; n++) {
            ; (0 !== this._byteCounter && 64 !== this._byteCounter) ||
                (this._chacha(), this._param[12]++, (this._byteCounter = 0))
            e[n] = this._keystream[this._byteCounter++]
        }
        return e
    }

    if (
        ('undefined' === typeof n && (n = 0),
            !(t instanceof Uint8Array) || 32 !== t.length)
    )
        throw new Error('Key should be 32 byte array!')
    if (!(e instanceof Uint8Array) || 12 !== e.length)
        throw new Error('Nonce should be 12 byte array!')
    this._rounds = 20
    this._sigma = [1634760805, 857760878, 2036477234, 1797285236]
    this._param = [
        this._sigma[0],
        this._sigma[1],
        this._sigma[2],
        this._sigma[3],
        this._get32(t, 0),
        this._get32(t, 4),
        this._get32(t, 8),
        this._get32(t, 12),
        this._get32(t, 16),
        this._get32(t, 20),
        this._get32(t, 24),
        this._get32(t, 28),
        n,
        this._get32(e, 0),
        this._get32(e, 4),
        this._get32(e, 8),
    ]
    this._keystream = [
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    ]
    this._byteCounter = 0
    var plain = new Uint8Array(38)
    //console.log(plain)
    console.log(this.encrypt(plain).toString())
}
for (n = [], o = 0; o <= 31; o++) n[o] = o
var t = new Uint8Array(n),
e = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 74, 0, 0, 0, 0]),
n = 1
r(t, e, n)

然后使用php来加载一下这个js文件:

<?php
    echo '<script src="2.js"></script>';    
?>

浏览器打开即可看到生成的异或序列:将结果输出toString()方便打印。


知白讲堂是启明星辰集团网络空间安全学院的在线教育培训平台。丰富的在线课程体系和专家直播讲堂为每一位学员授业解惑。知白讲堂,一直秉承“网络安全,人才当先”的理念,助力梦想,提升职业素养,打造网络安全的行业精英!


相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码