articles icon indicating copy to clipboard operation
articles copied to clipboard

逆向简单随笔

Open xinali opened this issue 6 years ago • 0 comments

逆向tips(简单随笔)

下面列出的就是一般程序中常用的从网络套接字中读取数据的 API 函数

read/write   
recv/send   
recvfrom/sendto   
WSARecv/WSASend   
WSARecvFrom/WSASendTo   
ioctl   octlsocket   
WSARecvDisconnect/WSASendDisconnect   
WSARecvEx/WSASendEx   
recvmsg/sendmsg   
WSARecvMsg/WSASendMsg

函数堆栈初始化

push        ebp  //栈底压栈
mov         ebp,esp //栈底下移,更详细的请参考我关于ebp,esp的解释
sub         esp,0CCh //局部变量预留空间
push        ebx  //保存ebx  
push        esi  //保存esi  
push        edi  //保存edi  
lea         edi,[ebp-0CCh] //下移edi到栈顶
mov         ecx,33h //0CCh/4 = 33h
mov         eax,0CCCCCCCCh //eax赋值
rep stos    dword ptr es:[edi] //从edi开始做33h次赋值0CCCCCCCCh ,初始化栈内存

运行时错误检查

mov     esi, esp
mov     eax, [ebp+Test.m_nInt]
push    eax
push    offset Format   ; "CTest: %d \r\n"
call    ds:__imp__printf
add     esp, 8
cmp     esi, esp
call    j___RTC_CheckEsp
call    j_?eof@?$char_traits@D@std@@SAHXZ ; std::char_traits<char>::eof(void)
mov     esi, esp

主要体现在

mov esi, esp
cmp esi, esp

c++ 类 this 指针使用

  1. 通过ecx传递(调用方式thiscall) c++ 源代码
class CTest {
	public:
		//void __stdcall SetNumber(int nNumber) {
		void SetNumber(int nNumber) {
			m_nInt = nNumber;
		}

	public:
		int m_nInt;
};

int _tmain(int argc, _TCHAR* argv[])
{
	CTest Test;
	Test.SetNumber(5);
	printf("CTest: %d \r\n", Test.m_nInt);
	std::cin.ignore();
}

对应的反汇编代码

009513EE                               6A 05                   push 0x5
009513F0                               8D4D F8                 lea ecx,dword ptr ss:[ebp-0x8]  ; 获取this指针
009513F3                               E8 F8FCFFFF             call test.009510F0
009513F8                               8BF4                    mov esi,esp
009513FA                               8B45 F8                 mov eax,dword ptr ss:[ebp-0x8]  ;  kernel32.7649336A
009513FD                               50                      push eax                 ; kernel32.BaseThreadInitThunk
009513FE                               68 30589500             push test.00955830              ; CTest: %d \r\n
00951403                               FF15 50839500           call dword ptr ds:[<&MSVCR100D.printf>]          ; msvcr100.printf
  1. 通过参数传递

c++ 源代码(调用方式__stdcall)

class CTest {
	public:
		void __stdcall SetNumber(int nNumber) {
			m_nInt = nNumber;
		}
	public:
		int m_nInt;
};

int _tmain(int argc, _TCHAR* argv[])
{
	CTest Test;
	Test.SetNumber(5); 
	printf("CTest: %d \r\n", Test.m_nInt);
}

对应的反汇编代码

011513EE                               6A 05                   push 0x5
011513F0                               8D45 F8                 lea eax,dword ptr ss:[ebp-0x8] ;this指针使用参数传递
011513F3                               50                      push eax                                         ; kernel32.BaseThreadInitThunk
011513F4                               E8 F7FCFFFF             call test.011510F0


