基础逆向练习题

check_in

附件:check_in.zip

init_array

通过IDA,逆向得到main函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
__int64 __fastcall main(int a1, char **a2, char **a3)
{
char s1[268]; // [rsp+10h] [rbp-110h] BYREF
int v5; // [rsp+11Ch] [rbp-4h]

v5 = 0;
gets(s1, 256LL, a3);
if ( !strncmp(s1, s2, 0x100uLL) )
puts("flag is right");
else
puts("flag is wrong");
return 0LL;
}

s2在data段,但是该字符串并非真正的flag。

1
2
.data:0000000000404040 ; char s2[]
.data:0000000000404040 s2 db 'flag{check_inn}',0

接下来有两种思路:

  1. init_array 中存在函数,在main函数之前执行。(因此s2被初始化成别的值)
  2. 查找对s2的引用,在 sub_4011D0() 函数中对s2做了更改。

两者都指向同一个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
_BYTE *sub_4011D0()
{
_BYTE *result; // rax

result = (_BYTE *)((unsigned int)s2 + 15);
*(_BYTE *)((int)result - 1LL) = '_';
*(_BYTE *)(int)result = 'i';
*(_BYTE *)((int)result + 1LL) = 's';
*(_BYTE *)((int)result + 2LL) = '_';
*(_BYTE *)((int)result + 3LL) = 'n';
*(_BYTE *)((int)result + 4LL) = 'o';
*(_BYTE *)((int)result + 5LL) = 't';
*(_BYTE *)((int)result + 6LL) = '_';
*(_BYTE *)((int)result + 7LL) = 'r';
*(_BYTE *)((int)result + 8LL) = 'e';
*(_BYTE *)((int)result + 9LL) = 'a';
*(_BYTE *)((int)result + 0xALL) = 'l';
*(_BYTE *)((int)result + 0xBLL) = 'l';
*(_BYTE *)((int)result + 0xCLL) = '}';
*(_BYTE *)((int)result + 0xDLL) = '\0';
return result;
}

所以flag是 flag{check_inn_is_not_reall}

dang-van

附件:dang-van.zip

一个更改过的base64

main函数如下,change函数中发现一个类似base64的索引表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v3; // bl
char v5[16]; // [rsp+0h] [rbp-40h] BYREF
char v6[16]; // [rsp+10h] [rbp-30h] BYREF
char v7[32]; // [rsp+20h] [rbp-20h] BYREF

std::string::string((std::string *)v5);
std::operator<<<std::char_traits<char>>(&std::cout, "Input your secret: ");
std::operator>><char>(&std::cin, v5);
std::string::string((std::string *)v6, (const std::string *)v5);
change((__int64)v7, (std::string *)v6);
v3 = std::operator==<char>(v7, "ms4otszPhcr7tMmzGMkHyFn=");
std::string::~string((std::string *)v7);
std::string::~string((std::string *)v6);
if ( v3 )
std::operator<<<std::char_traits<char>>(&std::cout, "Good boy! Submit your flag :)");
else
std::operator<<<std::char_traits<char>>(&std::cout, "Too bad :(");
std::string::~string((std::string *)v5);
return 0;
}

exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
a = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
b = "ELF8n0BKxOCbj/WU9mwle4cG6hytqD+P3kZ7AzYsag2NufopRSIVQHMXJri51Tdv="
b_str = "ms4otszPhcr7tMmzGMkHyFn="
dict1 = {}

for i in range(len(a)):
dict1[b[i]] = a[i]
print(dict1)

for j in b_str:
print(dict1[j],end="")

print("\n")

得到 RnVubnlfZW5jb2RlX2h1aCE= ,再base64解码:

1
2
$ echo RnVubnlfZW5jb2RlX2h1aCE= | base64 -d
Funny_encode_huh!

flag_finder

附件:flag_finder.zip

下断点调试

main函数如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
__int64 __fastcall main(int a1, char **a2, char **a3)
{
__int64 result; // rax
void *v4; // rsp
unsigned int v5; // ebx
char v6; // r13
char **v7; // [rsp+0h] [rbp-50h] BYREF
int v8; // [rsp+Ch] [rbp-44h]
unsigned int v9; // [rsp+18h] [rbp-38h]
int v10; // [rsp+1Ch] [rbp-34h]
__int64 v11; // [rsp+20h] [rbp-30h]
char *dest; // [rsp+28h] [rbp-28h]

v8 = a1;
v7 = a2;
if ( a1 == 2 )
{
v11 = (unsigned int)n - 1LL;
v4 = alloca(16 * (((unsigned __int64)(unsigned int)n + 15) / 0x10));
dest = (char *)&v7;
strcpy((char *)&v7, a2j);
v10 = 0;
v9 = 0;
while ( memcmp(dest, "9447", 4uLL) )
{
v5 = v9 % (unsigned int)n;
v6 = dest[v9 % (unsigned int)n];
dest[v5] = sub_40060D() ^ v6;
++v9;
}
if ( !memcmp(dest, v7[1], (unsigned int)n) )
printf("The flag is %s\n", v7[1]);
else
puts("Try again");
result = 0LL;
}
else
{
printf("Usage: %s <password>\n", *v7);
result = 1LL;
}
return result;
}

v7[1] 是我们输入的字符串,因此再memcmp(dest, v7[1], (unsigned int)n)处下断点即可看到dest中存放的字符串(即flag):

1
2
3
4
5
6
7
8
pwndbg> set arg aaaa
pwndbg> b *0x400729
pwndbg> r
────────────────────────────────────────[ DISASM ]──────────────────────────────────────────
► 0x400729 call memcmp@plt <memcmp@plt>
s1: 0x7fffffffdc10 ◂— '9447{C0ngr47ulaT1ons_p4l_buddy_y0Uv3_solved_the_re4l__H4LT1N6_prObL3M}'
s2: 0x7fffffffe154 ◂— 0x4548530061616161 /* 'aaaa' */
n: 0x46

