Week1
Simple_encryption
ida 打开
主要逻辑是根据输入的字符串长度,如果j%3==0
,那么就将输入的索引为j
的值减去 31
,如果 j%3==1
,加上41
,如果 j%3==2
,那么和 0x55
异或。然后值与 buffer
数组相比较,一样就成功,所以点进数组提取数据。
可以写出 python 脚本
buffer = [0x47, 0x95, 0x34, 0x48, 0xA4, 0x1C, 0x35, 0x88, 0x64, 0x16,
0x88, 0x07, 0x14, 0x6A, 0x39, 0x12, 0xA2, 0x0A, 0x37, 0x5C,
0x07, 0x5A, 0x56, 0x60, 0x12, 0x76, 0x25, 0x12, 0x8E, 0x28,
0x00, 0x00]
for i in range(len(buffer)):
if i % 3 == 0:
buffer[i] += 0x1f
if i % 3 == 1:
buffer[i] -= 0x29
if i % 3 == 2:
buffer[i] ^= 0x55
print(chr(buffer[i]), end='')
ezAndroidStudy
我记得之前在哪个师傅博客看到过说先看 .xml
文件的 activity
可以快速找到。
对于 apk 文件可以先拖进模拟器运行看看里面是什么样的,然后看看提示
所以查看 AndroidManifest.xml
,看见 activity
只有 work.pangbai.ezandroidstudy.Homo
和 work.pangbai.ezandroidstudy.MainActivity
拿到 flag1
看下一个
在 resources.arsc/res/value/string.xml
找到了
继续看下一个
/layout/activity_main.xml
里的 activity_main.xml
查找 flag4
打开 /res/raw
发现目录下有个 flag4.txt
flag 5
需要逆向 so 层,那么将 .so
提取出来,可以使用apktool
apktool d ezAndroidStudy.apk -o "需要生成的文件夹"
将"/lib/x86_64/libezandroidstudy.so"
丢进 ida 反编译就出来了
ez_debug
根据题目丢进 x64dbg
动调
首先查找字符串,看看有没有可以字符串,可以发现有一些 flag
字样
在 Decrypted flag
处下个断点运行,得到结果
Week2
Pangbai 泰拉记(1)
ida 打开反汇编查看,flag
和一个 key
异或
__int64 __fastcall main()
{
std::ostream *v0; // rax
std::ostream *v1; // rax
int i; // [rsp+24h] [rbp+4h]
j___CheckForDebuggerJustMyCode(&_6D15E8DE_Pangbai____1__Pangbai____1__1_cpp);
v0 = std::operator<<<std::char_traits<char>>(std::cout, "Use your debugger to discover the hidden flag!");
std::ostream::operator<<(v0, std::endl<char,std::char_traits<char>>);
for ( i = 0; i < 32; ++i )
flag[i] ^= key[i];
v1 = std::operator<<<std::char_traits<char>>(std::cout, "Click on the flag to obtain?");
std::ostream::operator<<(v1, std::endl<char,std::char_traits<char>>);
return 0i64;
}
对 key
交叉引用能看见还没 main0
函数调用,进去看看
发现有两个函数 IsDebuggerPresent()
和 CheckRemoteDebuggerPresent()
,这是两个反调试函数
对于 CheckRemoteDebuggerPresent()
函数:
kernel32
的 CheckRemoteDebuggerPresent()
函数用于检测指定进程是否正在被调试. Remote
指同一个机器中的不同进程
BOOL WINAPI CheckRemoteDebuggerPresent(
_In_ HANDLE hProcess,
_Inout_ PBOOL pbDebuggerPresent
);
如果说检测到正在被调试,那么 pbDebuggerPresent
指向的值会被设置为 0xffffffff
。
对于这道题,调试一下可以看得出来流程,进程会跳转到右边这里,这里执行的是将 key
替换为不正确的 key
,所以可以将 jz
指令改为 jnz
指令,让程序跳转不了
jz: Jump if Zero,零标志位 ZF=1 时,jz 指令执行跳转
jnz: Jump if Not Zero,零标志位 ZF=0 时,jnz 指令执行跳转
然后进行调试
提取数据,写脚本
flag = [0x63, 0x61, 0x6E, 0x20, 0x79, 0x6F, 0x75, 0x20, 0x66, 0x69,
0x6E, 0x64, 0x20, 0x6D, 0x65, 0x20, 0x63, 0x61, 0x6E, 0x20,
0x79, 0x6F, 0x75, 0x20, 0x66, 0x69, 0x6E, 0x64, 0x20, 0x6D,
0x65, 0x3F]
key = [0x05, 0x0D, 0x0F, 0x47, 0x02, 0x02, 0x0C, 0x7F, 0x22, 0x5A,
0x0C, 0x11, 0x47, 0x0A, 0x56, 0x52, 0x3C, 0x0C, 0x0F, 0x59,
0x26, 0x5E, 0x06, 0x7F, 0x04, 0x08, 0x00, 0x0A, 0x45, 0x09,
0x5A, 0x42]
for i in range(0, 32):
flag[i] ^= key[i]
print(chr(flag[i]), end="")
drink_tea
先读一遍流程,输入长度为 32 的字符串,进入 sub_140001180
,然后再比较
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+20h] [rbp-28h]
__int64 v5; // [rsp+28h] [rbp-20h]
printf("Please Input: \n");
sub_140001120("%32s", byte_140004700);
v5 = -1i64;
do
++v5;
while ( byte_140004700[v5] );
if ( v5 == dword_140004078 )
{
for ( i = 0; i < dword_140004078; i += 8 )
sub_140001180(&byte_140004700[i], aWelcometonewst);
if ( !memcmp(byte_140004700, &unk_140004080, dword_140004078) )
printf("Right! \n");
else
printf("wrong!");
return 0;
}
else
{
printf("Wrong! \n");
return 0;
}
}
进入 sub_140001180
函数查看
__int64 __fastcall sub_140001180(unsigned int *a1, _DWORD *a2)
{
__int64 result; // rax
unsigned int v3; // [rsp+0h] [rbp-38h]
unsigned int v4; // [rsp+4h] [rbp-34h]
int v5; // [rsp+8h] [rbp-30h]
unsigned int i; // [rsp+Ch] [rbp-2Ch]
v3 = *a1;
v4 = a1[1];
v5 = 0;
for ( i = 0; i < 0x20; ++i )
{
v5 -= 1640531527;
v3 += (a2[1] + (v4 >> 5)) ^ (v5 + v4) ^ (*a2 + 16 * v4);
v4 += (a2[3] + (v3 >> 5)) ^ (v5 + v3) ^ (a2[2] + 16 * v3);
}
*a1 = v3;
result = 4i64;
a1[1] = v4;
return result;
}
看到这个加密流程就能发现是 tea 算法加密找到key
和输入的值直接逆
脚本如下:
#include <stdio.h>
#include <stdint.h>
void decrypt (uint32_t* v, uint32_t* k) {
uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i;
uint32_t delta=0x9e3779b9;
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];
for (i=0; i<32; i++) {
v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
sum -= delta;
}
v[0]=v0; v[1]=v1;
}
int main()
{
int i,j;
unsigned char flag[] = {0x78, 0x20, 0xF7, 0xB3, 0xC5, 0x42, 0xCE, 0xDA, 0x85,
0x59, 0x21, 0x1A, 0x26, 0x56, 0x5A, 0x59, 0x29, 0x02, 0x0D, 0xED, 0x07,
0xA8,0xB9, 0xEE, 0x36, 0x59, 0x11, 0x87, 0xFD, 0x5C, 0x23, 0x24};
unsigned char keys[]="WelcomeToNewStar";
uint32_t *v = (uint32_t*)flag;
uint32_t *k = (uint32_t*)keys;
for(i=0;i<8;i+=2){ //每8个字节(2个 uint32)解密
decrypt(v+i,k);
}
for(j=0;j<32;j++){
printf("%c",flag[j]);
}
return 0;
}
ezencrypt
先查看 MainActivity
函数发现有加密逻辑,Enc enc = new Enc(tx)
,那就去 Enc
函数看看
Enc
的构造函数里进行第一次加密,ECB
模式的 AES
加密,密钥是 MainActivity
的 title
doEncCheck
函数进行加密数据检查,发现有 native
关键字,在 Java 中,native
关键字用于声明一个方法是由本地代码(通常是C或C++)实现的,所以说明函数是 C/C++ 编写的,所以主体在 so 文件,进行 so 提取
IDA 打开 so 文件,找到 doEncCheck
,点进去查看
enc
函数的加密伪代码
__int64 __fastcall enc(char *a1)
{
int i; // [rsp+0h] [rbp-20h]
int v3; // [rsp+4h] [rbp-1Ch]
v3 = __strlen_chk(a1, -1LL);
for ( i = 0; i < v3; ++i )
a1[i] ^= xork[i % 4];
return encc(xork, a1);
}
又看见一个 encc
加密函数,(太好了,又有个算法加密,我们有救了!)
__int64 __fastcall encc(char *a1, char *a2)
{
unsigned __int64 v2; // rcx
__int64 result; // rax
unsigned __int8 v4; // [rsp+Ch] [rbp-34h]
int v5; // [rsp+14h] [rbp-2Ch]
int v6; // [rsp+18h] [rbp-28h]
int i; // [rsp+1Ch] [rbp-24h]
init_sbox(a1);
v5 = 0;
v6 = 0;
for ( i = 0; ; ++i )
{
v2 = __strlen_chk(a2, -1LL);
result = i;
if ( i >= v2 )
break;
v6 = (v6 + 1) % 256;
v5 = (sbox[v6] + v5) % 256;
v4 = sbox[v6];
sbox[v6] = sbox[v5];
sbox[v5] = v4;
a2[i] ^= sbox[(sbox[v5] + sbox[v6]) % 256];
}
return result;
}
一个异或,一个 RC4 加密,找到了异或的字符串 “meow” 和要解密的数据
解密流程:RC4->异或->Base64->AES
#include<stdio.h>
#include<string.h>
char sbox[257] = {0};
char xork[] = "meow";
//s盒
void init_sbox(char*a1){
int i,j,k,tmp;
for ( i = 0; i < 0x100; i++ )
sbox[i] = i;
for ( i = 0; i < 0x100; i++ )
{
tmp = sbox[i];
j = (a1[k] + tmp + j) % 256;
sbox[i] = sbox[j];
sbox[j] = tmp;
if ( ++k >= strlen(a1))
k = 0;
}
}
//解密 RC4
void encc(char *a1,char*data){
init_sbox(a1);
int i,j,k,tmp;
for ( i = 0;i<strlen(data) ; i++ )
{
j = (j + 1) % 256;
k = (sbox[j] + k) % 256;
tmp = sbox[j];
sbox[j] = sbox[k];
sbox[k] = tmp;
data[i] ^= sbox[(sbox[j] + sbox[k]) % 256];
}
}
//异或
void enc(char *a1){
int i;
int len=strlen(a1);
for ( i = 0; i < len; ++i )
a1[i] ^= xork[i % 4];
encc(xork, a1);
}
int main(){
int i;
char mm[]={0xC2, 0x6C, 0x73, 0xF4, 0x3A, 0x45, 0x0E, 0xBA, 0x47, 0x81,
0x2A, 0x26, 0xF6, 0x79, 0x60, 0x78, 0xB3, 0x64, 0x6D, 0xDC, 0xC9, 0x04,
0x32, 0x3B, 0x9F, 0x32, 0x95, 0x60, 0xEE, 0x82, 0x97, 0xE7, 0xCA, 0x3D,
0xAA, 0x95, 0x76, 0xC5, 0x9B, 0x1D, 0x89, 0xDB, 0x98, 0x5D};
enc(mm);
for(i=0;i<44;i++){
putchar(mm[i]);
}
puts("");
}
AES 密钥是`MainActivity.title
也就是 “IamEzEncryptGame” ,厨子梭出来
Dirty_flowers
不能 f5,有花指令,那就按下 space 键看文本流程,直接将 push-pop 的指令也就是 0x4012f1~0x401302
的指令全部 nop 掉 ,然后在 main
函数头按下 U 和 P 重新编译
伪代码大概流程:输入 36 长度字符串,进入 sub_401100
加密,再与 sub_4011D0
判断
if ( strlen(v4) == 36 )
{
sub_401100(v4, 36);
if ( sub_4011D0(v4, 36) )
printf("success!\n");
else
printf("no!\n");
return 0;
}
else
{
printf("wrong length!\n");
return 0;
}
sub_401100
也有花指令,和上面的一样流程来去花
signed int __cdecl sub_401100(int a1, int a2)
{
signed int v2; // kr00_4
signed int result; // eax
int i; // [esp+1Ch] [ebp-1Ch]
char v5[16]; // [esp+24h] [ebp-14h] BYREF
strcpy(v5, "dirty_flower");
v2 = strlen(v5);
result = v2;
for ( i = 0; i < a2; ++i )
{
result = i + a1;
*(_BYTE *)(i + a1) ^= v5[i % v2];
}
return result;
}
sub_4011D0
函数比较内容大概是:(我比较喜欢看汇编流程图,感觉比伪代码更方便 :) 嘻嘻)
解密脚本
enc = [0x02, 0x05, 0x13, 0x13, 0x02, 0x1e, 0x53, 0x1f, 0x5c, 0x1a, 0x27, 0x43, 0x1d, 0x36, 0x43,
0x07, 0x26, 0x2d, 0x55, 0x0d, 0x03, 0x1b, 0x1c, 0x2d, 0x02, 0x1c, 0x1c, 0x30, 0x38, 0x32,
0x55, 0x02, 0x1b, 0x16, 0x54, 0x0f, 0x00]
str = "dirty_flower"
flag = ""
for i in range(len(enc)):
enc[i] ^= ord(str[i % len(str)])
flag += chr(enc[i])
print(flag)
Ptrace
ida 居然还能反编译.txt
,真的是 tql。第一次见这种类型的题目,看 wp 理解了半天,复现了俩小时(
记得将 son.txt
和 father.txt
放在同一目录下,打开 father.txt
文件
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp-14h] [ebp-30h]
int v5; // [esp-10h] [ebp-2Ch]
int v6; // [esp-Ch] [ebp-28h]
int v7; // [esp-8h] [ebp-24h]
int v8; // [esp-4h] [ebp-20h]
const char **v9; // [esp+0h] [ebp-1Ch]
char stat_loc[4]; // [esp+8h] [ebp-14h] BYREF
__pid_t v11; // [esp+Ch] [ebp-10h]
unsigned int v12; // [esp+10h] [ebp-Ch]
int *p_argc; // [esp+14h] [ebp-8h]
p_argc = &argc;
v9 = argv;
v12 = __readgsdword(0x14u);
puts("Please input your flag:");
__isoc99_scanf("%32s", &s, v4, v5, v6, v7, v8, v9);
v11 = fork(); // 创建子进程
if ( v11 )
{//父进程
if ( v11 <= 0 )
{
perror("fork");
return -1;
}
wait(stat_loc);
ptrace(PTRACE_POKEDATA, addr, addr, 3);
ptrace(PTRACE_CONT, 0, 0, 0);
wait(0);
}
else
{//子进程
ptrace(PTRACE_TRACEME, 0, 0, 0);
execl("./son", "son", &s, 0); //把 s 作为新进程的参数
}
return 0;
}
对于fork()
、execl()
函数:都是 Linux 中的进程控制函数
fork()
:创建新的进程,该进程几乎相当于当前进程的一个完全拷贝
execl()
:是函数族 exec()
之一,用来启动另外的进程以取代当前运行的进程
execl()
四个参数
参数 | 变量类型 | 解释 |
---|---|---|
绝对路径 | const char* | 文件存储路径 |
标识符 | const char* | 大多数时候是文件名 |
参数 | ------ | 选项 |
NULL | ------ | NULL |
所以 main()
函数中流程:fork()
创建子进程,返回的 pid
就是 v11
,v11
>0 为父进程,v11=0
为子进程,子进程中使用了 execl()
函数,启动当前目录下的 son
文件,传入 s
作为新进程的参数,这里的新进程替换掉之前的子进程,使自己变成子进程。
打开 son.txt
查看
int __cdecl sub_600011AD(int a1, int a2)
{
signed int i; // [esp+2h] [ebp-28h]
signed int j; // [esp+6h] [ebp-24h]
char *s; // [esp+Ah] [ebp-20h]
signed int v6; // [esp+Eh] [ebp-1Ch]
s = *(char **)(a2 + 4);
v6 = strlen(s);
for ( i = 0; i < v6; ++i )
byte_60004080[i] = ((int)(unsigned __int8)s[i] >> dword_60004040) | (s[i] << (8 - dword_60004040));
for ( j = 0; j < v6; ++j )
{
if ( byte_60004080[j] != byte_60004020[j] )
{
puts("this is Wrong~");
return 0;
}
}
puts("this is right~");
return 0;
}
这里的 s = *(char **)(a2 + 4)
,其实它就是指向 father
传入的 s
。execl
执行的命令为 ./son s
,而对于 son
文件的主函数而言,第一个参数是 a1
表示执行命令参数的个数,这里就是 2,而后面的 a2
真实类型为 const char*
,代表的就是命令的各个参数,所以这里的 a2 + 4
执行的就是第二个参数,也就是 s
.
大概流程为:将 s
中的每个字节循环移位来进行变化,最后与密文进行比较
对于 father
文件的 ptarce
,ptrace
是用于进程跟踪的,它提供了父进程可以观察和控制其子进程执行的能力,并允许父进程检查和替换子进程的内核镜像(包括寄存器)的值。其基本原理是: 当使用了ptrace
跟踪后,所有发送给被跟踪的子进程的信号(除了SIGKILL
),都会被转发给父进程,而子进程则会被阻塞,这时子进程的状态就会被系统标注为TASK_TRACED
。而父进程收到信号后,就可以对停止下来的子进程进行检查和修改,然后让子进程继续运行。
而这里查看子进程,可以发现使用 ptrace(PTRACE_TRACEME, 0, 0, 0);
,它就是允许父进程对自身进行调试的语句,然后在父进程中,使用 PTRACE_POKEDATA
对数据进行修改,然后使用 PTRACE_CONT
让子进程继续执行。因此我们要关注的就是父进程对子进程的什么数据进行了修改
我们能看到有语句ptrace(PTRACE_POKEDATA, addr, addr, 3);
,就是将 addr
所指向的地址进行了数据修改,更改为了 3
,点进去 addr
指向的就是 0x60004040
位置的数据
这个地址在 son
文件中也出现了
for ( i = 0; i < v6; ++i )
byte_60004080[i] = ((int)(unsigned __int8)s[i] >> dword_60004040) | (s[i] << (8 - dword_60004040));
所以这个 ptrace
修改的是偏移值,将 4 改为了 3
因此,按照偏移 3 进行逆向变换,脚本
enc = [0xCC, 0x8D, 0x2C, 0xEC, 0x6F, 0x88, 0xED, 0xEB, 0x2F, 0xED,
0xAE, 0xEB, 0x4E, 0xAC, 0x2C, 0x8D, 0x8D, 0x2F, 0xEB, 0x6D,
0xCD, 0xED, 0xEE, 0xEB, 0x0E, 0x8E, 0x4E, 0x2C, 0x6C, 0xAC,
0xE7, 0xAF]
for i in range(len(enc)):
enc[i] = (enc[i] << 3 | enc[i] >> 5)&0xff
print(chr(enc[i]), end='')
UPX
看了下 wp 需要脱壳,但是我拿到的附件已经是脱好壳的了,所以直接看伪代码
一般进行 upx 脱壳方法
upx -d "文件路径"
int __cdecl main(int argc, const char **argv, const char **envp)
{
int status; // [rsp+Ch] [rbp-4h]
puts("Please input your flag:");
__isoc99_scanf("%22s", s);
RC4(s, key);
for ( status = 0; status <= 21; ++status )
{
if ( s[status] != data[status] )
{
puts("this is Wrong~");
exit(status);
}
}
puts("this is right~");
return 0;
}
输入的字符串长度为 22
,然后进入 RC4
函数加密,后续进行 for
循环中,把s
和 data
进行比较
RC4 函数内容:
__int64 __fastcall RC4(const char *a1, __int64 a2)
{
__int64 result; // rax
unsigned __int8 v3; // [rsp+14h] [rbp-Ch]
unsigned __int8 v4; // [rsp+15h] [rbp-Bh]
unsigned int i; // [rsp+18h] [rbp-8h]
unsigned int v6; // [rsp+1Ch] [rbp-4h]
v3 = 0;
v4 = 0;
init_sbox(a2);
v6 = strlen(a1);
for ( i = 0; ; ++i )
{
result = i;
if ( i >= v6 )
break;
v4 += sbox[++v3];
swap(&sbox[v3], &sbox[v4]);
a1[i] ^= sbox[(unsigned __int8)(sbox[v3] + sbox[v4])];
}
return result;
}
RC4 加密,好讨厌写解密算法。。。
看 wp 可以进行动调,又学到了,直接下断点
这是 elf
文件,所以只能远程动调,连接 kali
随便输入字符串,断点断在了加密函数,查看数据就是我们输入的字符串
然后找到 data
,把数据提取出来
from ida_bytes import *
addr = 0x55FE34CF0040 //s的起始地址
enc = [0xC4, 0x60, 0xAF, 0xB9, 0xE3, 0xFF, 0x2E, 0x9B, 0xF5, 0x10,
0x56, 0x51, 0x6E, 0xEE, 0x5F, 0x7D, 0x7D, 0x6E, 0x2B, 0x9C,
0x75, 0xB5]
for i in range(22):
patch_byte(addr + i, enc[i])
print('Done')
点进 run
之后,s 就会有变化,然后 f9 运行又会断在与 data
比较的地方,这个时候可以看 s
的值,与之前又不一样,按 a
可以转化为字符串(又学到了!)
over~
没有评论