re learning (34) Attack and defense world-csaw2013reversing2 (modify assembly order)

Reference article:

re study notes (27) Attack and defense world - re-csaw2013reversing2_Forgo7ten's blog - CSDN blog
Attack and defense world reverse introductory questions csaw2013reversing2_Muyi·Lin's blog - CSDN blog

Three approaches

1. ida static analysis modification instructions

Main function decompiled code

Since the code after running is garbled, it can be guessed that the function that generates the flag is not executed, so it needs to jump to the function that generates the flag. However, the previous interrupt function cannot be executed and needs to be nop out, and the subsequent function to exit the program cannot be executed and needs to be jumped. Continue execution to the pop-up function. (The modified path and file name should not contain Chinese characters. I ran into a trap when I used ida to modify it. You can give it a try)

1. How to modify the code with ida:

1. Hover the mouse on the assembly code to be modified, and then click Edit > Patch program > Assemble (Chinese: Edit > Patch > Assemble)

2. After the modification is completed: Edit > Patch program > Apply paths to input file > OK (Chinese: Edit > Patch program > Apply paths to input file > OK)

2. Explanation of IDA graphic view:

Graphical view breaks a function into a number of basic blocks to vividly display the flow of control of the function from one block to another

A basic block is a maximum instruction sequence that is executed from beginning to end without branches.

The first instruction in a basic block is usually the target of a branch instruction, and the last instruction is usually a branch instruction.

IDA uses different colored arrows to distinguish various types of flow between function blocks

Normal flow (also called ordinary flow) means that instructions are executed continuously by default, jump flow means that the current instruction jumps to (or may jump to) a non-continuous position, and call flow means that the current instruction will call a subroutine.

Depending on the test conditions, a basic block that terminates at a conditional jump may generate two flows: the arrow on the Yes edge (yes, the branch is executed) defaults to green, and the arrow on the No edge (no, the branch is not executed) defaults to red

Basic blocks with only one successor block utilize a normal edge (blue by default) pointing to the next block to be executed.

In graphics mode, IDA displays one function at a time

Users who use a wheel mouse can use "CTRL+mouse wheel" to adjust the size of the graphic.

Modify the previous assembly code

Modified assembly code

 After the modification is completed, run the file directly to get the flag

2. ollydbg dynamic debugging, nop method

After importing the file into ollydbg, right-click > Chinese Search Engine > Smart Search and find Flag

 After double-clicking, go up to find the IsDebuggerPresent function, click on this assembly, set a breakpoint, and reload

F8 twice and found a jump. According to the previous analysis of ida, this should be the judgment of the if statement. The skipped middle part is the function that generates the flag, so we nop this jump. 

 Continue F8 execution and execute to int 3. This is an interrupt statement, so it is also nop.

 After F8 executes the function that generates the flag, there is a big jump at the end to the function that exits the program.

 

So we nop this jump, continue F8, and after executing a MessageBoxA (pop-up box) function, we find that the program is in the Running state at this time, and a box with nothing pops up. In fact, this is another pop-up box function. The pop-up function that actually outputs the flag is the latter one. In the assembly diagram after our previous modification of ida, we can also find that there is indeed a pop-up function that has not been called, so we can change the nop jump to Jump to the pop-up function below, but since it is said to be a nop method, just nop to the end.

 After clicking abort, I found that another jump was to be executed, skipping our real pop-up function.

Nop this jump, then F8, you can see the flag

3. Analyze code and write scripts

main function code

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, lpMem);
    ExitProcess(0xFFFFFFFF);
  }
  MessageBoxA(0, lpMem + 1, "Flag", 2u);
  HeapFree(hHeap, 0, lpMem);
  HeapDestroy(hHeap);
  ExitProcess(0);
}

 The two parameters of the key function sub_401000 are not used later in v3. Look up for the assignment statement of lpMem, memcpy_s, and give it the value of the unk_409B10 address. Double-click to view it.

 Enter the sub_401000 function, the code

unsigned int __fastcall sub_401000(int a1, int a2)
{
  int v2; // esi
  unsigned int v3; // eax
  unsigned int v4; // ecx
  unsigned int result; // eax
 
  v2 = dword_409B38;
  v3 = a2 + 1 + strlen((const char *)(a2 + 1)) + 1;
  v4 = 0;
  result = ((v3 - (a2 + 2)) >> 2) + 1;
  if ( result )
  {
    do
      *(_DWORD *)(a2 + 4 * v4++) ^= v2;
    while ( v4 < result );
  }
  return result;
}

 a2 is lpMem. It is found that the following XOR statement contains v2. Look up for the assignment statement of v2 and find v2 = dword_409B38. Double-click dword_409B38 to find the content.

Here are four bytes displayed, and due to little-endian storage, the order is reversed. We can convert it into one byte to view.

wp:
 

x=[0xbb,0xaa,0xcc,0xdd]
y=[0xBB,0xCC,0xA0,0xBC,0xDC,0xD1,0xBE,0xB8,0xCD,0xCF,0xBE,0xAE,0xD2,0xC4,0xAB,0x82,0xD2,0xD9,0x93,0xB3,0xD4,0xDE,0x93,0xA9,0xD3,0xCB,0xB8,0x82,0xD3,0xCB,0xBE,0xB9,0x9A,0xD7,0xCC,0xDD]
i=0
z=[]
while i<len(y):
    t=chr(y[i]^x[i%4])
    z.append(t)
    i+=1
print(z)
print(''.join(z))

['\x00', 'f', 'l', 'a', 'g', '{', 'r', 'e', 'v', 'e', 'r', 's', 'i', 'n', 'g', '_', 'i', 's', '_', 'n', 'o', 't', '_', 't', 'h', 'a', 't', '_', 'h', 'a', 'r', 'd', '!', '}', '\x00', '\x00']
flag{reversing_is_not_that_hard!}

Here you can know why calling the first pop-up window will output blank, because the first pop-up window function is output directly from the first character, but the first character is decoded to '\0', which is directly truncated, so The output will be blank, and the second pop-up window will be output starting from lpMem+1.

Guess you like

Origin blog.csdn.net/m0_66039322/article/details/132343812