得到flag:9447{C0ngr47ulaT1ons_p4l_buddy_y0Uv3_solved_the_re4l__H4LT1N6_prObL3M}

debug_me

附件:debug_me.zip

反调试

main函数中将我们的输入传给 sub_4006FD() 函数处理。此时有两个方法:1)直接在for循环中下断点,查看每次执行时 *(char *)(v3[i % 3] + 2 * (i / 3)) 的结果,将每个数减1就得到flag。2)重写这个函数,编译跑一遍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__int64 __fastcall sub_4006FD(__int64 a1)
{
int i; // [rsp+14h] [rbp-24h]
__int64 v3[4]; // [rsp+18h] [rbp-20h]

v3[0] = (__int64)"Dufhbmf";
v3[1] = (__int64)"pG`imos";
v3[2] = (__int64)"ewUglpt";
for ( i = 0; i <= 11; ++i )
{
if ( *(char *)(v3[i % 3] + 2 * (i / 3)) - *(char *)(i + a1) != 1 )
return 1LL;
}
return 0LL;
}

方法1

gdb调试时发现程序无法正常运行,ctrl-c 后可以看到,程序在0x4007e4处进入了循环。(另一种方法,在gdb r命令前,使用 catch syscall 对每一个系统调用下断点,也能跟踪到ptrace调用点)

1
2
3
4
──────────────────────────────────────[ DISASM ]─────────────────────────────────────────────
► 0x4007e4 jmp 0x4007e4 <0x4007e4>

0x4007e4 jmp 0x4007e4 <0x4007e4>

对应到二进制的 sub_4007A8() 函数,该二进制开启了反调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
__int64 sub_4007A8()
{
__int64 result; // rax

if ( (unsigned int)getenv("LD_PRELOAD") ) // 防注入
{
while ( 1 )
;
}
result = ptrace(PTRACE_TRACEME, 0LL, 0LL, 0LL); // 反调试
if ( result < 0 )
{
while ( 1 )
;
}
return result;
}

这个题比较简单,虽然开了反调试,但我们可以在调用 sub_4007A8() 函数之前下断点,gdb中修改内存或寄存器值,绕过检测,然后进入 sub_4006FD() 函数查看字符串计算结果,得到flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void __fastcall init(unsigned int a1, __int64 a2, __int64 a3)
{
__int64 v4; // rbx
signed __int64 v5; // rbp

v4 = 0LL;
v5 = &off_600E18 - funcs_4008D9;
init_proc();
if ( v5 ) // 在这之前将v5设置成0
{
do
((void (__fastcall *)(_QWORD, __int64, __int64))funcs_4008D9[v4++])(a1, a2, a3);
while ( v4 != v5 );
}
}

方法2

exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<stdio.h>
#include<stdint.h>

int main(){

uint64_t v3[3];
v3[0] = (uint64_t)"Dufhbmf";
v3[1] = (uint64_t)"pG`imos";
v3[2] = (uint64_t)"ewUglpt";

int i = 0,a =0;
for(i = 0; i <= 11; i++){
a = *(char *)(v3[i%3] + 2 *(i/3));
printf("0x%x, ",a-1);
printf("%c, ",a-1);
}

return 0;
}
// Code_Talkers

float

附件:float.zip

【暂未解出】
可参考WP:强网杯2021 ctf线上赛ezmath wp

本题的主要计算步骤在sub_13f3函数中,v3的初始值为0.2021,i的起始值为0x2021,乍一看v3 = 2.718281828459045 - (double)i * v3 的计算结果会小于0。而main函数中待比对的结果dbl_4020数组中值都是大于0的浮点数。所以这里的计算很奇怪,加上init_array中有一个奇怪的计算函数,所以我们动态调试来看看v3这个位置的实际情况。调试结果如下图所示,v3的初始值是0.00048291080524950886,并不是我们之前分析的0.2021。

actaul_v3

python:16进制与double/float之间的转换

Python 十六进制与浮点数互相转换

python中struct.pack()函数和struct.unpack()函数

转换示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import struct

num_hex = 0x3f19ad3fbd59ca39
num_double = 0.00009794904266317233

# hex to double
num_hex_str = '{:x}'.format(num_hex) # 将数转成hex string - '3f19ad3fbd59ca39'
result_double = struct.unpack('>d',bytes.fromhex(num_hex_str))[0] # 将hex string转成double型数
print("double is : {}".format(result_double))

# double to hex
result_hex_str = struct.pack(">d",num_double).hex() # 将double转成hex string
result_int = int(result_hex_str,16) # hex string转成数
print("hex is : 0x{:x}".format(result_int))

sympy库:解数学方程

rMath-浮点运算的逆向分析

一个例子,假设要计算以下式子中x的值:

1
2.718281828459045 - x*0.00048291080524950886 = 0.00009794904266317233

那么可以利用如下脚本

1
2
3
4
5
6
7
8
9
10
import struct
from sympy import *

Str = symbols('Str')

jie = solve(2.718281828459045 - Str*0.00048291080524950886 - 0.00009794904266317233,Str)

ret = struct.pack(">f",jie[0]).hex()

print(ret)

IDA使用小技巧——将数据转换成double类型的操作:

ida_to_double

simulator

附件:simulator.zip

GoodRE

附件:GoodRE.zip

SimpleFileSytem

附件:SimpleFileSystem.zip

SoMuchCode

附件:SoMuchCode.zip