adworld-re新手练习区
RE
re1
下载得到exe文件,运行:
使用exeinfo分析,发现是32位无壳的exe,拖入IDA,f5反编译:
int __cdecl main(int argc, const char **argv, const char **envp) { int v3; // eax __int128 v5; // [esp+0h] [ebp-44h] __int64 v6; // [esp+10h] [ebp-34h] int v7; // [esp+18h] [ebp-2Ch] __int16 v8; // [esp+1Ch] [ebp-28h] char v9; // [esp+20h] [ebp-24h] _mm_storeu_si128((__m128i *)&v5, _mm_loadu_si128((const __m128i *)&xmmword_413E34)); v7 = 0; v6 = qword_413E44; v8 = 0; printf(&byte_413E4C); printf(&byte_413E60); printf(&byte_413E80); scanf("%s", &v9); v3 = strcmp((const char *)&v5, &v9); if ( v3 ) v3 = -(v3 < 0) | 1; if ( v3 ) printf(aFlag); else printf((const char *)&unk_413E90); system("pause"); return 0; }
追踪查看了unk_413E90中存放的内容是:flagget 所以v3运行结果应该是0,也即v5和v9中存放的东西相同;所以去找v5中的内容,发现:
_mm_storeu_si128((__m128i *)&v5, _mm_loadu_si128((const __m128i *)&xmmword_413E34));
猜测意思是将xmmword_413E34的内容赋值给v5,所以继续看xmmword_413E34的内容,得到flag:DUTCTF{We1c0met0DUTCTF}
注:
void _mm_storeu_si128(_m128i *p,_m128i a)
SSE指令,将_m128i变量a的值存储到p所指定的变量中
_m128i _mm_loadu_si128(_m128i *p)
SSE指令,返回寄存器中的值
game
打开题目,玩一会2333,好吧,玩是玩不出flag的,放入exeinfo,发现32位,拖入IDA,找到main主函数,f5反编译,发现全部变成1后,会执行sub_457AB4( )函数:
跟进它:
来到sub_45E940( )函数,发现关键部分:
写脚本跑一下:
list1=[] list2=[] file1 = open("1.txt",'r') file2 = open("2.txt",'r') file3 = open("3.txt",'w') for line in file1: line=line.strip('\n') list11=line.split('=') list1.append(list11[1]) for line in file2: line=line.strip('\n') list12=line.split('=') list2.append(list12[1]) list3=[] for i in range(57): list3.append(int(list1[i])^int(list2[i])) file3.write(str(list3[i])) file3.write('\n') file1.close() file2.close() file3.close() list4=[] file4=open("3.txt",'r') for line in file4: line=line.strip('\n') list4.append(line) for i in range(57): list4[i]=chr(int(list4[i])^0x13) print ''.join(list4)
注:如果IDA不能正确的获得自定义函数的名字,那么IDA会用sub__加上自定义函数的起始地址来定义函数的名字
Hello,CTF
打开题目,IDA->f5,发现关键代码:
要求输入的字符串与v13相同且长度不超过17,题目提示不一定是明文比较,所以将v13十六进制转换成字符串:CrackMeJustForFun,也即flag
注:
int sprintf(char *string, char *format [,argument,...]);
说明:把格式化(format与其后数量不定的参数)的数据写入某个字符串缓冲区(string),有缓冲区溢出的可能,不安全
open-source
下载得到一个C源码,
将上面的参数值代入计算,得到flag.
Simple-unpack
exeinfo分析下载的文件:
Linux upx -d 脱壳,载入IDA发现flag.
logmein
exeinfo显示下载文件为64位ELF文件,拖入IDA,f5反编译:
要求flag长度应与v8相同,且每一位都是由v7和v8的对应字符异或得到,所以编写EXP:
v7="harambe" #重点! str=":\"AL_RT^L*.?+6/46" flag='' for i in range(len(str)): flag += chr(ord(str[i])^ord(v7[i%7])) print flag
或者C++脚本:
#include <iostream> #include <cstring> typedef unsigned char BYTE; using namespace std; /* run this program using the console pauser or add your own getch, system("pause") or input loop */ int main(int argc, char** argv) { long long int v7=28537194573619560; char v8[18] =":\"AL_RT^L*.?+6/46"; char s[18];//定义字符数组变量,char a[字符串长度+1]; int i; for(i=0;i<strlen(v8);i++) { s[i]=(char)(*((BYTE *)&v7+i%7)^v8[i]); } s[i]='\0'; cout<<s; return 0; }
跑出flag.
注:
1.在计算机中数据全部以二进制形式储存,所以v7是int型还是字符串型本质上是相同的,但是在该程序中,计算机以小端字节序储存数据,所以v7变成字符串时应该从后向前读取。
2.v8是":\"AL_RT^L*.?+6/46",该字符串长度应为17,其中包含转移字符\"
3.IDA宏定义:
typedef unsigned char uint8; #define _BYTE uint8 //所以说_BYTE代表了四个字节
insanity
ida->f5查看伪码:
大概意思应该是不断地随机(以程序运行时间为种子,取随机数)取strs这个字符串中的字符,跟进strs,得到flag:9447{This_is_a_flag}
no-strings-attached
拿到下载文件放进exeinfo分析一下:
为32位ELF文件,拖进IDA分析,找啊找,找到decrypt函数!
wchar_t *__cdecl decrypt(wchar_t *s, wchar_t *a2) { size_t v2; // eax signed int v4; // [esp+1Ch] [ebp-1Ch] signed int i; // [esp+20h] [ebp-18h] signed int v6; // [esp+24h] [ebp-14h] signed int v7; // [esp+28h] [ebp-10h] wchar_t *dest; // [esp+2Ch] [ebp-Ch] v6 = wcslen(s); v7 = wcslen(a2); v2 = wcslen(s); dest = (wchar_t *)malloc(v2 + 1); wcscpy(dest, s); while ( v4 < v6 ) { for ( i = 0; i < v7 && v4 < v6; ++i ) // v6是s的长度 v7是a2的长度 dest是s dest[v4++] -= a2[i]; } return dest; }
意思是s的每一位循环减去a2的每一位,最后存入dest。
找到调用位置----
void authenticate() { int ws[8192]; // [esp+1Ch] [ebp-800Ch] wchar_t *s2; // [esp+801Ch] [ebp-Ch] s2 = decrypt(&s, &dword_8048A90); if ( fgetws(ws, 0x2000, stdin) ) { ws[wcslen(ws) - 1] = 0; if ( !wcscmp(ws, s2) ) wprintf((int)&unk_8048B44); else wprintf((int)&unk_8048BA4); } free(s2); }
可以看出flag应该就是加密后的密文,下面有两种方法解决——
方法一:静态分析,编写exp
#include <iostream> using namespace std; /* run this program using the console pauser or add your own getch, system("pause") or input loop */ int main(int argc, char** argv) { int i=1,j=0; int a[]={0x3A,0x36,0x37,0x3B,0x80,0x7A,0x71,0x78,0x63,0x66,0x73,0x67,0x62,0x65,0x73,0x60,0x6B,0x71,0x78,0x6A,0x73,0x70,0x64,0x78,0x6E,0x70,0x70,0x64,0x70,0x64,0x6E,0x7B,0x76,0x78,0x6A,0x73,0x7B,0x80,0}; char t; while(a[j]!=0) { t=a[j++]-(i++); cout<<t; if(i==6) { i=1; } } return 0; }
跑出flag: 9447{you_are_an_international_mystery}
方法二:linux下gdb动态分析(来自平台wp)
gdb 文件路径
先在dectypt函数处下断点---- b decrypt
然后运行程序至断点处---- r
单步执行---- n
在IDA中可以看到
加密后的结果存入了寄存器eax中,
所以最后查看运行结果---- x/50wx $eax【注:wx为以word即两个字节32bit形式查看】
16进制转ASCII同样得到flag。
csaw2013reversing2
exeinfo发现32bit 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, MaxCount + 1); memcpy_s(lpMem, MaxCount, &unk_409B10, MaxCount); if ( sub_40102A() || IsDebuggerPresent() ) { __debugbreak(); sub_401000(v3 + 4, lpMem); ExitProcess(0xFFFFFFFF); } MessageBoxA(0, lpMem + 1, "Flag", 2u); HeapFree(hHeap, 0, lpMem); HeapDestroy(hHeap); ExitProcess(0); }
关键是if( )函数,其中sub_401000应该为得到flag的函数,
unsigned int __fastcall sub_401000(int a1, int a2) { int v2; // esi unsigned int v3; // eax unsigned int v4; // ecx unsigned int result; // eax v2 = dword_409B38; v3 = a2 + 1 + strlen((const char *)(a2 + 1)) + 1; v4 = 0; result = ((v3 - (a2 + 2)) >> 2) + 1; if ( result ) { do *(_DWORD *)(a2 + 4 * v4++) ^= v2; while ( v4 < result ); } return result; }
但是跟进sub_40102A发现永远return 0;而另一个条件则在调试状态下成立,因此暂时有三种思路可以解决这个问题:
(一)IDA静态分析
分析sub_401000( )函数,自己编写函数实现的脚本跑出flag:
#include <iostream> using namespace std; typedef unsigned char BYTE; /* run this program using the console pauser or add your own getch, system("pause") or input loop */ int main(int argc, char** argv) { int v2[] ={0xBB,0xAA,0xCC,0xDD}; char result[]={0xBB,0xCC,0xA0,0xBC,0xDC,0xD1,0xBE,0xB8,0xCD,0xCF,0xBE,0xAE,0xD2,0xC4,0xAB,0x82,0xD2,0xD9,0x93,0xB3,0xD4,0xDE,0x93,0xA9,0xD3,0xCB,0xB8,0x82,0xD3,0xCB,0xBE,0xB9,0x9A,0xD7,0xCC,0xDD}; int i; for(i=0;i<0x24;i++) { result[i]^=v2[i%4]; cout<<result[i]; } return 0; }
注:
1.在main()中可以看到由memcpy_s( )函数对lpmem空间初始化,也即result[]初值
2.分析memcpy_s( )函数可以看到每个单元为8bit,也即两个十六进制位,所以实现异或时,v2也应8bit为一个单元进行异或,即v2[]初值
(二)OllyDBG动态调试
在调试状态下,执行if( )函数:
首先int 3为断点指令——
所以调试到int 3停止,在OD中f8单步执行,
随后edx寄存器中存储函数执行结果的地址,查看得到flag:
(三)IDA改变函数执行逻辑
修改汇编语言指令,再次运行,得到flag:
getit
exeinfo分析知为64bit ELF文件,IDA反汇编分析:
int __cdecl main(int argc, const char **argv, const char **envp) { char v3; // al __int64 v5; // [rsp+0h] [rbp-40h] int i; // [rsp+4h] [rbp-3Ch] FILE *stream; // [rsp+8h] [rbp-38h] char filename[8]; // [rsp+10h] [rbp-30h] unsigned __int64 v9; // [rsp+28h] [rbp-18h] v9 = __readfsqword(0x28u); LODWORD(v5) = 0; while ( (signed int)v5 < strlen(s) ) { if ( v5 & 1 ) v3 = 1; else v3 = -1; *(&t + (signed int)v5 + 10) = s[(signed int)v5] + v3; LODWORD(v5) = v5 + 1; } // flag实现过程 strcpy(filename, "/tmp/flag.txt"); stream = fopen(filename, "w"); fprintf(stream, "%s\n", u, v5); for ( i = 0; i < strlen(&t); ++i ) { fseek(stream, p[i], 0); fputc(*(&t + p[i]), stream); fseek(stream, 0LL, 0); fprintf(stream, "%s\n", u); } // p[]中为0x01-0x47,即将刚刚得到的flag写入文件 fclose(stream); remove(filename); return 0; }
大致是运行程序,会在当前目录下创建文件并写入内容,最后将文件删除的过程,所以写脚本实现flag输出:
#include <iostream> #include <string> using namespace std; /* run this program using the console pauser or add your own getch, system("pause") or input loop */ int main(int argc, char** argv) { string t="SharifCTF{"; string s="c61b68366edeb7bdce3c6820314b7498"; int v5=0,v3,i; for(v5=0;v5<s.length();v5++) { if(v5&1) { v3=1; } else { v3=-1; } t+=(s[v5]+v3); } t+="}"; cout<<t; return 0; }
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:
import base64 def decode(message): s = base64.b64decode(message) result='' for i in s: i=ord(i)-16 i=i^32 result+=chr(i) return result correct = 'XlNkVmtUI1MgXWBZXCFeKY+AaXNt' flag = decode(correct) print flag
maze
迷宫问题:
IDA反汇编得到的伪码如下:
__int64 __fastcall main(__int64 a1, char **a2, char **a3) { signed __int64 v3; // rbx signed int v4; // eax bool v5; // bp bool v6; // al const char *v7; // rdi __int64 v9; // [rsp+0h] [rbp-28h] v9 = 0LL; puts("Input flag:"); scanf("%s", &s1, 0LL); 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 > 'N' ) { v4 = (unsigned __int8)v4; if ( (unsigned __int8)v4 == 'O' ) // 向左走 { v6 = sub_400650((_DWORD *)&v9 + 1); goto LABEL_14; } if ( v4 == 'o' ) // 向右走 { v6 = sub_400660((int *)&v9 + 1); goto LABEL_14; } } else { v4 = (unsigned __int8)v4; if ( (unsigned __int8)v4 == '.' ) // 向上走 { v6 = sub_400670(&v9); goto LABEL_14; } if ( v4 == '0' ) // 向下走 { v6 = sub_400680((int *)&v9); LABEL_14: v5 = v6; goto LABEL_15; } } LABEL_15: if ( !(unsigned __int8)sub_400690((__int64)asc_601060, SHIDWORD(v9), v9) ) goto LABEL_22; if ( ++v3 >= strlen(&s1) - 1 ) { if ( v5 ) break; LABEL_20: v7 = "Wrong flag!"; goto LABEL_21; } } } if ( asc_601060[8 * (signed int)v9 + SHIDWORD(v9)] != 35 ) goto LABEL_20; v7 = "Congratulations!"; LABEL_21: puts(v7); return 0LL; }
其中的LABEL只是吓人的,其实代码本身逻辑还是很清楚的,其中限制我一开始没看懂的关键有几点:
(1)
v6 = sub_400650((_DWORD *)&v9 + 1);
typedef unsigned long DWORD;
DWORD是指两个word、四个字节、32bit。因此,这里的函数参数是变量v9的后一个32bit存储单元的指针。
(2)
(unsigned __int8)sub_400690((__int64)asc_601060, SHIDWORD(v9), v9)
其中,
#define SHIDWORD(x) (*((int32*)&(x)+1))
也就是指:变量v9的下一个指向32bit内存单元的指针所指的内容,也就是上面那个函数的参数
(3)
__int64 __fastcall sub_400690(__int64 a1, int a2, int a3) { __int64 result; // rax result = *(unsigned __int8 *)(a1 + a2 + 8LL * a3); LOBYTE(result) = (_DWORD)result == 32 || (_DWORD)result == 35; return result; }
最关键的是result那一行,分析后可以转变成:a2为列数、a3为行数,也就是第二个参数是列数,于是可以倒推出上面四个函数中符号代表的方向。
=>于是,条件是:
flag为24位字符串,格式为nctf{xxxxxxx}
'O','o','.','0'分别代表向左、向右、向上、向下
迷宫为:
00******
*000*00*
***0*0**
**00*0**
*00*#00*
**0***0*
**00000*
********
因此,手动得到flag:nctf{o0oo00O000oooo..OO}