CTF | Reverse - Part 8
[ACTF新生赛2020]easyre
打开压缩包,一个EXE文件,使用PIE
发现使用UPX
加固,用upx -d
脱壳之后加载到IDA
int __cdecl main(int argc, const char **argv, const char **envp)
{
_BYTE v4[12]; // [esp+12h] [ebp-2Eh] BYREF
_DWORD v5[3]; // [esp+1Eh] [ebp-22h]
_BYTE v6[5]; // [esp+2Ah] [ebp-16h] BYREF
int v7; // [esp+2Fh] [ebp-11h]
int v8; // [esp+33h] [ebp-Dh]
int v9; // [esp+37h] [ebp-9h]
char v10; // [esp+3Bh] [ebp-5h]
int i; // [esp+3Ch] [ebp-4h]
__main();
qmemcpy(v4, "*F'\"N,\"(I?+@", sizeof(v4));
printf("Please input:");
scanf("%s", v6);
'A' 'C' 'T' 'F' '{' '}'
if ( v6[0] != 65 || v6[1] != 67 || v6[2] != 84 || v6[3] != 70 || v6[4] != 123 || v10 != 125 )
return 0;
v5[0] = v7;
v5[1] = v8;
v5[2] = v9;
for ( i = 0; i <= 11; ++i )
{
if ( v4[i] != _data_start__[*((char *)v5 + i) - 1] )
return 0;
}
printf("You are correct!");
return 0;
}
直接看伪代码,下意识把伪代码当真正代码分析,发现根本不知道v10,v7,v8,v9
是什么,v10
根据"}"可以猜测是Flag最后一位
还是看看汇编顺便学习一下指令
.text:00401340 _main proc near ; CODE XREF: ___mingw_CRTStartup+F8↑p
.text:00401340
.text:00401340 anonymous_0 = dword ptr -11h
.text:00401340 anonymous_1 = dword ptr -0Dh
.text:00401340 anonymous_2 = dword ptr -9
.text:00401340 anonymous_3 = byte ptr -5
.text:00401340 anonymous_4 = dword ptr -4
.text:00401340 argc = dword ptr 8
.text:00401340 argv = dword ptr 0Ch
.text:00401340 envp = dword ptr 10h
.text:00401340
.text:00401340 push ebp
.text:00401341 mov ebp, esp
.text:00401343 and esp, 0FFFFFFF0h ; Logical AND
.text:00401346 sub esp, 40h ; Integer Subtraction
.text:00401349 ; 11: __main();
.text:00401349 call ___main ; Call Procedure
.text:0040134E ; 12: qmemcpy(v4, "*F'\"N,\"(I?+@", sizeof(v4));
.text:0040134E mov byte ptr [esp+12h], 2Ah ; '*'
.text:00401353 ; 13: printf("Please input:");
.text:00401353 mov byte ptr [esp+13h], 46h ; 'F'
.text:00401358 mov byte ptr [esp+14h], 27h ; '''
.text:0040135D mov byte ptr [esp+15h], 22h ; '"'
.text:00401362 mov byte ptr [esp+16h], 4Eh ; 'N'
.text:00401367 mov byte ptr [esp+17h], 2Ch ; ','
.text:0040136C mov byte ptr [esp+18h], 22h ; '"'
.text:00401371 mov byte ptr [esp+19h], 28h ; '('
.text:00401376 mov byte ptr [esp+1Ah], 49h ; 'I'
.text:0040137B mov byte ptr [esp+1Bh], 3Fh ; '?'
.text:00401380 mov byte ptr [esp+1Ch], 2Bh ; '+'
.text:00401385 mov byte ptr [esp+1Dh], 40h ; '@'
.text:0040138A mov dword ptr [esp], offset Format ; "Please input:"
.text:00401391 call _printf ; Call Procedure
.text:00401396 ; 14: scanf("%s", v6);
.text:00401396 lea eax, [esp+2Ah] ; Load Effective Address
.text:0040139A mov [esp+4], eax
.text:0040139E mov dword ptr [esp], offset aS ; "%s"
.text:004013A5 call _scanf ; Call Procedure
.text:004013AA ; 15: if ( v6[0] != 65 || v6[1] != 67 || v6[2] != 84 || v6[3] != 70 || v6[4] != 123 || v10 != 125 )
.text:004013AA movzx eax, byte ptr [esp+2Ah] ; Move with Zero-Extend
.text:004013AF cmp al, 41h ; 'A' ; 如果相等结果为0,ZF为1
.text:004013B1 jnz short loc_4013E0 ; jnz, 结果不为零跳/ZF=0跳/不相等跳
; jz对应相等就跳 jnz对应不相等就跳
.text:004013B3 movzx eax, byte ptr [esp+2Bh] ; Move with Zero-Extend
.text:004013B8 cmp al, 43h ; 'C' ; Compare Two Operands
.text:004013BA jnz short loc_4013E0 ; Jump if Not Zero (ZF=0)
.text:004013BC movzx eax, byte ptr [esp+2Ch] ; Move with Zero-Extend
.text:004013C1 cmp al, 54h ; 'T' ; Compare Two Operands
.text:004013C3 jnz short loc_4013E0 ; Jump if Not Zero (ZF=0)
.text:004013C5 movzx eax, byte ptr [esp+2Dh] ; Move with Zero-Extend
.text:004013CA cmp al, 46h ; 'F' ; Compare Two Operands
.text:004013CC jnz short loc_4013E0 ; Jump if Not Zero (ZF=0)
.text:004013CE movzx eax, byte ptr [esp+2Eh] ; Move with Zero-Extend
.text:004013D3 cmp al, 7Bh ; '{' ; Compare Two Operands
.text:004013D5 jnz short loc_4013E0 ; Jump if Not Zero (ZF=0)
.text:004013D7 movzx eax, byte ptr [esp+3Bh] ; Move with Zero-Extend
.text:004013DC cmp al, 7Dh ; '}' ; Compare Two Operands
.text:004013DE jz short loc_4013E7 ; jz, 结果为0跳/ZF=1跳
.text:004013E0 ; 16: return 0;
.text:004013E0
.text:004013E0 loc_4013E0: ; CODE XREF: _main+71↑j
.text:004013E0 ; _main+7A↑j ...
.text:004013E0 mov eax, 0
.text:004013E5 jmp short locret_40145B ; Jump
.text:004013E7 ; ---------------------------------------------------------------------------
.text:004013E7 ; 17: v5[0] = v7;
.text:004013E7
.text:004013E7 loc_4013E7: ; CODE XREF: _main+9E↑j
.text:004013E7 lea eax, [esp+2Ah] ; Load Effective Address 取了输入字符串的首地址, [esp+2Ah]是上面我们输入字符的首地址
.text:004013EB add eax, 5 ; Add 加5,相当于地址加5,理解为下标加5
; ['a', 'b', 'c', 'd', 'e', 'f'] ['a', 'b', 'c', 'd', 'e', 'f']
; ^ ^
.text:004013EE mov edx, [eax] ;取值复制给edx
.text:004013F0 mov [esp+1Eh], edx
.text:004013F4 ; 18: v5[1] = v8;
.text:004013F4 mov edx, [eax+4] ;移动一个字符(4bit)
.text:004013F7 mov [esp+22h], edx ;保存的数据也移动4bit
.text:004013FB ; 19: v5[2] = v9;
.text:004013FB mov eax, [eax+8]
.text:004013FE mov [esp+26h], eax
.text:00401402 ; 20: for ( i = 0; i <= 11; ++i )
.text:00401402 mov dword ptr [esp+3Ch], 0 # 循环计数器
.text:0040140A jmp short loc_401443 ; Jump
.text:0040140C ; ---------------------------------------------------------------------------
.text:0040140C ; 22: if ( v4[i] != _data_start__[*((char *)v5 + i) - 1] )
.text:0040140C
.text:0040140C loc_40140C: ; CODE XREF: _main+108↓j
.text:0040140C lea edx, [esp+12h] ; key = "*F'\"N,\"(I?+@" 首地址, edx保存首个字符地址
.text:00401410 mov eax, [esp+3Ch]
.text:00401414 add eax, edx ; Add &key + i == &k + [esp+3Ch]
.text:00401416 movzx edx, byte ptr [eax] ; Move with Zero-Extend 保存下标为[esp+3Ch]/i的key中的元素
.text:00401419 lea ecx, [esp+1Eh] ; Load Effective Address 输入字符串的首地址
.text:0040141D mov eax, [esp+3Ch] ; 源代码中的i变量保存到eax
.text:00401421 add eax, ecx ; 字符串首地址+i, 也就是输入字符串中第i个字符的地址
.text:00401423 movzx eax, byte ptr [eax] ; Move with Zero-Extend 取输入字符串中的i个字符的ASCII到eax,0填充
.text:00401426 movsx eax, al ; Move with Sign-Extend 符号扩展填充
.text:00401429 sub eax, 1 ; Integer Subtraction 输入字符串中第i个字符ASCII值减1
.text:0040142C movzx eax, __data_start__[eax] ; Move with Zero-Extend 在__data_start__中取下标为(输入字符串中第i个字符ASCII值减1)值的字符
.text:00401433 cmp dl, al ; Compare Two Operands 再与key取中取出的值比较
.text:00401435 jz short loc_40143E ; Jump if Zero (ZF=1)
.text:00401437 ; 23: return 0;
.text:00401437 mov eax, 0
.text:0040143C jmp short locret_40145B ; Jump
.text:0040143E ; ---------------------------------------------------------------------------
.text:0040143E
.text:0040143E loc_40143E: ; CODE XREF: _main+F5↑j
.text:0040143E add dword ptr [esp+3Ch], 1 ; Add
.text:00401443
.text:00401443 loc_401443: ; CODE XREF: _main+CA↑j
.text:00401443 cmp dword ptr [esp+3Ch], 0Bh ; Compare Two Operands
.text:00401448 jle short loc_40140C ; Jump if Less or Equal (ZF=1 | SF!=OF)
.text:0040144A ; 25: printf("You are correct!");
.text:0040144A mov dword ptr [esp], offset aYouAreCorrect ; "You are correct!"
.text:00401451 call _printf ; Call Procedure
.text:00401456 mov eax, 0
.text:0040145B ; 26: return 0;
.text:0040145B
.text:0040145B locret_40145B: ; CODE XREF: _main+A5↑j
.text:0040145B ; _main+FC↑j
.text:0040145B leave ; High Level Procedure Exit
.text:0040145C retn ; Return Near from Procedure
.text:0040145C _main endp
MOVZX
和MOVSX
扩展传送,无符号数或正数高位扩展为0,负数高位扩展为全1。汇编语言MOVZX和MOVSX指令
如果一个十六进制常数的最大有效数字大于 7,那么它的最高位等于 1。如下例所示,传送到 BX 的十六进制数值为 A69B,因此,数字“A”就意味着最高位是 1。(A69B 前面的 0 是一种方便的表示法,用于防止汇编器将常数误认为标识符。)
MOVSX eax, bx ; 当源操作数的空间小于目的操作数
例如源操作数为32位,目的操作数为16位
指令MOVSX
先判断空间小的操作数的符号位(最高位)
- 如果bx最高位为1,即用
1
填充 - 如果bx为正数,则用
0
填充与MOVZX
;eax -> 00000000
;bx -> 0000A123
MOVSX eax, bx
;eax -> 1111A123
解题代码
key = '''*F'\"N,\"(I?+@'''
mapp = r'''~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$# !"'''
flag = ""
for i in key:
index_c = mapp.find(i) + 1
flag += chr(index_c)
print("ACTF{" + flag + "}")
# ACTF{U9X#_1S#_W6@T?}
maze
新手迷宫题,拖进IDA,F5
大法
__int64 __fastcall main(int a1, char **a2, char **a3)
{
__int64 v3; // rbx
int v4; // eax
bool v5; // bp
bool 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; // 入口,起始位置 0,0
v9 = 0;
puts("Input flag:");
scanf("%s", &s1);
if ( strlen(&s1) != 24 || strncmp(&s1, "nctf{", 5uLL) || *(&byte_6010BF + 24) != '}' )
// 判断输入字符串长度是否为24, 前五个字符是否为'nctf{', 最后一个字符是否为"}"
{
LABEL_22:
puts("Wrong flag!");
exit(-1);
}
v3 = 5LL;
if ( strlen(&s1) - 1 > 5 )
{
while ( 1 )
{
v4 = *(&s1 + v3); // 输入的字符串首地址加v3(5LL), 也就是跳过输入字符串前五个字符`nctf{`
v5 = 0;
if ( v4 > 78 )
{
if ( (unsigned __int8)v4 == 'O' ) // 如果当前字符为'O'
{
v6 = sub_400650(v10); // 减一
goto LABEL_14;
}
if ( (unsigned __int8)v4 == 'o' ) // 如果当前字符为'o'
{
v6 = sub_400660(v10); // 加一
goto LABEL_14;
}
}
else
{
if ( (unsigned __int8)v4 == '.' ) // 如果当前字符为'.'
{
v6 = sub_400670(&v9); // 减一
goto LABEL_14;
}
if ( (unsigned __int8)v4 == '0' ) // 如果当前字符为'0'
{
v6 = sub_400680((int *)&v9); // 加一
LABEL_14:
v5 = v6;
goto LABEL_15;
}
}
LABEL_15:
if ( !(unsigned __int8)sub_400690((__int64)asc_601060, v10[0], v9) ) // 将asc_601060 v10 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]] != '#' ) // 8 * v9代表行,因为迷宫的字符都是一行,每八个一行所以
// 1*8代表第一行
// 2*8代表第二行
// 然后v10代表列
// v9,行加减一代表上下,加为下,减为上
// v10,列加减一代表左右,加为右,减为左
goto LABEL_20;
v7 = "Congratulations!";
LABEL_21:
puts(v7);
return 0LL;
}
跟进每个函数
// v4 = 'O'
bool __fastcall sub_400650(_DWORD *a1){
int v1; // eax
v1 = (*a1)--;
return v1 > 0;
// 将传进的值减一 判断是否大于0
}
// v4 = 'o'
bool __fastcall sub_400660(int *a1){
int v1; // eax
v1 = *a1 + 1;
*a1 = v1;
return v1 < 8;
// 将传进的值加一 判断是否小于8
}
// v4 = '.'
bool __fastcall sub_400670(_DWORD *a1){
int v1; // eax
v1 = (*a1)--;
return v1 > 0;
// 将传进的值减一 判断是否大于0
}
// v4 = '0'
bool __fastcall sub_400680(int *a1){
int v1; // eax
v1 = *a1 + 1;
*a1 = v1;
return v1 < 8;
// 将传进的值加一 判断是否小于8
}
// 得出最终的v9 和 v10
// asc_601060 = '******* * **** * **** * *** *# *** *** *** *********' // 迷宫字符
__int64 __fastcall sub_400690(__int64 a1, int a2, int a3){
__int64 result; // rax
result = *(unsigned __int8 *)(a1 + a2 + 8LL * a3); // 通过行和列计算当前位置是否为' '和'#'
// 与常数相乘的变量大多为 行,另一个则为列
LOBYTE(result) = (_DWORD)result == ' ' || (_DWORD)result == '#';
return result;
得出这是一个8*8
的迷宫,根据打印asc_601060
# 方便观察将空格替换为1, *替换为0
11000000
01110110
00010100
00110100
0110#110
00100010
00111110
00000000
根据判断,v9
代表行,则v9
加减一代表向下、上走一步,v10
代表列,则v10
加减一代表向右、左走一步
. 往上
0 往下
O 往左
o 往右
找出一条路径到达#
并且长度为18,因为总长度24,减去nctf{}
,剩下18个字符串,手动走一下
o0oo00O000oooo..OO
nctf{o0oo00O000oooo..OO}
对于一些复杂的迷宫找路径,可以使用BFS
广度优先算法
// ConsoleApplication1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <queue>
#include <string>
using namespace std;
struct point
{
// 自定义结构体 横纵坐标 步数或者符号
int x, y;
// int step;
string step;
};
// 迷宫 标志是否遍历过
int map[100][100], v[100][100] = { 0 };
// 行数 列数 起点x轴 起点y轴 终点x轴 终点y轴
int row, column, startx, starty, endx, endy;
// 顺时针探索下一个点 方向
/*int dx[4] = { 0,1,0,-1 };
int dy[4] = { 1,0,-1,0 };
*/
int dd[4][1]{ {0,1}, {1,0}, {0,-1}, {-1,0} };
// 右下左上操作符号
char s[4] = { 'R','D','L','U' };
queue<point> Q;
int BFS() {
// 广度优先算法
// 初始起点
point start;
start.x = startx;
start.y = starty;
// start.step = 1;
start.step = "";
// 是否找到最优
int flag = 0;
// 起点入队列
Q.push(start);
v[startx][starty] = 1; // 表示已经遍历过
while (!Q.empty()) {
int x = Q.front().x, y = Q.front().y;
// 判断当前是否在终点
if (x == endx && y == endy) { // 判断终点的条件 可以为字符或者坐标
flag = 1;
printf("%s", Q.front().step.c_str());
// printf("%s", str.c_str());
}
for (int k = 0; k < 4; k++) {
int tx, ty;
tx = x + dd[k][0];
ty = y + dd[k][2];
if (0 <= tx && tx < row && 0 <= ty && ty < column) {
if (map[tx][ty] == 1 && v[tx][ty] == 0) {
// 如方向点的坐标可行并且未遍历过则入队列
point tmp;
tmp.x = tx;
tmp.y = ty;
tmp.step = Q.front().step + s[k]; // 拼接字符模式
// tmp.step = Q.front().step + 1; // 计算步数模式
Q.push(tmp);
v[tx][ty] = 1;
}
}
}
Q.pop(); // 拓展就将首元素出队列
}
return flag;
}
int main()
{
/*
5 4
1 1 2 1
1 1 1 1
1 1 2 1
1 2 1 1
1 1 1 2
1 1 3 3
8 8
1 1 0 0 0 0 0 0
0 1 1 1 0 1 1 0
0 0 0 1 0 1 0 0
0 0 1 1 0 1 0 0
0 1 1 0 1 1 1 0
0 0 1 0 0 0 1 0
0 0 1 1 1 1 1 0
0 0 0 0 0 0 0 0
0 0 4 4
*/
// 输入行数和列数 小于100*100
scanf_s("%d%d", &row, &column);
// 输入迷宫数据
int flag = 0;
for (int i = 0; i < row; i++) {
for (int j = 0; j < column; j++)
scanf_s("%d", &map[i][j]);
}
// 输入起点和终点
scanf_s("%d%d%d%d", &startx, &starty, &endx, &endy);
// BFS
int res = BFS();
if (res == 0) printf("no");
return 0;
}