011514E0 test.CTest::SetNumberc_erro>  55                      push ebp
011514E1                               8BEC                    mov ebp,esp
011514E3                               81EC C0000000           sub esp,0xC0
011514E9                               53                      push ebx
011514EA                               56                      push esi
011514EB                               57                      push edi
011514EC                               8DBD 40FFFFFF           lea edi,dword ptr ss:[ebp-0xC0]
011514F2                               B9 30000000             mov ecx,0x30
011514F7                               B8 CCCCCCCC             mov eax,0xCCCCCCCC
011514FC                               F3:AB                   rep stos dword ptr es:[edi]
011514FE                               8B45 08                 mov eax,dword ptr ss:[ebp+0x8] ;取得this指针(第一个参数)
01151501                               8B4D 0C                 mov ecx,dword ptr ss:[ebp+0xC] ;取得第二个参数
01151504                               8908                    mov dword ptr ds:[eax],ecx
01151506                               5F                      pop edi                                          ; kernel32.7649336A
01151507                               5E                      pop esi                                          ; kernel32.7649336A
01151508                               5B                      pop ebx                                          ; kernel32.7649336A
01151509                               8BE5                    mov esp,ebp
0115150B                               5D                      pop ebp                                          ; kernel32.7649336A
0115150C                               C2 0800                 retn 0x8

TIPS

  1. 类中对象的数据成员传参顺序:最先定义的数据成员最后压栈,最后定义的数据成员最先压栈。

  2. 类中没有数据成员,而编译器为我们添加了vptr

  3. 在调试泡泡堂程序的过程中,在send设下了断点,一直提示设置的断点范围不在程序范围内,解决办法

    在设置里options->debug options->security->warn when breakpoint is outside the code section

问题

线程发包 ==> 跳出线程

心跳包的地址不固定,但是每次运行开始,到进入游戏,心跳发包的线程应该是一样的,因为socket id和相关的buff地址是一样的

ollydbg单线程调试,一个线程启动,别的线程挂起。

ollydbg自带的调用栈不可信,应该使用堆栈里面提供的调用栈,这个才是实时的调用栈。

如何从send函数中跳出

原理就是跟踪写入send参数buff的函数,可以这样解释

send(socket_id, len, buff, flags),为了程序的性能问题,一般的游戏会把send/recv放在线程中,加解密又是在另一部分,send函数肯定不会改变buff的数据值,那么能够改变buff值的一般也就是加解密函数了,所以我们使用内存断点在buff的地址设下写断点,这样就可以找到加解密改变buff数据的函数了,同样的也就成功的跳出了,发包线程,不会一直在线程中循环。

关于壳

壳一般是对程序进行压缩或者加密,无法进行动态或者静态调试

动态调试:

  1. 成功脱壳,之后进行调试
  2. 壳一般会检测调试软件的进程,发现了调试进程就直接退出,如果能够不让其发现调试进程或是骗过调试进程,就能够成功的进行程序的调试(刘庆民的od就是利用了这个方式),但是这里也有疑问,这个壳没有去除,是否会影响对于代码的检查和程序逻辑思路的推理呢?

可以根据刘庆民的绕过方法,进行进一步的探索延伸学习!

关于函数参数地址传递参数的理解

void f1 (int x, int y, int *sum, int *product)
{
    *sum=x+y;
    *product=x*y;
};

int sum, product;

int main()
{
    f1(123, 456, &sum, &product);
    printf ("sum=%d, product=%d", sum, product);
    return 0;
};

对应的反汇编代码

sum DD  01H DUP (?)
product DD 01H DUP (?)
$SG5336 DB        'sum=%d, product=%d', 00H
EXTRN   ___acrt_iob_func:PROC
EXTRN   ___stdio_common_vfprintf:PROC
_main   PROC
        push     ebp
        mov      ebp, esp
        push     OFFSET ?product@@3HA
        push     OFFSET ?sum@@3HA
        push     456                    ; 000001c8H
        push     123                    ; 0000007bH
        call     ?f1@@YAXHHPAH0@Z
        add      esp, 16              ; 00000010H
        mov      eax, DWORD PTR product
        push     eax
        mov      ecx, DWORD PTR sum
        push     ecx
        push     OFFSET $SG5336
        call     _printf
        add      esp, 12              ; 0000000cH
        xor      eax, eax
        pop      ebp
        ret      0
