open-source
题目附件为一段C语言代码
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
if (argc != 4) {
printf("what?\n");
exit(1);
}
unsigned int first = atoi(argv[1]);
if (first != 0xcafe) {
printf("you are wrong, sorry.\n");
exit(2);
}
unsigned int second = atoi(argv[2]);
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 = first * 31337 + (second % 17) * 11 + strlen(argv[3]) - 1615810207;
printf("Get your key: ");
printf("%x\n", hash);
return 0;
}
对代码稍作分析,计算key的关键语句为:unsigned int hash = first * 31337 + (second % 17) * 11 + strlen(argv[3]) - 1615810207;
又根据之前的多个if语句可以判断:first = 0xcafe,second % 17 = 8, argv[3] = “h4cky0u”
知道了这些参数,就能够计算最后的hash值
根据分析对代码稍作修改,如下:
#include <stdio.h>
#include <string.h>
int main() {
long long int hash = 0xcafe * 31337 + (8) * 11 + 7 - 1615810207;
printf("Get your key: ");
printf("%x\n", hash);
return 0;
}
运行,输出结果即为flag
c0ffee
insanity
Shift+F12打开字符串窗口,找到flag
9447{This_is_a_flag}
getit
用IDA64打开
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;
}
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);
}
fclose(stream);
remove(filename);
return 0;
}
这段代码前半部分是生成flag的逻辑,后半部分只是将flag写进flag.txt而已。flag其实就是经过while循环处理的t字符串。
.data:00000000006010A0 public s
.data:00000000006010A0 ; char s[]
.data:00000000006010A0 s db 'c61b68366edeb7bdce3c6820314b7498',0
.data:00000000006010A0 ; DATA XREF: main+25↑o
.data:00000000006010A0 ; main+3F↑r
.data:00000000006010C1 align 20h
.data:00000000006010E0 public t
.data:00000000006010E0 ; char t
.data:00000000006010E0 t db 53h ; DATA XREF: main+65↑w
.data:00000000006010E0 ; main+C9↑o ...
.data:00000000006010E1 aHarifctf db 'harifCTF{????????????????????????????????}',0
.data:000000000060110C align 20h
.data:0000000000601120 public u
.data:0000000000601120 u db '*******************************************',0
.data:0000000000601120 ; DATA XREF: main+A5↑o
.data:0000000000601120 ; main+13F↑o
这里要注意的是,字符串t参数应为53h+'harifCTF{????????????????????????????????}'
,53h即大写字母S,因此flag格式应为:SharifCTF{????????????????????????????????}
按照C伪代码生成flag的逻辑写python脚本:
s = 'c61b68366edeb7bdce3c6820314b7498'
flag = ''
for i in range(len(s)):
if(i & 1):
v3 = 1
else:
v3 = -1
flag += chr(ord(s[i])+v3)
print(flag)
运行即可得到{}括号内的内容
完整flag:SharifCTF{b70c59275fcfa8aebf2d5911223c6589}
Hello, CTF
IDA打开,找到主函数F5反编译成C伪代码如下:
int __cdecl main(int argc, const char **argv, const char **envp)
{
signed int v3; // ebx
char v4; // al
int result; // eax
int v6; // [esp+0h] [ebp-70h]
int v7; // [esp+0h] [ebp-70h]
char v8; // [esp+12h] [ebp-5Eh]
char v9[20]; // [esp+14h] [ebp-5Ch]
char v10; // [esp+28h] [ebp-48h]
__int16 v11; // [esp+48h] [ebp-28h]
char v12; // [esp+4Ah] [ebp-26h]
char v13; // [esp+4Ch] [ebp-24h]
strcpy(&v13, "437261636b4d654a757374466f7246756e");
while ( 1 )
{
memset(&v10, 0, 32u); // v10字符串清空
v11 = 0;
v12 = 0;
sub_40134B((int)aPleaseInputYou, v6); // please input your serial:
scanf(aS, v9);
if ( strlen(v9) > 17 ) // 输入字串长度大于17 就退出
break;
v3 = 0;
do
{
v4 = v9[v3];
if ( !v4 )
break;
sprintf(&v8, asc_408044, v4); // sprintf(&v8,%x,v4); v8存放输入字符ascii码值的十六进制
strcat(&v10, &v8);
++v3;
}
while ( v3 < 17 ); //flag长度为17
if ( !strcmp(&v10, &v13) )
sub_40134B((int)aSuccess, v7); //处理后的flag即v13字串
else
sub_40134B((int)aWrong, v7);
}
sub_40134B((int)aWrong, v7);
result = stru_408090._cnt-- - 1;
if ( stru_408090._cnt < 0 )
return _filbuf(&stru_408090);
++stru_408090._ptr;
return result;
}
分析以上代码可知,flag长度为17,程序中给出的 v13字符串为输入的flag各个字符的ASCII码的十六进制数拼接而成的。
编写Python脚本:
S = '437261636b4d654a757374466f7246756e'
flag = ''
l = []
i = 0
while(i<34):
a = '0x'
a += S[i]
a += S[i+1]
l.append(int(a, 16))
i+=2
for i in range(len(l)):
flag += chr(l[i])
print(flag)
运行即得flag
CrackMeJustForFun
simple-unpack
ExeInfo打开查壳,为upx壳
使用upx加壳脱壳软件脱壳
upx -d 文件路径
用IDA64打开,shift+F12打开字符串窗口,在最底下找到flag
flag{Upx_1s_n0t_a_d3liv3r_c0mp4ny}
python-trade
文件为pyc文件,可通过uncompyle工具将.pyc文件反编译成.py文件。
在cmd输入pip intall uncompyle
即可安装
在文件下载路径打开命令行,输入反编译命令即可
uncompyle6 pyc文件名 > py文件名
本题反编译出的py文件如下:
# uncompyle6 version 3.7.4
# Python bytecode 2.7 (62211)
# Decompiled from: Python 3.5.1 (v3.5.1:37a07cee5969, Dec 6 2015, 01:54:25) [MSC v.1900 64 bit (AMD64)]
# Embedded file name: 1.py
# Compiled at: 2017-06-03 10:20:43
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'
# okay decompiling f417c0d03b0344eb9969ed0e1f772091.pyc
将correct字符串进行base64解码,解码后的各个字符编码值-16再与32异或即最终flag。
本来是想用python自带的base64解码的,虽然能解码,但是转成数值时会报错,最后还是选择了有各个字符值的在线解码:
a = [0X5E, 0X53, 0X64, 0X56, 0X6B, 0X54, 0X23, 0X53, 0X20, 0X5D, 0X60,
0X59, 0X5C, 0X21, 0X5E, 0X29, 0X8F, 0X80, 0X69, 0X73, 0X6D]
flag = ''
for i in range(len(a)):
b = a[i] - 16
b ^= 32
flag += chr(b)
print(flag)
运行即得flag
nctf{d3c0mpil1n9_PyC}
re1
IDA打开
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("欢迎来到DUTCTF呦\n");
printf("这是一道很可爱很简单的逆向题呦\n");
printf("输入flag吧:");
scanf("%s", &v9);
v3 = strcmp((const char *)&v5, &v9); // v3大于零程序继续
if ( v3 )
v3 = -(v3 < 0) | 1;
if ( v3 )
printf(aFlag_0); // flag错误
else
printf((const char *)&unk_413E90); // flag get√
system("pause");
return 0;
}
程序逻辑就是输入flag与内置flag字符串比对,找到字符串参数xmmword
如下图(程序内反向显示)
a = '}FTCTUD0tem0c1eW{FTCTUD'
flag = a[::-1]
print(flag)
写个小脚本把它反转:
DUTCTF{We1c0met0DUTCTF}
game
与bugku-reverse-游戏过关为同一道题,之前已写过wp,
点此进
zsctf{T9is_tOpic_1s_v5ry_int7resting_b6t_others_are_n0t}
logmein
IDA64打开,反编译
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
size_t v3; // rsi
int i; // [rsp+3Ch] [rbp-54h]
char s[36]; // [rsp+40h] [rbp-50h]
int v6; // [rsp+64h] [rbp-2Ch]
__int64 v7; // [rsp+68h] [rbp-28h]
char v8[8]; // [rsp+70h] [rbp-20h]
int v9; // [rsp+8Ch] [rbp-4h]
v9 = 0;
strcpy(v8, ":\"AL_RT^L*.?+6/46");
v7 = 'harambe';
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((__int64)"%32s", (__int64)s);
v3 = strlen(s);
if ( v3 < strlen(v8) ) // 输入的密码长度大于等于v8长度
sub_4007C0();
for ( i = 0; i < strlen(s); ++i )
{
if ( i >= strlen(v8) )
sub_4007C0(); // flag长度等于v8长度
if ( s[i] != (char)(*((_BYTE *)&v7 + i % v6) ^ v8[i]) )// flag算法逻辑
sub_4007C0();
}
sub_4007F0();
}
其中sub_4007C0()
返回密码错误提示,sub_4007F0()
返回密码正确。IDA一开始显示的v7是长串数字,需要转成字符串(可以根据最后if条件的BYTE看出)。IDA内右键可转换数值类型:
但是数值转字符串类型在IDA内结果是反向的,为‘ebmarah’,需要反转成’harambe’。
根据伪代码中的算法逻辑写脚本就行了,必要的v7, v6, v8 几个参数很直接的在代码中给出了。
a = 'harambe'
b = 7
c = ':\"AL_RT^L*.?+6/46'
flag = ''
for i in range(len(c)):
flag += chr((ord(a[i%b])) ^ ord(c[i]))
print(flag)
运行得到最后的flag:
RC3-2016-XORISGUD
no-strings-attached
IDA打开,反编译主函数main
int __cdecl main(int argc, const char **argv, const char **envp)
{
setlocale(6, &locale); //locale = 0
banner();
prompt_authentication();
authenticate();
return 0;
}
其中各个函数如下
int banner()
{
unsigned int v0; // eax
v0 = time(0);
srand(v0);
wprintf(&unk_80488B0); //Welcome to cyber malware control software.
rand();
return wprintf(&unk_8048960); //Currently tracking %d bots worldwide
/*-----------------------------------------------------*/
int prompt_authentication()
{
return wprintf(&unk_80489F8); //Please enter authentication details:
}
/*------------------------------------------------------*/
void authenticate()
{
wchar_t ws[8192];// [esp+1Ch] [ebp-800Ch]
wchar_t *s2;// [esp+801Ch] [ebp-Ch]
s2 = (wchar_t *)decrypt(&s, &dword_8048A90); //s2由decrypt函数加密而来
if ( fgetws(ws, 0x2000, stdin) ) //输入字符串ws
{
ws[wcslen(ws) - 1] = 0;
if ( !wcscmp(ws, s2) ) //字符串ws和s2比较 s2为flag
wprintf(&unk_8048B44);//Sucess! Welcome back!
else
wprintf(&unk_8048BA4);//Access dinied!
}
free(s2);
}
/*--------------------------------------------------*/
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 )
dest[v4++] -= a2[i];
}
return dest; //这里的返回值dest就是s2的值,即flag
}
有两种方法可以获得flag,一种是分析decrypt()函数计算flag,另一种是动态调试。
这里使用动态调试的方法。
查看decrypt函数的汇编代码
text:08048658 ; int __cdecl decrypt(wchar_t *s, wchar_t *)
.text:08048658 public decrypt
.text:08048658 decrypt proc near ; CODE XREF: authenticate+18↓p
.text:08048658
.text:08048658 var_1C = dword ptr -1Ch
.text:08048658 var_18 = dword ptr -18h
.text:08048658 var_14 = dword ptr -14h
.text:08048658 var_10 = dword ptr -10h
.text:08048658 dest = dword ptr -0Ch
.text:08048658 s = dword ptr 8
.text:08048658 arg_4 = dword ptr 0Ch
.text:08048658
.text:08048658 ; __unwind {
.text:08048658 push ebp
.text:08048659 mov ebp, esp
.text:0804865B push ebx
.text:0804865C sub esp, 34h
.text:0804865F mov eax, [ebp+s]
.text:08048662 mov [esp], eax ; s
.text:08048665 call _wcslen
.text:0804866A mov [ebp+var_14], eax
.text:0804866D mov eax, [ebp+arg_4]
.text:08048670 mov [esp], eax ; s
.text:08048673 call _wcslen
.text:08048678 mov [ebp+var_10], eax
.text:0804867B mov ebx, [ebp+s]
.text:0804867E mov eax, [ebp+s]
.text:08048681 mov [esp], eax ; s
.text:08048684 call _wcslen
.text:08048689 add eax, 1
.text:0804868C mov [esp], eax ; size
.text:0804868F call _malloc
.text:08048694 mov [ebp+dest], eax
.text:08048697 mov [esp+4], ebx ; src
.text:0804869B mov eax, [ebp+dest] #这里可以看到,dest值是传进了寄存器eax
.text:0804869E mov [esp], eax ; dest
.text:080486A1 call _wcscpy
.text:080486A6 mov [ebp+var_18], 0
.text:080486AD jmp short loc_80486F7
因为文件是ELF格式,所以需要使用到Linux系统内的gdb调试指令。
友情提示:要在root权限下使用gdb,如果是非root用户的话就有可能会像我一样在试图查看寄存器内容的时候疯狂报错“Cannot access memory at address XXXXX”(到现在还是没明白什么原因报的错,用的是ubuntu18.04虚拟机,差点搞到要重装系统= =)
root设置参考博客
以下为gdb调试指令:
(gdb) file 11 # file命令 读取文件(文件名为我随便重命名的11)
Reading symbols from 11...(no debugging symbols found)...done.
(gdb) b decrypt # b 在decrypt处设置断点
Breakpoint 1 at 0x804865c
(gdb) r # r 运行程序(run) 显示在断点处停止
Starting program: /home/terra/11
Welcome to cyber malware control software.
Currently tracking 1092326991 bots worldwide
Breakpoint 1, 0x0804865c in decrypt ()
(gdb) n # n 单步步入
Single stepping until exit from function decrypt,
which has no line number information.
0x08048725 in authenticate ()
(gdb) x/5sw $eax # x就是查看内存内容 5表示查看的单元个数 s是以字符串形式 w是按字单元(word) $eax表示查看eax寄存器内容
0x804cfd0: U"9447{you_are_an_international_mystery}"
0x804d06c: U""
0x804d070: U""
0x804d074: U""
0x804d078: U""
直接可以看到flag: 9447{you_are_an_international_mystery}
csaw2013reversing2
运行程序,会弹出一个标题为Flag的窗口,内容是乱码。
拖入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, (int)lpMem); //对lpMem操作 猜测为flag加密函数
ExitProcess(0xFFFFFFFF); //终止程序
}
MessageBoxA(0, lpMem + 1, "Flag", 2u); //弹窗函数 由此看出lpMem+1为flag内容
HeapFree(hHeap, 0, lpMem);
HeapDestroy(hHeap);
ExitProcess(0);
}
if条件内的第一个函数sub_40102A()
如下,它的返回值为0:
int sub_40102A()
{
char v0; // t1
v0 = *(_BYTE *)(*(_DWORD *)(__readfsdword(0x18u) + 48) + 2);
return 0;
}
而if条件内的第二个函数IsDebuggerPresent()
定义如下:
IsDebuggerPresent是确定调用进程是否由用户模式的调试器调试。
如果当前进程运行在调试器的上下文,返回值为非零值。
如果当前进程没有运行在调试器的上下文,返回值是零。
因此实际运行源程序时,很有可能为:if判断条件值为0(假),不执行if语句内的内容,跳过 if 内的函数直接弹窗而造成了乱码。
如果 if条件判断为真,程序虽然执行了加密函数sub_401000
,紧接着就会被ExitProcess
结束进程,不会执行之后的弹窗操作。
因此要得到最终flag有几个关键点:执行sub_401000
加密函数,以及 绕过ExitProcess
的中断。
将程序用OllyDBG打开,通过搜索“Flag”可以定位到弹窗位置:
可以看到,程序未执行IsDebuggerPresent
后 if条件的内容,而是直接跳转到了弹窗地址008510B9
。
将图中 je 跳转语句修改跳转到0085109B
位置(函数sub_401000
的前一个位置),这样绕过了中间的 int3 断点。
再将函数sub_401000
后的 jmp 跳转改为跳转至弹窗处008510B9
,就能绕过ExitProcess
的终止程序从而输出flag。
flag{reversing_is_not_that_hard!}"
maze
根据题目描述可知这是一道迷宫题。
用IDA64打开,F5将主函数main反编译成C伪代码:
__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) != '}' )
{ // flag长度为24 并且由nctf{}包裹
LABEL_22:
puts("Wrong flag!");
exit(-1);
}
v3 = 5LL;
if ( strlen(&s1) - 1 > 5 )
{
while ( 1 )
{
v4 = *(&s1 + v3); // 从flag花括号内的第一个字符开始比对
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)] != '#' ) //循环结束判断 是否到达迷宫终点'#'
goto LABEL_20;
v7 = "Congratulations!";
LABEL_21:
puts(v7);
return 0LL;
}
其中 SHIDWORD(v9)
即 &v9 + 1
( 常见IDA宏定义参考博客 )
迷宫字符串asc_601060[]
如下,长度为64:
******* * **** * **** * *** *# *** *** *** *********
根据以下代码能够判断出 &v9
为纵坐标(所在行), &v9+1
为横坐标(所在列),迷宫是8*8规格的。
if ( asc_601060[8 * (signed int)v9 + SHIDWORD(v9)] != '#' ) //判断是否到达迷宫终点'#'
其中逻辑为:
当前位置 = 8(迷宫横长) * 当前纵坐标(所在行) + 当前纵坐标(所在列)
其中横纵坐标由0开始算。
又根据伪代码中四个 if判断中的函数判断移动四个方向:
bool __fastcall sub_400650(_DWORD *a1) // a1为 &v9+1(横坐标)
{ // v4 = 'O'
int v1;
v1 = (*a1)--; // 横坐标-1 向左移动
return v1 > 0; // 是否超出边界
}
bool __fastcall sub_400660(int *a1) // a1为 &v9+1
{ // v4 = 'o'
int v1;
v1 = *a1 + 1; // 横坐标+1 向右移动
*a1 = v1;
return v1 < 8;
}
bool __fastcall sub_400670(_DWORD *a1) // a1为 v9(纵坐标)
{ // v4 = '.'
int v1;
v1 = (*a1)--; // 纵坐标-1 向上移动
return v1 > 0; // 超界判断
}
bool __fastcall sub_400680(int *a1) // a1为 v9
{
int v1; // v4 = '0'
v1 = *a1 + 1; // 纵坐标+1 向下移动
*a1 = v1;
return v1 < 8;
}
将迷宫字符串以8*8格式打印出来(起点在左上角,终点为#)
******
* * *
*** * **
** * **
* *# *
** *** *
** *
********
走出迷宫方式为:右下右右下下左下下下右右右右上上左左
对应flag字符串:o0oo00O000oooo…OO
字符串长度刚好也对应为程序开头的判断(18+6=24)
在Linux虚拟机内运行验证也正确。
nctf{o0oo00O000oooo..OO}
总算完成了 撒花✿✿ヽ(°▽°)ノ✿