Reverse(十)
csaw2013reversing2
这道题需要ollydbg来做更方便些,需要修改汇编代码
本题直接运行弹窗是乱码的,因此首先ida32查看下代码逻辑
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // ecx
CHAR *lpMem; // [esp+8h] [ebp-Ch]
HANDLE hHeap; // [esp+10h] [ebp-4h]
hHeap = HeapCreate(0x40000u, 0, 0);
lpMem = (CHAR *)HeapAlloc(hHeap, 8u, SourceSize + 1); // 很明显从内存中取出
memcpy_s(lpMem, SourceSize, &unk_409B10, SourceSize);
if ( !sub_40102A() && !IsDebuggerPresent() ) // 默认进去
{
MessageBoxA(0, lpMem + 1, "Flag", 2u); // 此处弹窗显示flag
HeapFree(hHeap, 0, lpMem);
HeapDestroy(hHeap);
ExitProcess(0);
}
__debugbreak();
sub_401000(v3 + 4, lpMem);
ExitProcess(0xFFFFFFFF);
}
要想获得正确flag,需要经过sub_401000函数处理,因此下图中判断完是否调试器后test完,修改ZF位,使得跳转不成立,去执行sub_401000函数
同样要注意INT3是异常中断指令,因此可以右键在下一行mov上选择EIP为此时位置,调用完到JMP时再切换回下面调用MessageBoxA的开始位置(即008B10B9),运行即可显示flag
补充:整型溢出
re4-unvm-me
pyc文件反编译即可
# uncompyle6 version 3.9.0
# Python bytecode version base 2.7 (62211)
# Decompiled from: Python 3.9.12 (main, Apr 4 2022, 05:22:27) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: unvm_me.py
# Compiled at: 2016-12-21 05:44:01
import md5
md5s = [
174282896860968005525213562254350376167, 137092044126081477479435678296496849608,
126300127609096051658061491018211963916, 314989972419727999226545215739316729360,
256525866025901597224592941642385934114, 115141138810151571209618282728408211053,
8705973470942652577929336993839061582, 256697681645515528548061291580728800189,
39818552652170274340851144295913091599, 65313561977812018046200997898904313350,
230909080238053318105407334248228870753, 196125799557195268866757688147870815374,
74874145132345503095307276614727915885]
print 'Can you turn me back to python ? ...'
flag = raw_input('well as you wish.. what is the flag: ')
if len(flag) > 69:
print 'nice try'
exit()
if len(flag) % 5 != 0:
print 'nice try'
exit()
for i in range(0, len(flag), 5):
s = flag[i:i + 5]
if int('0x' + md5.new(s).hexdigest(), 16) != md5s[i / 5]:
print 'nice try'
exit()
print 'Congratz now you have the flag'
暴力破解,虽然可能花些时间,有个关键点注意m = hashlib.md5(),这句话必须每次都要创建一个新的对象,否则加密结果是错的,不能重复调用
# 暴力破解5位md5
import hashlib
p = ['', '', '', '', '', '', '', '', '', '', '', '', '']
md5s = [
174282896860968005525213562254350376167, 137092044126081477479435678296496849608,
126300127609096051658061491018211963916, 314989972419727999226545215739316729360,
256525866025901597224592941642385934114, 115141138810151571209618282728408211053,
8705973470942652577929336993839061582, 256697681645515528548061291580728800189,
39818552652170274340851144295913091599, 65313561977812018046200997898904313350,
230909080238053318105407334248228870753, 196125799557195268866757688147870815374,
74874145132345503095307276614727915885]
md5s = [str(hex(i))[2:] for i in md5s]
for i1 in range(48, 127):
for i2 in range(48, 127):
for i3 in range(48, 127):
for i4 in range(48, 127):
for i5 in range(48, 127):
m = hashlib.md5()
str = chr(i1)+chr(i2)+chr(i3)+chr(i4)+chr(i5)
m.update(str.encode('utf8'))
md5 = m.hexdigest()
if md5 in md5s:
p[md5s.index(md5)] = str
print(p) #
当然最好还是直接找md5解密网站快多了
crackme
exeinfo查看得知是nSpack壳,学习下相关知识
- esp定律:也称堆栈平衡定律,如果要返回父程序,则当我们在堆栈中进行堆栈的操作的时候,一定要保证在RET这条指令之前,ESP指向的是我们压入栈中的地址。
- OEP:程序入口点,可以直接OD载入
以题目为例手动脱nSpack壳(北斗壳):
- OD载入直接跳到了PUSHFD
- F8执行到PUSHAD,此时ESP变化,对ESP右键设置断点(HW break)
- F9执行到POPFD,下面有一条JMP指令
- F8跳转到JMP位置,00401336就是OEP地址
- 右键使用Dump Process,将EIP设置为OEP,转储
另一种方法是到这里,使用PEtool管理员运行,找到这个程序(3fdxxx.exe)右键Dump all
- 再去ida反编译可以成功看到伪代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
void (__cdecl *v3)(char *); // esi
int v5; // eax
char v6; // [esp+4h] [ebp-38h] BYREF
char v7[51]; // [esp+5h] [ebp-37h] BYREF
v6 = 0;
sub_4018F4(v7, 0, 49);
v3 = (void (__cdecl *)(char *))dword_402094;
dword_402094(aPleaseInputFla); // Please Input Flag:
dword_402090(&v6, 44);
if ( strlen(&v6) == 42 )
{
v5 = 0;
while ( (v7[v5 - 1] ^ byte_402130[v5 % 16]) == dword_402150[v5] )// v7其实就是v6数组
{
if ( ++v5 >= 42 )
{
v3(aRight); // right!
return 0;
}
}
v3(aError);
return 0;
}
else
{
v3(aError);
return -1;
}
}
debug
exeinfo提示dotfuscator进行了加壳混淆,因此使用de4dot来脱壳
然后dnspy查看.net程序,并定位关键函数
using System;
using System.Security.Cryptography;
using System.Text;
// Token: 0x02000002 RID: 2
internal class Class0
{
// Token: 0x06000001 RID: 1 RVA: 0x000020C8 File Offset: 0x000002C8
private static int smethod_0(int int_0, int int_1)
{
return (new int[]
{
2,
3,
5,
7,
11,
13,
17,
19,
23,
29,
31,
37,
41,
43,
47,
53,
59,
61,
67,
71,
73,
79,
83,
89,
97,
101,
103,
107,
109,
113
})[int_1] ^ int_0;
}
// Token: 0x06000002 RID: 2 RVA: 0x000020E8 File Offset: 0x000002E8
private static string smethod_1(string string_0)
{
byte[] bytes = Encoding.ASCII.GetBytes(string_0);
return "flag{" + BitConverter.ToString(new MD5CryptoServiceProvider().ComputeHash(bytes)).Replace("-", "") + "}";
}
// Token: 0x06000003 RID: 3 RVA: 0x00002130 File Offset: 0x00000330
private static void smethod_2(string string_0, int int_0, ref string string_1)
{
int num = 0;
if (0 < string_0.Length)
{
do
{
char c = string_0[num];
int num2 = 1;
do
{
c = Convert.ToChar(Class0.smethod_0(Convert.ToInt32(c), num2));
num2++;
}
while (num2 < 15);
string_1 += c;
num++;
}
while (num < string_0.Length);
}
string_1 = Class0.smethod_1(string_1);
}
// Token: 0x06000004 RID: 4 RVA: 0x00002198 File Offset: 0x00000398
private static void Main(string[] args)
{
string b = null;
string value = string.Format("{0}", DateTime.Now.Hour + 1);
string string_ = "CreateByTenshine";
Class0.smethod_2(string_, Convert.ToInt32(value), ref b); // 加密函数赋给b
string a = Console.ReadLine();
if (a == b)
{
Console.WriteLine("u got it!");
Console.ReadKey(true);
}
else
{
Console.Write("wrong");
}
Console.ReadKey(true);
}
}
解密脚本
#include<iostream>
#include<string>
using namespace std;
int main() {
int a[] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113};
string s = "CreateByTenshine";
string flag = "";
for (int i = 0; i < s.length(); i++) {
int num2 = 1;
int c = (int)s[i];
do {
c = a[num2] ^ c;
num2++;
} while (num2 < 15);
flag += c;
}
cout << flag << endl;
}
得到的结果md5取大写,然后加上flag{}即可
ReverseMe-120
https://blog.csdn.net/xiao__1bai/article/details/120107210
这道题难度在于读懂代码,涉及到很多考点
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned int v3; // edx
unsigned int v4; // ecx
__m128i si128; // xmm1
unsigned int v6; // esi
const __m128i *v7; // eax
__m128i v8; // xmm0
int v9; // eax
char v11[100]; // [esp+0h] [ebp-CCh] BYREF
char v12[100]; // [esp+64h] [ebp-68h] BYREF
unsigned int v13; // [esp+C8h] [ebp-4h]
printf("please input your flah:");
memset(v11, 0, sizeof(v11));
scanf("%s", v11); // 输入赋给了v11
memset(v12, 0, sizeof(v12));
sub_401000(v11, strlen(v11)); // 一个核心函数,后面分析可知是base64解密函数
v3 = v13; // 这里最开始搞不懂为啥v13没初始化就有值,其实是在上面sub_401000函数里
v4 = 0;
if ( v13 )
{
if ( v13 >= 0x10 )
{
si128 = _mm_load_si128((const __m128i *)&xmmword_414F20);// 用于加载数据,从内存到暂存器,查看可知都是0x25
v6 = v13 - (v13 & 0xF);
v7 = (const __m128i *)v12;
do
{
v8 = _mm_loadu_si128(v7); // 读取16字节
v4 += 16;
++v7;
v7[-1] = _mm_xor_si128(v8, si128); // 相当于v12结果每一位和si128即0x25异或
}
while ( v4 < v6 );
}
for ( ; v4 < v3; ++v4 ) // 没用,貌似得调试发现
v12[v4] ^= 0x25u;
}
v9 = strcmp(v12, "you_know_how_to_remove_junk_code");// 说明有垃圾代码没有运行起干扰作用
if ( v9 )
v9 = v9 < 0 ? -1 : 1;
if ( v9 )
printf("wrong\n");
else
printf("correct\n");
system("pause");
return 0;
}
sub_401000之所以参数多于实际参数是因为有寄存器传参
int __usercall sub_401000@<eax>(unsigned int *a1@<edx>, _BYTE *a2@<ecx>, unsigned __int8 *a3, unsigned int a4)
{
int v4; // ebx
unsigned int v5; // eax
int v6; // ecx
unsigned __int8 *v7; // edi
int v8; // edx
bool v9; // zf
unsigned __int8 v10; // cl
char v11; // cl
_BYTE *v12; // esi
unsigned int v13; // ecx
int v14; // ebx
unsigned __int8 v15; // cl
char v16; // dl
int v20; // [esp+14h] [ebp-4h]
unsigned int v21; // [esp+14h] [ebp-4h]
int i; // [esp+24h] [ebp+Ch]
v4 = 0;
v5 = 0;
v6 = 0;
v20 = 0;
if ( !a4 )
return 0;
v7 = a3;
do
{
v8 = 0;
v9 = v5 == a4;
if ( v5 < a4 )
{
do
{
if ( a3[v5] != 32 )
break;
++v5;
++v8;
}
while ( v5 < a4 );
v9 = v5 == a4;
}
if ( v9 )
break;
if ( a4 - v5 >= 2 && a3[v5] == 13 && a3[v5 + 1] == 10 || (v10 = a3[v5], v10 == 10) )
{
v6 = v20;
}
else
{
if ( v8 )
return -44;
if ( v10 == 61 && (unsigned int)++v4 > 2 )
return -44;
if ( v10 > 0x7Fu )
return -44;
v11 = byte_414E40[v10];
if ( v11 == 127 || (unsigned __int8)v11 < 0x40u && v4 )
return -44;
v6 = ++v20;
}
++v5;
}
while ( v5 < a4 );
if ( !v6 )
return 0;
v12 = a2;
v13 = ((unsigned int)(6 * v6 + 7) >> 3) - v4;
if ( a2 && *a1 >= v13 )
{
v21 = 3;
v14 = 0;
for ( i = 0; v5; --v5 )
{
v15 = *v7;
if ( *v7 != 13 && v15 != 10 && v15 != 32 )
{
v16 = byte_414E40[v15];
v21 -= v16 == 64;
v14 = v16 & 0x3F | (v14 << 6); // base64解密特征
if ( ++i == 4 )
{
i = 0;
if ( v21 )
*v12++ = BYTE2(v14);
if ( v21 > 1 )
*v12++ = BYTE1(v14);
if ( v21 > 2 )
*v12++ = v14;
}
}
++v7;
}
*a1 = v12 - a2;
return 0;
}
*a1 = v13;
return -42;
}
流程:base64解密--xor 0x25--明文比较
逆向:明文逐位和0x25异或--base64加密
得到:XEpQek5LSlJ6TUpSelFKeldASEpTQHpPUEtOekZKQUA=
EASYHOOK
找到主函数和hook函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
HANDLE FileA; // eax
DWORD NumberOfBytesWritten; // [esp+4h] [ebp-24h] BYREF
char Buffer[32]; // [esp+8h] [ebp-20h] BYREF
sub_401370(aPleaseInputFla);
scanf("%31s", Buffer);
if ( strlen(Buffer) == 19 )
{
sub_401220(); // hook WriteFile
FileA = CreateFileA(FileName, 0x40000000u, 0, 0, 2u, 0x80u, 0);// Your_input
WriteFile(FileA, Buffer, 0x13u, &NumberOfBytesWritten, 0);
sub_401240(Buffer, &NumberOfBytesWritten);
if ( NumberOfBytesWritten == 1 )
sub_401370(aRightFlagIsYou);
else
sub_401370(aWrong);
system(Command);
return 0;
}
else
{
sub_401370(aWrong);
system(Command);
return 0;
}
}
int sub_401220()
{
HMODULE LibraryA; // eax
DWORD CurrentProcessId; // eax
CurrentProcessId = GetCurrentProcessId();
hProcess = OpenProcess(0x1F0FFFu, 0, CurrentProcessId);
LibraryA = LoadLibraryA(LibFileName);
WriteFile_0 = (BOOL (__stdcall *)(HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED))GetProcAddress(LibraryA, ProcName);
lpAddress = WriteFile_0;
if ( !WriteFile_0 )
return sub_401370(&unk_40A044);
unk_40C9B4 = *(_DWORD *)lpAddress;
*((_BYTE *)&unk_40C9B4 + 4) = *((_BYTE *)lpAddress + 4);
byte_40C9BC = -23;
dword_40C9BD = (char *)sub_401080 - (char *)lpAddress - 5; // 计算被替换成的函数与WriteFile函数地址偏移
return sub_4010D0(); // hook
}
定位hook替换的函数
int __stdcall sub_401080(
HANDLE hFile,
LPCVOID lpBuffer, // 输入的字符串
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten, // 要确保lpNumberOfBytesWritten==1
LPOVERLAPPED lpOverlapped)
{ // 实际上被hook替换成的函数
int v5; // ebx
v5 = sub_401000(lpBuffer, nNumberOfBytesToWrite); // 关键函数
sub_401140();
WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped); // 原来的功能
if ( v5 ) // v5必须不为0
*lpNumberOfBytesWritten = 1;
return 0;
}
int __cdecl sub_401000(int a1, int a2)
{
char i; // al
char v3; // bl
char v4; // cl
int v5; // eax
for ( i = 0; i < a2; ++i ) // 对输入字符串加密
{
if ( i == 18 )
{
*(_BYTE *)(a1 + 18) ^= 0x13u;
}
else
{
if ( i % 2 )
v3 = *(_BYTE *)(i + a1) - i;
else
v3 = *(_BYTE *)(i + a1 + 2);
*(_BYTE *)(i + a1) = i ^ v3;
}
}
v4 = 0;
if ( a2 <= 0 )
return 1;
v5 = 0;
while ( byte_40A030[v5] == *(_BYTE *)(v5 + a1) )
{
v5 = ++v4;
if ( v4 >= a2 )
return 1; // 要确保到这里,也就是说循环不能退出
}
return 0;
}
写脚本逆向,一定要记得运算符优先性,记得加括号,然后第一位可以猜到是f(没法逆向知道具体值)
b = [0x61, 0x6A, 0x79, 0x67, 0x6B, 0x46, 0x6D, 0x2E, 0x7F, 0x5F, 0x7E, 0x2D, 0x53, 0x56, 0x7B, 0x38, 0x6D, 0x4C, 0x6E]
a1 = ['' for i in range(19)]
for i in range(0, 19):
if i == 18:
a1[i] = chr(b[i] ^ 0x13)
else:
if i % 2:
a1[i] = chr((b[i] ^ i) + i)
else:
a1[i + 2] = chr(b[i] ^ i)
flag = ''.join(a1)
print(flag)
gametime
运行发现是个游戏,首先ida静态分析下,主函数超长,经过分析发现只有game里的函数(两个,sub_401435和sub_401507,功能基本一样)是关键
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // edi
unsigned int v4; // eax
int v5; // ecx
int v6; // ecx
int v7; // ecx
void (__stdcall *v8)(DWORD); // ebx
int v9; // esi
int v10; // esi
int v11; // esi
int v12; // esi
int v13; // esi
int i; // edi
int v15; // esi
unsigned __int8 *v16; // esi
int v17; // ebx
int v18; // esi
int v19; // eax
int v20; // esi
int v21; // eax
char v22; // cl
int v23; // eax
int v25; // [esp+10h] [ebp-20h]
int v26; // [esp+14h] [ebp-1Ch] BYREF
char v27; // [esp+1Bh] [ebp-15h]
_WORD v28[3]; // [esp+1Ch] [ebp-14h] BYREF
int v29; // [esp+22h] [ebp-Eh]
int v30; // [esp+26h] [ebp-Ah]
__int16 v31; // [esp+2Ah] [ebp-6h]
strcpy((char *)v28, " ");
v26 = 7630702;
*(_DWORD *)&v28[1] = 0;
v29 = 0;
v30 = 0;
v31 = 0;
v3 = 0;
v25 = 0;
printf((int)"\r\tZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMG\n");
printf((int)"\tkey is %s (%s)", (const char *)&v26, (const char *)v28);
sub_401423(); // 相当于消除上一行,光标移动到开始了
printf((int)"\r\tZOMGZOMG ZOMGZOMG\n");
printf((int)"\tkey is %s (%s)", (const char *)&v26, (const char *)v28);
sub_401423();
printf((int)"\r\tZOMGZOMG TAP TAP REVOLUTION!!!!!!! ZOMGZOMG\n");
printf((int)"\tkey is %s (%s)", (const char *)&v26, (const char *)v28);
sub_401423();
printf((int)"\r\tZOMGZOMG ZOMGZOMG\n");
printf((int)"\tkey is %s (%s)", (const char *)&v26, (const char *)v28);
sub_401423();
printf((int)"\r\tZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMG\n\n\n");
printf((int)"\tkey is %s (%s)", (const char *)&v26, (const char *)v28);
sub_401423();
printf((int)"\r\t R U READDY?!\n\n\n");
printf((int)"\tkey is %s (%s)", (const char *)&v26, (const char *)v28);
sub_401423();
printf((int)"\rThe game is starting in...\n");
v4 = _time64(0);
srand(v4);
sub_4012B2(); // 10秒倒计时
sub_4012D5(0xC8u); // 打印Get ready to play
if ( !sub_401435(0x1F4u, 32, 10, v5, (const char *)v28, (const char *)&v26) )// sub_401435传入参数,第一个倒计时时间,第二个需要按下的字符,第三个点号出现次数(出现完要按下空格),后两个没用
return 0; // 必须三个game都成功,否则return
if ( !sub_401435(0x12Cu, 120, 8, v6, (const char *)v28, (const char *)&v26) )
return 0;
if ( !sub_401435(0x12Cu, 109, 5, v7, (const char *)v28, (const char *)&v26) )
return 0;
printf((int)"key is %s (%s)", (const char *)&v26, (const char *)v28);
printf((int)"\rTRAINING COMPLETE! \n");
v8 = Sleep;
v9 = 20;
do
{
Sleep(0xC8u);
printf((int)"\n");
--v9;
}
while ( v9 ); // 换行20次
printf((int)"key is %s (%s)", (const char *)&v26, (const char *)v28);
printf((int)"\rNow you know everything you need to know");
v10 = 4;
do
{
printf((int)".");
Sleep(0x3E8u);
--v10;
}
while ( v10 );
printf((int)"\n\n\nfor the rest of your life!\n");
v11 = 20;
do
{
Sleep(0xC8u);
printf((int)"\n");
--v11;
}
while ( v11 );
printf((int)"LETS PLAY !\n");
v12 = 20;
do
{
Sleep(0xC8u);
printf((int)"\n");
--v12;
}
while ( v12 );
sub_4012B2();
sub_4012D5(0x64u);
if ( !sub_401507(5, 32, 0xC8u, (const char *)v28, (const char *)&v26) )// 看着跟上面很像,传入参数第一个是点号打印次数,第二是按下的字符,第三个是必须按下字符的规定时间,后两个还是没用
return 0;
if ( !sub_401507(2, 120, 0xC8u, (const char *)v28, (const char *)v28) )
return 0;
if ( !sub_401507(1, 109, 0xC8u, (const char *)v28, (const char *)v28) )
return 0;
printf((int)"key is %s (%s)", (const char *)&v26, (const char *)v28);
sub_401423();
printf((int)"\rooooh, you fancy!!!\n");
if ( !sub_401507(5, 109, 0xC8u, (const char *)v28, (const char *)&v26)// 再来一轮,必须都成功
|| !sub_401507(2, 120, 0xC8u, (const char *)v28, (const char *)&v26)
|| !sub_401507(1, 32, 0xC8u, (const char *)v28, (const char *)&v26) )
{
return 0;
}
printf((int)"key is %s (%s)", (const char *)&v26, (const char *)v28);
printf((int)"\b\b");
printf((int)"NIIICE JOB)!!!!\n");
v13 = 20;
do
{
Sleep(0x32u);
printf((int)"\n");
--v13;
}
while ( v13 );
v27 = 1;
do // 从这里开始难以静态分析,因此直接让代码自己动态运行就好,关键是绕开game的判断
{
if ( v3 % 3 == 1 )
{
printf((int)"key is %s (%s)", (const char *)&v26, (const char *)v28);
sub_401423();
printf((int)"\rTURBO TIME! \n");
for ( i = 0; i < 20; ++i )
{
v8(0x32u);
printf((int)"\n"); // 打印换行符,直到最后一次
if ( i == 19 )
{
v15 = sub_40141D();
sub_401D02(v28, v15 - 5514);
dword_41A1F8 = (int)v28;
dword_41A1FC = v15 - 5498;
sub_401AA5();
sub_401CC9();
printf((int)"key is %s (%s)", byte_417D02, (const char *)&v26);
printf((int)"\b\b");
v16 = (unsigned __int8 *)v28;
v17 = 16;
do
{
printf((int)"%02x", *v16++);
--v17;
}
while ( v17 );
printf((int)")\n\n");
v8 = Sleep;
}
}
v18 = 0;
while ( 1 )
{
v19 = rand();
if ( !sub_401507(1, byte_417B08[v19 % 3], 0x64u, (const char *)v28, (const char *)&v26) )
break;
if ( ++v18 >= 10 )
goto LABEL_33;
}
v27 = 0;
LABEL_33:
v3 = v25;
}
v20 = 0;
while ( 1 )
{
v21 = rand();
v22 = v27;
v23 = v21 % 3;
if ( v27 )
break;
LABEL_38:
if ( ++v20 >= 10 )
goto LABEL_41;
}
if ( sub_401507(v23 + 3, byte_417B08[v23], 0x64u, (const char *)v28, (const char *)&v26) )
{
v22 = v27;
goto LABEL_38;
}
v22 = 0;
v27 = 0;
LABEL_41:
if ( v3 == 1337 )
{
sub_4012F6(); // 成功结算
v22 = v27;
}
v25 = ++v3;
}
while ( v22 );
return 0;
}
sub_401435和sub_401507中我们只看一个代码就好
char __usercall sub_401435@<al>(DWORD a1@<edx>, int a2@<ecx>, int a3, int a4, const char *a5, const char *a6)
{
int v8; // edi
printf((int)"key is %s (%s)", a6, a5);
sub_401423();
printf((int)"\rZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMG\n");
if ( a2 == 32 )
printf((int)"\nWhen you see an 's', press the space bar\n\n");
else
printf((int)"\nWhen you see an '%c', press the '%c' key\n\n", a2, a2);
printf((int)"key is %s (%s)", a6, a5);
sub_401423();
printf((int)"\rZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMG\n");
sub_4012D5(a1);
v8 = a3;
if ( a3 > 0 )
{
do
{
printf((int)".");
Sleep(0xC8u);
--v8;
}
while ( v8 );
}
if ( (unsigned __int8)sub_401260(a2, 100000) )// 判断是否规定时间内按下对应字符
return 1;
printf((int)"key is %s (%s)\r", a6, a5);
sub_401423();
printf((int)"\rUDDER FAILURE! http://imgur.com/4Ajx21P \n");// 失败
return 0;
}
只需保证return为1即可游戏成功,因此ollydbg定位所有带“UDDER FAILURE! http://imgur.com/4Ajx21P”这句话,后面的return改成1
如图,原来是XOR AL, AL,表示清0返回0,现在改成和下面一样,return 1(注意改两处)
F9让ollydbg自动跑,一段时间后就会发现打印了key
notsequence
ida打开,定位主函数
int __cdecl main()
{
_DWORD *v0; // eax
int v2; // [esp+14h] [ebp-Ch]
_DWORD *v3; // [esp+1Ch] [ebp-4h]
memset(&unk_8049BE0, 0, 0x4000u);
puts("input raw_flag please:");
v3 = &unk_8049BE0;
do
{
v0 = v3++;
scanf("%d", v0);
}
while ( *(v3 - 1) );
v2 = sub_80486CD((int)&unk_8049BE0); // 第一个加密,返回不能为0
if ( v2 == -1 )
{
printf("check1 not pass");
system("pause");
}
if ( (unsigned __int8)sub_8048783((int)&unk_8049BE0, v2) != 1 )// 第二个加密,返回必须为1
{
printf("check2 not pass!");
exit(0);
}
if ( v2 == 20 ) // 这里可知v2=20
{
puts("Congratulations! fl4g is :\nRCTF{md5(/*what you input without space or \\n~*/)}");
exit(0);
}
return 0;
}
分析两个加密函数
sub_80486CD
int __cdecl sub_80486CD(int a1) { int j; // [esp+0h] [ebp-14h] int v3; // [esp+4h] [ebp-10h] int i; // [esp+8h] [ebp-Ch] int v5; // [esp+Ch] [ebp-8h] v5 = 0; for ( i = 0; i <= 1024 && *(_DWORD *)(4 * i + a1); i = v5 * (v5 + 1) / 2 )// v5<=44 { // i=0、1、3、6 ... 19x(19+1)/2=190 v3 = 0; for ( j = 0; j <= v5; ++j ) v3 += *(_DWORD *)(4 * (j + i) + a1); // int数组 if ( 1 << v5 != v3 ) // v3必须等于2^v5 return -1; ++v5; } return v5; // 由主函数知v5=20 }
该代码逻辑是:逐个取1、2、3...n...19个数,每次取的数之和等于$2^{n-1}$
sub_8048783
int __cdecl sub_8048783(int a1, int a2) { int v3; // [esp+10h] [ebp-10h] int v4; // [esp+14h] [ebp-Ch] int i; // [esp+18h] [ebp-8h] int v6; // [esp+1Ch] [ebp-4h] v6 = 0; for ( i = 1; i < a2; ++i ) // a2=20 { v4 = 0; v3 = i - 1; if ( !*(_DWORD *)(4 * i + a1) ) return 0; while ( a2 - 1 > v3 ) { v4 += *(_DWORD *)(4 * (v3 * (v3 + 1) / 2 + v6) + a1);// V6比i小1 ++v3; } if ( *(_DWORD *)(4 * (v3 * (v3 + 1) / 2 + i) + a1) != v4 )// 最后一行 return 0; ++v6; } return 1; }
具体逻辑不好分析代码:大意是分为1、2、3...n...19个数,除了最后一组的其他每组数的第i个数之和=最后一组数第i+1数(至此可知是杨辉三角构造,最好别钻牛角尖想最后一行,直接类推分析,只有两行(1 11)、只有三行(1 11 121)、...就能看出来了)
至此代码分析完成,得知输入的是19行杨辉三角数,去除空格再md5
import hashlib
def get_row(n):
row = [1] * (n + 1)
for i in range(1, n):
# 由于每一行都是上一行的基础上得到的,我们可以从第二项开始进行遍历
for j in range(i, 0, -1):
# 每一项都是它前面两项之和
row[j] = row[j] + row[j - 1]
row = [str(i) for i in row]
return row
yanghui = []
for i in range(0, 20):
yanghui.extend(get_row(i))
print("".join(yanghui))
md5 = hashlib.md5()
md5.update("".join(yanghui).encode('utf-8'))
print(md5.hexdigest()) # 37894beff1c632010dd6d524aa9604db
key
这道题主函数十分复杂,但是可以ida看流程
首先第一处分支:走错会打印what happen?(深入去读会发现是一个读取文件操作,不管直接看正确路线)
读不懂,看了wp,结合代码前期逻辑知道代码会生成一段字符串和从flag文件中的内容比较
int sub_401100()
{
signed int v0; // esi
signed int v1; // esi
unsigned int v2; // edi
void **v3; // ebx
void **v4; // eax
int v5; // ecx
int v6; // ST04_4
int v7; // ST08_4
int v8; // ST0C_4
int v9; // eax
int v10; // ST0C_4
char *v11; // esi
int v12; // ecx
void **v13; // eax
int v14; // eax
int v15; // ST0C_4
int v16; // eax
int v17; // ST0C_4
int v18; // eax
int v19; // ST0C_4
int v20; // eax
int v21; // ST0C_4
int v22; // eax
int v23; // ST0C_4
int v24; // eax
int v25; // ST0C_4
int v26; // eax
int v27; // ST0C_4
int v28; // eax
int result; // eax
int v30; // [esp-4h] [ebp-13Ch]
int Dst; // [esp+14h] [ebp-124h]
char v32[4]; // [esp+20h] [ebp-118h]
char v33; // [esp+24h] [ebp-114h]
int v34; // [esp+5Ch] [ebp-DCh]
char v35; // [esp+61h] [ebp-D7h]
int v36; // [esp+64h] [ebp-D4h]
int v37; // [esp+68h] [ebp-D0h]
char v38; // [esp+6Ch] [ebp-CCh]
FILE *File; // [esp+70h] [ebp-C8h]
char v40; // [esp+84h] [ebp-B4h]
void *v41; // [esp+CCh] [ebp-6Ch]
int v42; // [esp+DCh] [ebp-5Ch]
unsigned int v43; // [esp+E0h] [ebp-58h]
void *v44; // [esp+E4h] [ebp-54h]
int v45; // [esp+F4h] [ebp-44h]
unsigned int v46; // [esp+F8h] [ebp-40h]
void *Memory[4]; // [esp+FCh] [ebp-3Ch]
int v48; // [esp+10Ch] [ebp-2Ch]
unsigned int v49; // [esp+110h] [ebp-28h]
__int128 v50; // [esp+114h] [ebp-24h]
__int16 v51; // [esp+124h] [ebp-14h]
char v52; // [esp+126h] [ebp-12h]
int v53; // [esp+134h] [ebp-4h]
v46 = 15;
v45 = 0;
LOBYTE(v44) = 0;
v53 = 0;
v43 = 15;
v42 = 0;
LOBYTE(v41) = 0;
LOBYTE(v53) = 1;
v0 = 0;
v48 = 1684630885;
LOWORD(v49) = 97;
*(_OWORD *)Memory = xmmword_40528C; // 小端'adimehtadimehtadimeht',正常大端是'themidathemidathemida'
v51 = 11836;
v52 = 0;
v50 = xmmword_4052A4; // 小端'.<<<<....++++---->',正常大端是'>----++++....<<<<.'
do
{
sub_4021E0(1u, (*((_BYTE *)Memory + v0) ^ *((_BYTE *)&v50 + v0)) + 22); // 做了异或再+22
++v0;
}
while ( v0 < 18 );
v1 = 0;
v49 = 15;
v48 = 0;
LOBYTE(Memory[0]) = 0;
LOBYTE(v53) = 2;
v2 = v43;
v3 = (void **)v41;
do
{
v4 = &v41;
if ( v2 >= 0x10 )
v4 = v3;
sub_4021E0(1u, *((_BYTE *)v4 + v1++) + 9); // 再+9
}
while ( v1 < 18 );
memset(&Dst, 0, 0xB8u);
sub_401620(v5, v6, v7, v8);
LOBYTE(v53) = 3;
if ( v32[*(_DWORD *)(Dst + 4)] & 6 )
{
v9 = sub_402A00(std::cerr, "?W?h?a?t h?a?p?p?e?n?", sub_402C50);
std::basic_ostream<char,std::char_traits<char>>::operator<<(v9, v10);
exit(-1);
}
sub_402E90(&Dst, &v44);
v11 = &v33;
if ( File )
{
if ( !(unsigned __int8)sub_4022F0(&v33) )
v11 = 0;
if ( fclose(File) )
v11 = 0;
}
else
{
v11 = 0;
}
v38 = 0;
v35 = 0;
std::basic_streambuf<char,std::char_traits<char>>::_Init(&v33);
v36 = dword_408590;
File = 0;
v37 = dword_408594;
v34 = 0;
if ( !v11 )
std::basic_ios<char,std::char_traits<char>>::setstate((char *)&Dst + *(_DWORD *)(Dst + 4), 2, 0);
v13 = Memory;
if ( v49 >= 0x10 )
v13 = (void **)Memory[0];
if ( sub_4020C0(v12, v45, v13, v48) )
{
v28 = sub_402A00(std::cout, "=W=r=o=n=g=K=e=y=", sub_402C50);
}
else
{
v14 = sub_402A00(std::cout, "|------------------------------|", sub_402C50);
std::basic_ostream<char,std::char_traits<char>>::operator<<(v14, v15);
v16 = sub_402A00(std::cout, "|==============================|", sub_402C50);
std::basic_ostream<char,std::char_traits<char>>::operator<<(v16, v17);
v18 = sub_402A00(std::cout, "|==============================|", sub_402C50);
std::basic_ostream<char,std::char_traits<char>>::operator<<(v18, v19);
v20 = sub_402A00(std::cout, "|==============================|", sub_402C50);
std::basic_ostream<char,std::char_traits<char>>::operator<<(v20, v21);
v22 = sub_402A00(std::cout, "\\ /\\ /\\ /\\ /\\==============|", sub_402C50);
std::basic_ostream<char,std::char_traits<char>>::operator<<(v22, v23);
v24 = sub_402A00(std::cout, " \\/ \\/ \\/ \\/ \\=============|", sub_402C50);
std::basic_ostream<char,std::char_traits<char>>::operator<<(v24, v25);
v26 = sub_402A00(std::cout, " |-------------|", sub_402C50);
std::basic_ostream<char,std::char_traits<char>>::operator<<(v26, v27);
std::basic_ostream<char,std::char_traits<char>>::operator<<(std::cout, sub_402C50);
v28 = sub_402A00(std::cout, "Congrats You got it!", sub_402C50);
}
std::basic_ostream<char,std::char_traits<char>>::operator<<(v28, v30);
sub_401570(&v40);
std::basic_ios<char,std::char_traits<char>>::~basic_ios<char,std::char_traits<char>>(&v40);
if ( v49 >= 0x10 )
sub_402630(Memory[0], v49 + 1);
if ( v2 >= 0x10 )
sub_402630(v3, v2 + 1);
result = v46;
if ( v46 >= 0x10 )
result = sub_402630(v44, v46 + 1);
return result;
}
python解密脚本
Windows_Reverse1
upx脱壳,ida分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[1024]; // [esp+4h] [ebp-804h] BYREF
char v5[1024]; // [esp+404h] [ebp-404h] BYREF
memset(v5, 0, sizeof(v5));
memset(v4, 0, sizeof(v4));
printf("please input code:");
scanf("%s", v5);
sub_401000(v5); //加密函数,对输入做处理
if ( !strcmp(v4, "DDCTF{reverseME}") ) // 这里是用v4来比较,说明上面函数中对v4做了处理
printf("You've got it!!%s\n", v4);
else
printf("Try again later.\n");
return 0;
}
查看sub_401000函数,发现唯一可能赋值给主函数里v4的是v1
unsigned int __cdecl sub_401000(const char *a1)
{
_BYTE *v1; // ecx
unsigned int v2; // edi
unsigned int result; // eax
const char *v4; // ebx
v2 = 0;
result = strlen(a1);
if ( result )
{
v4 = (const char *)(a1 - v1);
do
{
*v1 = byte_402FF8[(char)v1[(_DWORD)v4]];
++v2;
++v1;
result = strlen(a1);
}
while ( v2 < result );
}
return result;
}
为了分析v1到底是什么,需要去阅读汇编代码。回到主函数看v4,可以看到在调用加密函数前使用lea指令将v4地址传入了ecx
再进加密函数看汇编代码,可以看到v1指向的正是ecx,同时后面还有位赋值操作
接着就可以安心分析加密函数,首先byte_402FF8是一个字符数组,所以里面应该是数字型下标
v4 = (const char *)(a1 - v1);
这句代码中得到的是a1(输入的字符串地址)-v1(主函数v4的基地址),即地址差值
同时在循环中*v1 = byte_402FF8[(char)v1[(_DWORD)v4]];
表示v1的基地址+(地址差值),取输入字符串一个字符(ascii值),从数组里获取对应值赋给v1(很绕)。总结来说思路很简单就是一个替换加密,由于没法直接找到这个字符数组值或者右键导出,因此使用ida自带python获取地址范围内的值
addr=0x00402FF8
arr=[]
for i in range(0x00403078-0x00402FF8): #数组的个数
arr.append(idc.get_wide_byte(addr+i))
print(arr)
enflag = 'DDCTF{reverseME}'
flag = ''
for s in enflag:
flag += chr(arr[ord(s)])
print(flag) # ZZ[JX#,9(9,+9QY!