_main   ENDP
_x$ = 8                                       ; size = 4
_y$ = 12                                                ; size = 4
_sum$ = 16                                          ; size = 4
_product$ = 20                                      ; size = 4
f1 PROC
        push     ebp
        mov      ebp, esp
        sum DD  01H DUP (?)
product DD 01H DUP (?)
$SG5336 DB        'sum=%d, product=%d', 00H
EXTRN   ___acrt_iob_func:PROC
EXTRN   ___stdio_common_vfprintf:PROC
_main   PROC
        push     ebp
        mov      ebp, esp
        push     OFFSET ?product@@3HA
        push     OFFSET ?sum@@3HA
        push     456                    ; 000001c8H
        push     123                    ; 0000007bH
        call     ?f1@@YAXHHPAH0@Z
        add      esp, 16              ; 00000010H
        mov      eax, DWORD PTR product
        push     eax
        mov      ecx, DWORD PTR sum
        push     ecx
        push     OFFSET $SG5336
        call     _printf
        add      esp, 12              ; 0000000cH
        xor      eax, eax
        pop      ebp
        ret      0
_main   ENDP
_x$ = 8                                       ; size = 4
_y$ = 12                                                ; size = 4
_sum$ = 16                                          ; size = 4
_product$ = 20                                      ; size = 4
f1 PROC
        push     ebp
        mov      ebp, esp
        mov      eax, DWORD PTR _x$[ebp]
        add      eax, DWORD PTR _y$[ebp]
        mov      ecx, DWORD PTR _sum$[ebp]
        mov      DWORD PTR [ecx], eax
        mov      edx, DWORD PTR _x$[ebp]
        imul     edx, DWORD PTR _y$[ebp]
        mov      eax, DWORD PTR _product$[ebp]
        mov      DWORD PTR [eax], edx
        pop      ebp
        ret      0
f1 ENDP
        pop      ebp
        ret      0
f1 ENDP

这里有一个地方是我一直不理解的,其中有问号的地方

mov      eax, DWORD PTR _x$[ebp]
add      eax, DWORD PTR _y$[ebp]
mov      ecx, DWORD PTR _sum$[ebp]  ???
mov      DWORD PTR [ecx], eax
mov      edx, DWORD PTR _x$[ebp]
imul     edx, DWORD PTR _y$[ebp]
mov      eax, DWORD PTR _product$[ebp] ???
mov      DWORD PTR [eax], edx

f1函数中直接将参数的值给了ecx和eax,既然是地址传递,我一直觉得这里应该用lea命令,将参数的地址赋值出来,但是我们结合main函数的汇编代码,我们就能看出来了,main 函数里面push的就是地址,所以我们可以知道,ebp+n的参数值本身就是一个地址,我们要是利用lea,取地址之后再取地址,这里不是我们所期望的,所以正确的应该是将ebp+n的值(也就是参数的地址)给ecx/eax,之后再将计算的值放入该地址,也就完成了函数的指针传递。

for 循环反汇编基础代码

#include <stdio.h>
void f(int i)
{
    printf ("f(%d)", i);
};
int main()
{
    int i;
    for (i=2; i<10; i++)
    {
        f(i);
    }
    return 0;
};

对应的反汇编代码

_i$ = -4
_main     PROC
        push    ebp
        mov     ebp, esp
        push    ecx
        mov     DWORD PTR _i$[ebp], 2       ; loop initialization
        jmp     SHORT $LN3@main
$LN2@main:
        mov     eax, DWORD PTR _i$[ebp]     ; here is what we do after each iteration:
        add     eax, 1                      ; add 1 to i value
        mov     DWORD PTR _i$[ebp], eax
