退役ACMer(其实ACM也才划水了一个多月)艰难入门CTF逆向工程,第一发学习小结。(啥也不会,只能对着IDA出的代码懵逼,天天坐牢)
-
Hello,CTF
-
insanity
-
python-trade
-
re1
-
game
-
open-source
-
simple-unpack
-
logmein
-
no-strings-attached
-
getit
-
csaw2013reversing2
-
maze
Hello,CTF
可以看出输入一个字符串v9,并判断长度小于等于17,再把v9的每个字符赋值给v4,最后通过sprintf函数将v4通过%x(16进制)输出为Buffer字符串,再通过strcat函数将Buffer字符串给v10,最后将v10与v13进行比较。
程序的意思是字符串转为16进制,那么我们只需要逆向把16进制转为字符串就可以了。
flag:
insanity
shift+F12快速打开string窗口找到flag
python-trade
发现附件是一个pyc文件
于是通过在线反编译网站得到python代码
import base64
def encode(message):
s = ""
for i in message:
x = ord(i) ^ 32
x = x + 16
s += chr(x)
return base64.b64encode(s)
correct = "XlNkVmtUI1MgXWBZXCFeKY+AaXNt"
flag = ""
print "Input flag:"
flag = raw_input()
if encode(flag) == correct:
print "correct"
else:
print "wrong"
发现是将输入的flag通过encode函数变成和correct相同的字符串。那么对encode函数逆向decode即可。
分析encode函数,发现ord()函数就是返回一个字符串的ASCII码,chr()函数就是返回一个ASCII码对应的字符串,最后通过base64encode加密。那么decode脚本就好写了。
先把correct字符串base64decode
#include<iostream>
#include<cstring>
#include<string>
using namespace std;
char correct[100]="^SdVkT#S ]`Y!^)ism";
char flag[40];
int main()
{
for(int i=0;i<strlen(correct);i++)
{
char x=(correct[i]-16)^32;
cout<<x;
}
}
不会python,就用C++写了,得到flag。
re1
IDA之后F5查看伪代码,发现
可疑,进入xmmword后,按A键直接转化为字符串得到flag
game
IDA后,ctrl+F寻找main函数,得到源代码
发现了通过游戏的判断条件,双击进入sub函数,发现是对两个数组进行了两次异或运算
由于地址是连续的,所以v3,v4,以及strcpy的内容其实都属于v2,于是逆向写脚本得到flag。
改成
,并将前面的代码全部删去确保能跑到这一步即可。
simple-unpack
拖入Exeinfo查壳发现有壳,需要用upx脱壳。
将upx.exe和目标文件放入同一目录,打开cmd,输入指令脱壳。
可以发现脱壳以后文件变大了,再次查壳,发现没了。
拖入IDA64,反编译后进入flag,直接得到flag
logmein
先查壳,发现没壳,拖入IDA,反编译找到main函数(有注释):
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
size_t v3; // rsi
int i; // [rsp+3Ch] [rbp-54h]
char s[36]; // [rsp+40h] [rbp-50h] BYREF
int v6; // [rsp+64h] [rbp-2Ch]
__int64 v7; // [rsp+68h] [rbp-28h]
char v8[28]; // [rsp+70h] [rbp-20h] BYREF
int v9; // [rsp+8Ch] [rbp-4h]
v9 = 0;
strcpy(v8, ":"AL_RT^L*.?+6/46");//给v8赋值
v7 = 0x65626D61726168LL;//16进制数
v6 = 7;
printf("Welcome to the RC3 secure password guesser.
");
printf("To continue, you must enter the correct password.
");
printf("Enter your guess: ");
__isoc99_scanf("%32s", s);//输入s
v3 = strlen(s);
if ( v3 < strlen(v8) )//长度不小于v8
sub_4007C0();
for ( i = 0; i < strlen(s); ++i )
{
if ( i >= strlen(v8) )//长度不大于v8
sub_4007C0();
if ( s[i] != (char)(*((_BYTE *)&v7 + i % v6) ^ v8[i]) )//据此直接输出flag
sub_4007C0();
}
sub_4007F0();
}
于是开始写脚本。
#include<bits/stdc++.h>
#define _BYTE unsigned char
using namespace std;
char a[20]=":"AL_RT^L*.?+6/46";
long long b=0x65626D61726168LL;
int c=7;
char flag[40];
int main()
{
for(int i=0;i<strlen(a);i++)
{
flag[i]=(char)(*((_BYTE *)&b + i % c) ^ a[i]);
}
cout<<flag;
}
注:_BYTE为1个字节范围是0-255和unsigned char相同。
得到flag
拖入IDA发现 比较可疑
查看源代码
发现flag就是通过的decrypt得到的s2,由于之前查出来的
所以可以用ubuntu上的GDB跑一下。
先查main的汇编找到目标函数再查目标函数的汇编(gdb是真难用)
在decrypt处设置断点并执行。
由于
说明返回值在eax寄存器中,于是单步调试调用decrpt之后查询eax即可。
getit
拖入IDA得到代码,查看字符串内容:
用快捷键A把完整的t字符串显示出来,发现前缀,猜测应该是通过
将?填充进去获得完整flag(t[i+10]正好是从第一个问号开始的下标)
于是开始写脚本。
#include<bits/stdc++.h>
using namespace std;
char s[50]="c61b68366edeb7bdce3c6820314b7498";
char t[50]="SharifCTF{????????????????????????????????}";
int main()
{
for(int i=0;i<strlen(s);i++)
{
int x;
if(i&1)x=1;
else x=-1;
t[i+10]=s[i]+x;
}
cout<<t;
}
得到flag:
csaw2013reversing2
先运行,弹出一个窗口,标题为flag,但是是乱码,猜测最后的flag应该也是通过这种形式弹出。扔进Exeinfo PE查壳,没壳,直接扔进IDA反编译出代码:
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);
HeapFree(hHeap, 0, lpMem);
HeapDestroy(hHeap);
ExitProcess(0);
}
__debugbreak();
sub_401000(v3 + 4, lpMem);
ExitProcess(0xFFFFFFFF);
}
注意到有IsDebuggerPresent()函数用来反调试,MessageBoxA是用来弹出一个窗口,IpMem+1即为弹出的内容。
然后是进入函数sub_401000(),发现很像一个解密函数,那么猜测应该是要执行这个解密函数之后才能改变乱码得到flag。
考虑动态调试,拖入Ollydbg,先找到IsDebuggerPresent:
先把它给nop了,然后运行,发现还是弹出了乱码窗口,继续读汇编,发现通过je命令直接跳到了第二个MessageBoxA处,弹出乱码。
又看到这一行:
int3是一个断点指令,通过IDA的反编译可以查出是对应__debugbreak()函数,那么下面call的函数很可能是该解密函数,考虑修改je指令跳到该函数前一步:
又发现后面的jmp指令把两个MessageBoxA都跳过了
先尝试第一个MessageBoxA,把下面的jmp语句直接nop掉,然后运行:
弹出空白flag。
观察两个MessageBoxA的区别:
考虑到反编译代码中输出的pMem是从1开始的,输出空白flag很可能是因为有占位符' ',而下面的MessageBoxA猜测是通过一个寄存器的操作跳过了第一个占位符,那么考虑jmp到下面的MessageBoxA处,修改后的汇编如下:
运行得到flag:
maze
看题目就知道要找地图,直接扔进IDApro反编译:
__int64 __fastcall main(int a1, char **a2, char **a3)
{
__int64 v3; // rbx
int v4; // eax
char v5; // bp
char v6; // al
const char *v7; // rdi
unsigned int v9; // [rsp+0h] [rbp-28h] BYREF
int v10[9]; // [rsp+4h] [rbp-24h] BYREF
v10[0] = 0;
v9 = 0;
puts("Input flag:");
scanf("%s", &s1);
if ( strlen(&s1) != 24 || strncmp(&s1, "nctf{", 5uLL) || *(&byte_6010BF + 24) != 125 )
{
LABEL_22:
puts("Wrong flag!");
exit(-1);
}
v3 = 5LL;
if ( strlen(&s1) - 1 > 5 )
{
while ( 1 )
{
v4 = *(&s1 + v3);
v5 = 0;
if ( v4 > 78 )
{
if ( (unsigned __int8)v4 == 79 )
{
v6 = sub_400650(v10);
goto LABEL_14;
}
if ( (unsigned __int8)v4 == 111 )
{
v6 = sub_400660(v10);
goto LABEL_14;
}
}
else
{
if ( (unsigned __int8)v4 == 46 )
{
v6 = sub_400670(&v9);
goto LABEL_14;
}
if ( (unsigned __int8)v4 == 48 )
{
v6 = sub_400680(&v9);
LABEL_14:
v5 = v6;
}
}
if ( !(unsigned __int8)sub_400690(asc_601060, (unsigned int)v10[0], v9) )
goto LABEL_22;
if ( ++v3 >= strlen(&s1) - 1 )
{
if ( v5 )
break;
LABEL_20:
v7 = "Wrong flag!";
goto LABEL_21;
}
}
}
if ( asc_601060[8 * v9 + v10[0]] != 35 )
goto LABEL_20;
v7 = "Congratulations!";
LABEL_21:
puts(v7);
return 0LL;
}
先找到跑图代码:
对照ASCII码发现:
O:向左
o:向右
0:向下
.:向上
然后找到到达终点的代码 盲猜是一个8行的图,且重点为ASCII为35的字符(#)
进入asc_601060数组,用16进制窗口打开,应该就是地图: 发现地图是8*8的。
手动画图得到地图:
..******
*...*..*
***.*.**
**..*.**
*..*#..*
**.***.*
**.....*
********
跑图得到flag:nctf{o0oo00O000oooo..OO}
至此,攻防世界新手区完结撒花qwq❀