Reverse1
查看文件,發現是upx殼,我們用命令直接脫殼
thunder@thunder-PC:~/Desktop/CTF/reverse/DDCTF2019$ file reverse1_final.exe
reverse1_final.exe: PE32 executable (console) Intel 80386, for MS Windows, UPX compressed
thunder@thunder-PC:~/Desktop/CTF/reverse/DDCTF2019$ upx -d reverse1_final.exe
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2017
UPX 3.94 Markus Oberhumer, Laszlo Molnar & John Reiser May 12th 2017
File size Ratio Format Name
-------------------- ------ ----------- -----------
7680 <- 5632 73.33% win32/pe reverse1_final.exe
Unpacked 1 file.
我們脫完殼發現不能直接運行程序,我們可以使用PE Tools來關閉程序的ASLR即可運行
我們修改可選頭中的DLL特徵碼
把DLL可以移動勾去掉即可運行程序
然後我們直接用IDA打開,找到關鍵函數
我們的輸入經過check函數變化之後和DDCTF{reverseME}相同則成功,可以查看check函數
這裏可以看到我們的輸入和byte_402FF8內存的數據進行置換,我們用OD動態查看這塊數據
我們將它dump出來,找到映射關係即可寫出exp
#include<stdio.h>
#include<string.h>
int main()
{
char key[] = "DDCTF{reverseME}";
for(int i=0;i<strlen(key);i++)
{
key[i] = 158 - key[i];
printf("%c",key[i]);
}
return 0;
}
測試結果
C:\Users\Thunder_J>D:\DDCTF\reverse1\reverse1_upacked.exe
please input code:ZZ[JX#,9(9,+9QY!
You've got it!!DDCTF{reverseME}
Reverse2
查看程序發現有殼
我們用WASPACK進行脫殼,拖入IDA分析,可以看到有兩處check
第一處check判斷我們的輸入是否爲16進制的數據,並且輸入爲偶數
第二處check將我們的輸入判斷是字母還是數字,然後依次將字符轉換爲數值,存入v10後進入下一個函數
返回函數主要進行三次移位操作,然後根據索引表取出數據,然後異或0x76,索引表異或0x76的值和base64的索引表相同,三次位移操作又可以聯想到base64加密,所以這裏就是一個base64加密,具體也可以用OD動態調試可以看出
最後的flag也就是reverse+進行base64解密,但是最後加密完的字符需要全爲大寫,腳本如下:
import base64
import binascii
s = "reverse+"
a = base64.b64decode(s)
print binascii.b2a_hex(a).upper()
測試結果
C:\Users\Thunder_J >D:\DDCTF\reverse2\reverse2_final.exe
input code:ADEBDEAEC7BE
You've got it !!! DDCTF{reverse+}
Confused
首先程序是mac下的,但是不要怕,我們直接逆xia0Crackme就行了,進去之後一堆看不懂的API,但是可以看到有一個checkcode的函數,我們進去看關鍵判斷函數
函數內容如下
__int64 __fastcall sub_1000011D0(__int64 a1)
{
char v2; // [rsp+20h] [rbp-C0h]
__int64 v3; // [rsp+D8h] [rbp-8h]
v3 = a1;
memset(&v2, 0, 0xB8uLL);
sub_100001F60((__int64)&v2, a1);
return (unsigned int)sub_100001F00(&v2);
}
跟進sub_100001F60函數發現一堆賦值
__int64 __fastcall sub_100001F60(__int64 a1, __int64 a2)
{
*(_DWORD *)a1 = 0;
*(_DWORD *)(a1 + 4) = 0;
*(_DWORD *)(a1 + 8) = 0;
*(_DWORD *)(a1 + 12) = 0;
*(_DWORD *)(a1 + 16) = 0;
*(_DWORD *)(a1 + 176) = 0;
*(_BYTE *)(a1 + 32) = -16;
*(_QWORD *)(a1 + 40) = sub_100001D70;
*(_BYTE *)(a1 + 48) = -15;
*(_QWORD *)(a1 + 56) = sub_100001A60;
*(_BYTE *)(a1 + 64) = -14;
*(_QWORD *)(a1 + 72) = sub_100001AA0;
*(_BYTE *)(a1 + 80) = -12;
*(_QWORD *)(a1 + 88) = sub_100001CB0;
*(_BYTE *)(a1 + 96) = -11;
*(_QWORD *)(a1 + 104) = sub_100001CF0;
*(_BYTE *)(a1 + 112) = -13;
*(_QWORD *)(a1 + 120) = sub_100001B70;
*(_BYTE *)(a1 + 128) = -10;
*(_QWORD *)(a1 + 136) = sub_100001B10;
*(_BYTE *)(a1 + 144) = -9;
*(_QWORD *)(a1 + 152) = sub_100001D30;
*(_BYTE *)(a1 + 160) = -8;
*(_QWORD *)(a1 + 168) = sub_100001C60;
qword_100003F58 = malloc(0x400uLL);
return __memcpy_chk((char *)qword_100003F58 + 48, a2, 18LL, -1LL);
}
繼續查看sub_100001F00函數,發現需要根據條件循環,這裏可以聯想到虛擬指令,對於虛擬指令這篇文章寫的比較好:
https://www.52pojie.cn/forum.php?mod=viewthread&tid=860237&page=1
__int64 __fastcall sub_100001F00(__int64 a1)
{
*(_QWORD *)(a1 + 24) = (char *)&loc_100001980 + 4;
while ( **(unsigned __int8 **)(a1 + 24) != 243 )
sub_100001E50(a1);
free(qword_100003F58);
return *(unsigned int *)(a1 + 176);
}
這裏借用40K0師傅的IDA分析文件來分析,虛擬指令的結構體如下
00000000 vm_struc struc ; (sizeof=0xB4, mappedto_59)
00000000 eax_ dd ?
00000004 ebx_ dd ?
00000008 ecx_ dd ?
0000000C edx_ dd ?
00000010 flag dd ?
00000014 field_14 dd ?
00000018 pc dq ?
00000020 code_F0 dq ?
00000028 mov_reg_imm dq ?
00000030 code_F1 dq ?
00000038 xor_eax_ebx dq ?
00000040 code_F2 dq ?
00000048 cmp_eax_imm dq ?
00000050 code_F4 dq ?
00000058 add_eax_ebx dq ?
00000060 code_F5 dq ?
00000068 sub_eax_ebx dq ?
00000070 code_F3 dq ?
00000078 nop dq ?
00000080 code_F6 dq ?
00000088 jz_imm dq ?
00000090 code_F7 dq ?
00000098 mov_buf_imm dq ?
000000A0 code_F8 dq ?
000000A8 enc_eax_2 dq ?
000000B0 buffer dd ?
000000B4 vm_struc ends
我們定到sub_100001F60函數,也就是賦值的函數,可以看到這裏是初始化的地方
__int64 __fastcall init_vm(vm_struc *a1, char *input)
{
a1->eax_ = 0;
a1->ebx_ = 0;
a1->ecx_ = 0;
a1->edx_ = 0;
a1->flag = 0;
a1->buffer = 0;
LOBYTE(a1->code_F0) = -16; // eip指向opcode的位置
a1->mov_reg_imm = mov_reg_imm; // mov 立即數到寄存器,操作字節碼與函數關聯在一起
LOBYTE(a1->code_F1) = -15;
a1->xor_eax_ebx = xor_eax_ebx;
LOBYTE(a1->code_F2) = -14;
a1->cmp_eax_imm = cmp_eax_imm;
LOBYTE(a1->code_F4) = -12;
a1->add_eax_ebx = add_eax_ebx;
LOBYTE(a1->code_F5) = -11;
a1->sub_eax_ebx = sub_eax_ebx;
LOBYTE(a1->code_F3) = -13;
a1->nop = nop;
LOBYTE(a1->code_F6) = -10;
a1->jz_imm = jz_imm;
LOBYTE(a1->code_F7) = -9;
a1->mov_buf_imm = mov_buf_imm;
LOBYTE(a1->code_F8) = -8;
a1->enc_eax_2 = enc_eax_2; // 凱撒密碼 key = 2
buffer = malloc(0x400uLL);
return __memcpy_chk((buffer + 48), input, 18LL, -1LL);
}
sub_100001F00函數則需要返回正確,如下所示
__int64 __fastcall equal_to_1(vm_struc *a1)
{
a1->pc = &loc_100001980 + 4; // a1 = vm
while ( *a1->pc != 0xF3 )
run(a1);
free(buffer);
return a1->buffer;
}
也就是說,我們需要按照loc_100001980處的索引表來運行,也就是下表,運行到0xF3則停止
data = [0xF0, 0x10, 0x66, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x30, 0xF6, 0xC1, 0xF0, 0x10, 0x63, 0x00, 0x00,
0x00, 0xF8, 0xF2, 0x31, 0xF6, 0xB6, 0xF0, 0x10, 0x6A, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x32, 0xF6,
0xAB, 0xF0, 0x10, 0x6A, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x33, 0xF6, 0xA0, 0xF0, 0x10, 0x6D, 0x00,
0x00, 0x00, 0xF8, 0xF2, 0x34, 0xF6, 0x95, 0xF0, 0x10, 0x57, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x35,
0xF6, 0x8A, 0xF0, 0x10, 0x6D, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x36, 0xF6, 0x7F, 0xF0, 0x10, 0x73,
0x00, 0x00, 0x00, 0xF8, 0xF2, 0x37, 0xF6, 0x74, 0xF0, 0x10, 0x45, 0x00, 0x00, 0x00, 0xF8, 0xF2,
0x38, 0xF6, 0x69, 0xF0, 0x10, 0x6D, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x39, 0xF6, 0x5E, 0xF0, 0x10,
0x72, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x3A, 0xF6, 0x53, 0xF0, 0x10, 0x52, 0x00, 0x00, 0x00, 0xF8,
0xF2, 0x3B, 0xF6, 0x48, 0xF0, 0x10, 0x66, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x3C, 0xF6, 0x3D, 0xF0,
0x10, 0x63, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x3D, 0xF6, 0x32, 0xF0, 0x10, 0x44, 0x00, 0x00, 0x00,
0xF8, 0xF2, 0x3E, 0xF6, 0x27, 0xF0, 0x10, 0x6A, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x3F, 0xF6, 0x1C,
0xF0, 0x10, 0x79, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x40, 0xF6, 0x11, 0xF0, 0x10, 0x65, 0x00, 0x00,
0x00, 0xF8, 0xF2, 0x41, 0xF6, 0x06, 0xF7, 0x01, 0x00, 0x00, 0x00, 0xF3, 0xF7, 0x00, 0x00, 0x00,
0x00, 0xF3, 0x5D, 0xC3, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00]
翻譯過來大概流程如下
1
mov_reg_imm 0x66
enc_eax_2
cmp_eax_imm 0x30
jz_imm 0xC1
2
mov_reg_imm 0x63
enc_eax_2
cmp_eax_imm 0x31
jz_imm 0xB6
3
mov_reg_imm 0x6A
enc_eax_2
cmp_eax_imm 0x32
jz_imm 0xAB
…
(循環18次)
也就是說這個程序流程是很有規律的,然而enc_eax_2函數的內容如下,是一個典型的凱撒密碼
__int64 __fastcall sub_100001B80(char a1, int a2)
{
bool v3; // [rsp+7h] [rbp-11h]
bool v4; // [rsp+Fh] [rbp-9h]
char v5; // [rsp+17h] [rbp-1h]
v4 = 0;
if ( a1 >= 'A' )
v4 = a1 <= 'Z';
if ( v4 )
{
v5 = (a2 + a1 - 'A') % 26 + 'A';
}
else
{
v3 = 0;
if ( a1 >= 'a' )
v3 = a1 <= 'z';
if ( v3 )
v5 = (a2 + a1 - 'a') % 26 + 'a';
else
v5 = a1;
}
return v5;
}
我們就可以根據每一組一開始mov的字符串ASCII加2即可看出flag,如第一組0x66爲字符串 f ,轉化之後爲 h ,依次下去就可以得到flag