$LN3@main:
        cmp     DWORD PTR _i$[ebp], 10      ; this condition is checked *before* each iteration
        jge     SHORT $LN1@main             ; if i is biggest or equals to 10, let’s finish loop
        mov     ecx, DWORD PTR _i$[ebp]     ; loop body: call f(i)
        push    ecx
        call    _f
        add     esp, 4
        jmp     SHORT $LN2@main             ; jump to loop begin
$LN1@main:                                  ; loop end
        xor     eax, eax
        mov     esp, ebp
        pop     ebp
        ret     0
_main ENDP

一般分为这几个部分

  1. 开始的初始化
  2. 从初始化跳转到循环的主体执行,主体的开始部分会先判断循环的条件
  3. 循环主题执行完毕,会跳入一个过程,主要是i++

关于fs寄存器

偏移  说明
000  指向SEH链指针
004  线程堆栈顶部
008  线程堆栈底部
00C  SubSystemTib
010  FiberData
014  ArbitraryUserPointer
018  FS段寄存器在内存中的镜像地址
020  进程PID
024  线程ID
02C  指向线程局部存储指针
030  PEB结构地址(进程结构)
034  上个错误号

Win64 下 gs:[30] 是存放 TIB 块的 Self 指针,而在 Win32 下是 fs:[18]

反汇编数组

数组的计数一般存在ecx中,数组的基址靠近esp,msvc一般是[ebp-n+i],n值是其所需的所有的字节数,i的不断递增,访问整个数组。

dword ptr ds[eax-n]

eax并不会解引用,会直接用eax里面的值进行运算,其实细细类比一下就可以理解了,可以从两方面理解

  1. 有中括号的这种才会出现解引用
  2. 平时利用堆栈ss:[ebp-4],也是直接用ebp里面的地址,并不会解引用!

寄存器在参数传递中的作用

⚠️

lea ecx, ss:[ebp-n]
push ecx
call eax

很明显,ecx是通过指针的方式传递的,但是函数执行完之后,ecx的值很有可能会发生改变,我们传递这个地址,只是表明该地址在该函数中的数据会发生改变,不要简单的想着ecx的值不会改变,所以要注意一下这种地址的传递方式!

函数的查看、跟踪等,主要就是看参数和返回值,特别是传递地址时,因为c++会特别多的使用到指针。

对齐方式????

调用方式总结

调用方式 参数入栈方式 恢复栈平衡 使用语言
_stdcall 右—>左 子函数 C++
_fastcall 右—>左 子函数 Delphi
_cdcall 右—>左 母函数 C

delphi 逆向

参数传递与c/c++不同点:

参数1------> EAX 
参数2------> EDX 
参数3------> ECX 

其余参数通过栈的方式传递

在逆向delphi时,不能只看这几个参数所涉及到的寄存器或者是栈数据,比如

00457C96                  FF75 F4               push dword ptr ss:[ebp-0xC]
00457C99                  8D83 18030000         lea eax,dword ptr ds:[ebx+0x318]
00457C9F                  BA 04000000           mov edx,0x4
00457CA4                  E8 93BFFAFF           call CKme.00403C3C

如果仅仅只根据寄存器和栈,函数00403C3C的参数就只是这三个

ss:[ebp-0xc] 最先入栈,最右边的参数
eax 参数1
edx 参数2

在通过ida pro来看看伪代码

int v4; // ebx
int v6; // ST08_4
int v7; // ST04_4
int v8; // ecx
int v35; // [esp+2Ch] [ebp-Ch]
System::__linkproc__ LStrCatN(v4 + 792, 4, v8, v6, v7, v35);

这个是ida pro分析过后的代码,其实我们简单点分析,可以这样分析

sub_00403c3c(eax, edx, ss:[ebp-0x0c])

这样分析完全没有任何问题,函数内部是通过edx来连接几个字符串!

