IDA中F5
查看反汇编之后的C代码:
这里将输入的字符串放在变量v11中,然后与v7对比,v7和v8是连续的栈中变量,先后存放flag的一部分,找到给变量赋值的地方:
然后用python处理字符串输出:
载入ida分析,找到得出flag的地方,分析一下,程序预设两个一样长度(56)的字符串,通过异或的方法恢复出flag,关键部分如下:
依据这个,写出python脚本来得到flag:
#! usr/bin/python3
str0 = [0x12,0x40,0x62,0x5,0x2,4,6,3,6,0x30,0x31,0x41,0x20,0x0C,0x30,0x41,0x1F,0x4E,0x3E,
0x20,0x31,0x20,1,0x39,0x60,3,0x15,9,4,0x3E,3,5,4,1,2,3,0x2C, 0x41, 0x4E, 0x20,0x10,
0x61,0x36, 0x10, 0x2C,0x34, 0x20, 0x40, 0x59, 0x2D, 0x20, 0x41, 0x0F, 0x22, 0x12,
0x10]
str1 = [0x7b,0x20,0x12,0x62,0x77,0x6c,0x41,0x29,0x7c,0x50,0x7D,0x26,0x7c,0x6f,0x4a,0x31,0x53,
0x6c,0x5e,0x6c,0x54,6,0x60,0x53,0x2c,0x79,0x68,0x6e,0x20,0x5f,0x75,0x65,0x63,0x7b,
0x7f,0x77,0x60,0x30,0x6b,0x47,0x5c,0x1d,0x51,0x6b,0x5a,0x55,0x40,0x0c,0x2b,0x4c,
0x56,0x0d,0x72,1,0x75,0x7e]
for i in range(56):
str1[i] ^= str0[i]
str1[i] ^= 0x13
for i in str1:
print (chr(i),end='')
直接拖入IDA分析,经过相关分析并注释如下:
查看v13的具体内容:
又是16进制转ASCII码:
题目直接给了源码,不难,依次满足给定的命令行参数条件即可,不过第一个参数好像构造不出来,因为0xcafe已经超出ascii的表示范围了,所以就直接改源码,将最后计算hash的那句代码改了,构造其他3个就行了。
修改源代码:注释对第一个参数的判断,修改最后hash的计算
#include
#include
#include
int main (int argc, char *argv[]) {
if (argc != 4) {
printf ("what?\n");
exit (1);
}
/*unsigned int first = atoi (argv[1]);
printf ("%x\n", first);
if (first != 0xcafe) {
printf ("you are wrong, sorry.\n");
exit (2);
}*/
unsigned int second = atoi (argv[2]);
/*printf ("%d\n", first);*/
if (second % 5 == 3 || second % 17 != 8) {
printf ("ha, you won't get it!\n");
exit (3);
}
if (strcmp ("h4cky0u", argv[3])) {
printf ("so close, dude!\n");
exit (4);
}
printf ("Brr wrrr grr\n");
unsigned int hash = 0xcafe * 31337 + (second % 17) * 11 + strlen (argv[3]) - 1615810207;
printf ("Get your key: ");
printf ("%x\n", hash);
return 0;
}
ELF二进制程序,题目提示加壳,最近遇到的几个逆向的题也是加壳的,upx加壳和aspack加壳,在网上找的一些脱壳的软件大部分不能成功脱壳,应该是要学会自己手动脱壳吧,想起上学期老师说的脱壳技术,真的是头大啊,很菜,都没做出来,只能拿这个来攒攒信心了。
使用010 Editor二进制编辑器查看,文件类型:
有显示upx!
说明是被upx加壳了,之前说的遇到的被upx
加壳的软件这样打开来看的话会是upx0, upx1
,如果是Windows
的PE
程序,可以使用PEid
来看,这里的这个是ELF程序,是Unix
上的二进制可执行程序,以后应该会遇到更多的吧。
所以我们就是用upx
来脱壳,upx
可以到SourceForge
上去下,是一个GitHub上的项目,一直在不断的更新。下载解压之后,进入到exe
文件所在目录打开命令行,就可以脱壳了,upx
的具体命令用法可以查看下载的readme
文件,都说的很清楚。
如下图:
这里我是把upx.exe
的路径设为环境变量了,所以可以直接使用。
然后使用ida64位打开就可以直接看到flag了:
好了,这回这个没有加壳了,是一个64位的ELF
文件,拖入IDA
分析,F5
查看伪代码:
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
size_t v3; // rsi@1
int i; // [sp+3Ch] [bp-54h]@3
char s[36]; // [sp+40h] [bp-50h]@1
int v6; // [sp+64h] [bp-2Ch]@1
__int64 v7; // [sp+68h] [bp-28h]@1//var_28
char v8[8]; // [sp+70h] [bp-20h]@1
int v9; // [sp+8Ch] [bp-4h]@1
v9 = 0;
strcpy(v8, ":\"AL_RT^L*.?+6/46");
v7 = 28537194573619560LL;//注意这里
v6 = 7;
printf("Welcome to the RC3 secure password guesser.\n", a2, a3);
printf("To continue, you must enter the correct password.\n");
printf("Enter your guess: ");
__isoc99_scanf("%32s", s);
v3 = strlen(s);
if ( v3 < strlen(v8) )
wrong();
for ( i = 0; i < strlen(s); ++i )
{
if ( i >= strlen(v8) )
wrong();
if ( s[i] != (char)(*((_BYTE *)&v7 + i % v6) ^ v8[i]) )//关键点
wrong();
}
success();
}
提示输入flag
到s
变量中去,然后在用一个for
循环判断是否正确,比较简单,flag
就是v7
数组和v8
数组的字符异或,但是往上看v7
显示是一个长整型的值,跟踪过去进入汇编代码:
上图红色标记处把数据段的qword_40080
数据传入rax
,rax
再赋值给本地变量var_28
,而由上面的伪代码,我们知道这个就是v7
。我们再继续到数据段中去看:
使用R
键将数据转换成字符形式,得到:
这里我就跟着去写python
代码来恢复flag
了,但是恢复出来提交不对。
显然,结果也没有什么意义,虽然有些题的flag
可能没有什么意义。
后来(如果不去看提示的话估计我会卡很久),比对了v8
的赋值,才知道这是因为小端存放的原因,低字节的数据存放在高地址上面,如下面的两张图,即就是v8
的赋值和数据存放的方式:
于是v7
的值也需要把顺序倒过来,即:
这样flag
就出来了,发现cygwin
在Windows
下用着是真的方便啊,现在电脑上面可以使用的命令行就有power shell, cmd, git bash,cygwin
。
疯狂的;照例打开下载的文件,010Editor
查看是ELF
文件,所以使用IDA
打开,发现F5
不管用了,看了一下main
函数,也没有什么逻辑,陷入尴尬境地。查看Strings
窗口,发现一个类似flag
的flag
:
这一看就不像真正的flag
,况且还有其他字符串的信息。
结果真香,后来找思路的时候回到题目提示中去看,菜鸡觉得前面的太难了,来个简单的,结果就是把上面的flag
提交就可以了。套路,都是套路!
题目提示运行程序就能拿flag,elf程序,kali中运行,显示段错误:
使用ida打开,查看导出表,有一个decrypt,解密的,再使用gdb调试设置断点在这儿:
然后使用r
命令运行程序,就会在断点处停下,然后单步步过n
,使用info reg
查看寄存器的值,这时解密的结果应该在寄存器eax中了:
eflags
寄存器为0x282,'x/282 $eax’查看eax的内容(这里查看文档的,还不太懂,以后懂了回来再写):
红框中的数据即为flag
:
exe文件,使用ida打开,提示有调试信息,猜想后续可能和调试有关:
查看主函数的graph图:
在这之前已经试运行过这个程序,弹出了一个对话框,标题为flag,可是显示的是乱码:
仔细分析之后,程序会先使用IsDebuggerPresent
函数判断程序是否在调试器中运行,是的话调到解密函数,否则弹框输出乱码的flag,仔细来看是在调试器中运行后的这一部分:
有一个int 3
中断,然后[ebp+lpMem]
是存放乱码flag的地址,赋值给edx
作为后续处理,接着调用函数sub_401000
,一开始我不知道这是解密函数,进去看了之后才知道的,sub_401000
函数分析出来是将乱码的flag
4个字节为一组与0xAABBCCDD
异或得到可识别的flag,本来想写一个脚本来解出flag的,但是想试试调试的过程。
使用OD打开exe文件,因为程序本来就有中断,所以运行几次之后就找到了将要调用解密函数的位置:
如上图,地址000B109E
处就是调用解密函数的指令,在这里下一个断点,单步步过之后调到下一条指令,此时flag的地址是存放在edx中的,查看edx的值,为02D005B8
:
到内存中去找,就可以看到flag了:
注:学会了一个单词,maze-迷宫。
一个月前看着很难,现在看着就很简单了,函数的功能分析起来也容易得多。(2019.08.19)
下面是ida中分析的结果:
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
signed __int64 i; // rbx
signed int a_ch; // eax
bool flag; // bp
bool ret_value; // al
const char *prompt; // rdi
int *rows; // [rsp+0h] [rbp-28h]
rows = 0LL;
puts("Input flag:");
scanf("%s", &input, 0LL);
if ( strlen(&input) != 24 || strncmp(&input, "nctf{", 5uLL) || *(&byte_6010BF + 24) != '}' )
{
wrong:
puts("Wrong flag!");
exit(-1);
}
i = 5LL;
if ( strlen(&input) - 1 > 5 )
{
while ( 1 )
{
a_ch = *(&input + i); // a_ch = input[i]
flag = 0;
if ( a_ch > 'N' )
{
a_ch = (unsigned __int8)a_ch;
if ( (unsigned __int8)a_ch == 'O' )
{
ret_value = left((_DWORD *)&rows + 1);// 列数减一
goto set_flag;
}
if ( a_ch == 'o' )
{
ret_value = right((int *)&rows + 1); // 列数加一
goto set_flag;
}
}
else
{
a_ch = (unsigned __int8)a_ch;
if ( (unsigned __int8)a_ch == '.' )
{
ret_value = up(&rows); // 行数减1
goto set_flag;
}
if ( a_ch == '0' )
{
ret_value = down((int *)&rows); // 行数加1
set_flag:
flag = ret_value;
goto LABEL_15;
}
}
LABEL_15:
if ( !(unsigned __int8)is_in_maze((__int64)maze, SHIDWORD(rows), (int)rows) )
goto wrong;
if ( ++i >= strlen(&input) - 1 ) // 走完所有迷宫
{
if ( flag )
break;
LABEL_20:
prompt = "Wrong flag!";
goto LABEL_21;
}
}
}
if ( maze[8 * (signed int)rows + SHIDWORD(rows)] != '#' )// cols = SHIWORD(rows)
goto LABEL_20;
prompt = "Congratulations!";
LABEL_21:
puts(prompt);
return 0LL;
}
/* Orphan comments:
高四位存放的是当前列数
*/
目的是找到’#'字符所在的位置,将maze的数据提取出来,实际上就是一个char数组,按照8*8排列,如下:
一开始处于(0,0)处,程序中用一个int64数来表示当前的位置,高位int表示的是列数,低位int表示的是行数。这样就很容易分析其中具体函数的作用是改变行数还是列数了(如果你是真小白不懂的话找我问,我也是从完全不懂的小白过来的),分析出来输入的字符和对应的操作应该是:
left:O
right:o
up:.
down:0
再结合前面的一些判断的信息就可以得到flag了。