反汇编代码反应了代码的本质,一般也是逻辑最简洁的,感觉有疑问,直接跟进去,多运行几下就可以了

反汇编函数调用

被调用函数能够影响调用函数的方式主要有:

  1. lea 直接将地址(相当于指针)直接当参数传递给被调用函数
  2. ds数据段构成的全局变量
  3. 返回的eax

主要的几种调用方式stdcall,fastcall等都是子函数维持栈的平衡,所以子函数在返回前一般会保持栈平衡

在crackme160 004的逆向中遇到的函数00403c3c这样的“递归”函数(非真正意义上的递归函数),在最后也会保持栈平衡,但是在ida中如果想得到伪代码,还是需要更改汇编代码才行

根据寄存器的值设置条件断点

so上关于寄存器值设置断点

如果需要根据函数的参数设置断点,应该是记录esp的值,而不是ebp的值

逆向工具使用

IDA

  1. *查看结构信息
  2. N重命名标号

OD

####特定模块搜索

Ctrl+E->选中模块->右键->Follow entry,进入需要查找的模块,然后右键->中文搜索引擎->智能搜索

c++逆向重点代码

求字符串长度(release)

mov edi, [ebp+n] ; 获取参数数据
mov ecx, 0xffffffffh ; ecx=-1
xor eax, eax
repne scasb
not ecx
dec ecx

难记的汇编指令

  1. leave相当于

    mov esp, ebp
    pop ebp
    
  2. repe/repne 相等则重复/不想等重复,一般连同scasb使用

    repne scasb
    

    scasb 比较al和di,相当于cmp al, di

  3. Xor eax, ebx 用于判断两数是否相等

  4. cdq 将一个32位有符合数扩展为64位有符号数,数据能表示的数不变,具体是这样实现的,比如eax=fffffffb(值为-5),然后cdq把eax的最高位bit,也就是二进制1,全部复制到edx的每一个bit位,EDX 变成 FFFFFFFF,这时eax与edx连起来就是一个64位数,FFFFFFFF FFFFFFFB ,它是一个 64 bit 的大型数字,数值依旧是 -5。

  5. cmovx x指的是比较形式

IDA pro

快捷键

Ctl+l 搜索所有的函数名称
g 跳转到特定位置
Alt+t 搜索特定的汇编指令
Shift+F12 列出所有字符串

点windows API函数

  1. SetupDiGetDeviceRegistryProperty 查看设备属性(vmware, vbox...)

    BOOL SetupDiGetDeviceRegistryProperty(
      _In_      HDEVINFO         DeviceInfoSet,
      _In_      PSP_DEVINFO_DATA DeviceInfoData,
      _In_      DWORD            Property,
      _Out_opt_ PDWORD           PropertyRegDataType,
      _Out_opt_ PBYTE            PropertyBuffer, //指明设备名称 vmware, vbox, qemu
      _In_      DWORD            PropertyBufferSize,
      _Out_opt_ PDWORD           RequiredSize
    );
    

领悟

有很多情况下,遇到问题没法解决,想了很久也没有任何的思路,可以跳出这个问题,看别的,之后再来看,没准也就可以理解了,想通了。

主要有这种方式

([UNICODE[esp+10]] !="BAR") && ([UNICODE[esp]] =="FOO")
[[STRING[esp+8]] =="FOO"] && [[STRING[esp+4]] !="BAR"] 

并且设置在条件满足是记录相对应的数据

proc near
push esi
mov esi, ecx
mov dword ptr [esi], offset off_40C0D0
mov dword ptr [esi+4], 0BBh
call sub_401EB0
add esp, 18h
pop esi
retn
endp

汇编栈的使用

局部数组变量的存储是连续的,但是如果是通过参数传递进来的,并且这些数据使用的是栈进行临时存储,那么从局部的函数看的话,这些数据是不连续的,局部的函数栈帧中只是存储了这些数组的首字节地址

 -----
|     | ===> 字符串首地址 这种字符串是通过参数传递进来的
 -----
|     | ===> 其他数据
 ----- 

这种是局部数组

 -----
|     | ===> 字符串后四个字符
 -----
|     | ===> 字符串前四个字符
 ----- 

问题

函数的入口:

mainctrstartup

_tmainctrstartup

windows字符串类型

type Meaning in MBCS builds Meaning in Unicode builds
WCHAR wchar_t wchar_t
LPSTR zero-terminated string of char (char*) zero-terminated string of char (char*)
LPCSTR constant zero-terminated string of char (const char*) constant zero-terminated string of char (const char*)
LPWSTR zero-terminated Unicode string (wchar_t*) zero-terminated Unicode string (wchar_t*)
LPCWSTR constant zero-terminated Unicode string (const wchar_t*) constant zero-terminated Unicode string (const wchar_t*)
TCHAR char wchar_t
LPTSTR zero-terminated string of TCHAR (TCHAR*) zero-terminated string of TCHAR (TCHAR*)
LPCTSTR constant zero-terminated string of TCHAR (const TCHAR*) constant zero-terminated string of TCHAR (const TCHAR*)

##重点消息数值

 WM_CLOSE(16)
 WM_INITDIALOG(272)
 WM_COMMAND(273)

汇编串操作

移动串指令: **MOVSB**、**MOVSW**、**MOVSD** ;从 ESI -> EDI; 执行后, ESI 与 EDI 的地址移动相应的单位****

比较串指令: **CMPSB**、**CMPSW**、**CMPSD** ;比较 ESI、EDI; 执行后, ESI 与 EDI 的地址移动相应的单位****

扫描串指令: **SCASB**、**SCASW**、**SCASD** ;依据 AL/AX/EAX 中的数据扫描 EDI 指向的数据, 执行后 EDI 自动变化****

储存串指令: **STOSB**、**STOSW**、**STOSD** ;将 AL/AX/EAX 中的数据储存到 EDI 给出的地址, 执行后 EDI 自动变化****

载入串指令: **LODSB**、**LODSW**、**LODSD** ;将 ESI 指向的数据载入到 AL/AX/EAX, 执行后 ESI 自动变化****

其中的 B、W、D 分别指 ****Byte、********Word、********DWord, 表示每次操作的数据的大小单位.

上述指令可以有重复前缀:

**REP**             ECX**** > 0**** 时

**REPE** (或 **REPZ**)  ECX**** > 0**** 且 ZF=1**** 时

**REPNE**(或 **REPNZ**) ECX**** > 0**** 且 ZF=0**** 时

;重复前缀可以自动按单位(1、2、4)递减 ECX****

GetLastError()从 TEB 的 LastErrorValue 处得到错误码。

##调试子进程 建议用windbg

用命令
.childdbg 1

经典猜题思路

  1. 作者是懂汇编的,且程序是用汇编写的。因为3连call只能是人为设计的

  2. write up中有意思的猜想:

    1. 作者修改了_scanf的代码,获取input数据,并且以此修改key处的数据。
    2. 利用缓冲区溢出漏洞,retn跳转到其他地方,在那里输入的值被获取,并且处理,然后影响注册的成败

    我其实一开始以为的是第一种可能性,就差逆出scanf算法看他有什么猫腻了。。结果想想其实不是,因为IDA识别出了他是_scanf这个函数。如果作者修改了这个函数,IDA可能识别不出来这是个_scanf。

疑难点(知识盲点)

cdecl栈平衡

cdecl的栈平衡是通过母函数实现的,这句话主要的含义是这样的

push eax
push ecx
call xx.40xxxx
add esp, 0x8

并不是说其内部的栈是不平衡的,而是说参数的入栈是需要通过母函数来进行平衡的!

参考

反调试详解

xinali avatar Feb 27 '18 11:02 xinali