programing

박리된 애플리케이션의 주요 기능을 분해하는 방법은 무엇입니까?

jooyons 2023. 7. 24. 22:26
반응형

박리된 애플리케이션의 주요 기능을 분해하는 방법은 무엇입니까?

제가 아래 애플리케이션을 컴파일하여 해당 심볼을 제거했다고 가정해 보겠습니다.

#include <stdio.h>

int main()
{
    printf("Hello\n");
}

빌드 절차:

gcc -o hello hello.c
strip --strip-unneeded hello

애플리케이션이 벗겨지지 않았다면 메인 기능을 분해하는 것이 쉬울 것입니다.하지만, 저는 제거된 애플리케이션의 주요 기능을 분해하는 방법을 전혀 모릅니다.

(gdb) disas main
No symbol table is loaded.  Use the "file" command.

(gdb) info line main
Function "main" not defined.

제가 어떻게 할 수 있을까요?그게 가능할까요?

참고: 이 작업은 GDB에서만 수행해야 합니다.objdump는 잊어버려요.내가 코드에 접근할 수 없다고 가정합니다.

단계별로 예를 들어주시면 감사하겠습니다.

좋아요, 여기 제 이전 답변의 큰 판이 있습니다.이제 방법을 찾은 것 같아요.

당신(아직 :)은 다음과 같은 구체적인 문제를 가지고 있습니다.

(gdb) disas main
No symbol table is loaded.  Use the "file" command.

이제 코드를 컴파일하면 (제가 추가했습니다.return 0마지막에), 당신은 이해할 것입니다.gcc -S:

    pushq   %rbp
    movq    %rsp, %rbp
    movl    $.LC0, %edi
    call    puts
    movl    $0, %eax
    leave
    ret

이제 바이너리가 다음과 같은 정보를 제공하는 것을 확인할 수 있습니다.

줄무늬:

(gdb) info files
Symbols from "/home/beco/Documents/fontes/cpp/teste/stackoverflow/distrip".
Local exec file:
    `/home/beco/Documents/fontes/cpp/teste/stackoverflow/distrip', file type elf64-x86-64.
    Entry point: 0x400440
    0x0000000000400238 - 0x0000000000400254 is .interp
    ...
    0x00000000004003a8 - 0x00000000004003c0 is .rela.dyn
    0x00000000004003c0 - 0x00000000004003f0 is .rela.plt
    0x00000000004003f0 - 0x0000000000400408 is .init
    0x0000000000400408 - 0x0000000000400438 is .plt
    0x0000000000400440 - 0x0000000000400618 is .text
    ...
    0x0000000000601010 - 0x0000000000601020 is .data
    0x0000000000601020 - 0x0000000000601030 is .bss

은 여서가장중항목은입니다..text코드의 어셈블리 시작에 대한 일반적인 이름이며, 아래의 메인 설명을 통해 크기로 보아 메인이 포함되어 있음을 알 수 있습니다.이를 분해하면 __libc_start_main에 대한 호출이 표시됩니다.가장 중요한 것은 실제 코드인 양호한 진입점을 분해하는 것입니다(DATA를 CODE로 변경하는 것은 오해의 소지가 없습니다).

disas 0x0000000000400440,0x0000000000400618
Dump of assembler code from 0x400440 to 0x400618:
   0x0000000000400440:  xor    %ebp,%ebp
   0x0000000000400442:  mov    %rdx,%r9
   0x0000000000400445:  pop    %rsi
   0x0000000000400446:  mov    %rsp,%rdx
   0x0000000000400449:  and    $0xfffffffffffffff0,%rsp
   0x000000000040044d:  push   %rax
   0x000000000040044e:  push   %rsp
   0x000000000040044f:  mov    $0x400540,%r8
   0x0000000000400456:  mov    $0x400550,%rcx
   0x000000000040045d:  mov    $0x400524,%rdi
   0x0000000000400464:  callq  0x400428 <__libc_start_main@plt>
   0x0000000000400469:  hlt
   ...

   0x000000000040046c:  sub    $0x8,%rsp
   ...
   0x0000000000400482:  retq   
   0x0000000000400483:  nop
   ...
   0x0000000000400490:  push   %rbp
   ..
   0x00000000004004f2:  leaveq 
   0x00000000004004f3:  retq   
   0x00000000004004f4:  data32 data32 nopw %cs:0x0(%rax,%rax,1)
   ...
   0x000000000040051d:  leaveq 
   0x000000000040051e:  jmpq   *%rax
   ...
   0x0000000000400520:  leaveq 
   0x0000000000400521:  retq   
   0x0000000000400522:  nop
   0x0000000000400523:  nop
   0x0000000000400524:  push   %rbp
   0x0000000000400525:  mov    %rsp,%rbp
   0x0000000000400528:  mov    $0x40062c,%edi
   0x000000000040052d:  callq  0x400418 <puts@plt>
   0x0000000000400532:  mov    $0x0,%eax
   0x0000000000400537:  leaveq 
   0x0000000000400538:  retq   

__libc_start_main에 대한 호출은 첫 번째 인수로 main()에 대한 포인터를 가져옵니다.따라서 호출 직전 스택의 마지막 인수가 기본() 주소입니다.

   0x000000000040045d:  mov    $0x400524,%rdi
   0x0000000000400464:  callq  0x400428 <__libc_start_main@plt>

여기 0x400524가 있습니다(이미 알고 있음).이제 중단점을 설정하고 다음을 시도합니다.

(gdb) break *0x400524
Breakpoint 1 at 0x400524
(gdb) run
Starting program: /home/beco/Documents/fontes/cpp/teste/stackoverflow/disassembly/d2 

Breakpoint 1, 0x0000000000400524 in main ()
(gdb) n
Single stepping until exit from function main, 
which has no line number information.
hello 1
__libc_start_main (main=<value optimized out>, argc=<value optimized out>, ubp_av=<value optimized out>, 
    init=<value optimized out>, fini=<value optimized out>, rtld_fini=<value optimized out>, 
    stack_end=0x7fffffffdc38) at libc-start.c:258
258 libc-start.c: No such file or directory.
    in libc-start.c
(gdb) n

Program exited normally.
(gdb) 

이제 다음을 사용하여 분해할 수 있습니다.

(gdb) disas 0x0000000000400524,0x0000000000400600
Dump of assembler code from 0x400524 to 0x400600:
   0x0000000000400524:  push   %rbp
   0x0000000000400525:  mov    %rsp,%rbp
   0x0000000000400528:  sub    $0x10,%rsp
   0x000000000040052c:  movl   $0x1,-0x4(%rbp)
   0x0000000000400533:  mov    $0x40064c,%eax
   0x0000000000400538:  mov    -0x4(%rbp),%edx
   0x000000000040053b:  mov    %edx,%esi
   0x000000000040053d:  mov    %rax,%rdi
   0x0000000000400540:  mov    $0x0,%eax
   0x0000000000400545:  callq  0x400418 <printf@plt>
   0x000000000040054a:  mov    $0x0,%eax
   0x000000000040054f:  leaveq 
   0x0000000000400550:  retq   
   0x0000000000400551:  nop
   0x0000000000400552:  nop
   0x0000000000400553:  nop
   0x0000000000400554:  nop
   0x0000000000400555:  nop
   ...

이것이 기본적으로 해결책입니다.

BTW, 이것은 작동하는지 보기 위해 다른 코드입니다.그것이 위의 어셈블리가 조금 다른 이유입니다.위의 코드는 다음 c 파일에서 가져온 것입니다.

#include <stdio.h>

int main(void)
{
    int i=1;
    printf("hello %d\n", i);
    return 0;
}

하지만!


이 방법이 작동하지 않는 경우에도 몇 가지 힌트를 얻을 수 있습니다.

이제부터 모든 기능의 시작 부분에 중단점을 설정해야 합니다.은 들은바앞있다습니에 .ret또는leave첫 번째 진입점은 다음과 같습니다..text시작이지만.이것은 조립 시작이지만 메인은 아닙니다.

문제는 중단점이 항상 프로그램을 실행할 수 있는 것은 아니라는 것입니다.아▁the에 있는 처럼..text:

(gdb) break *0x0000000000400440
Breakpoint 2 at 0x400440
(gdb) run
Starting program: /home/beco/Documents/fontes/cpp/teste/stackoverflow/disassembly/d2 

Breakpoint 2, 0x0000000000400440 in _start ()
(gdb) n
Single stepping until exit from function _start, 
which has no line number information.
0x0000000000400428 in __libc_start_main@plt ()
(gdb) n
Single stepping until exit from function __libc_start_main@plt, 
which has no line number information.
0x0000000000400408 in ?? ()
(gdb) n
Cannot find bounds of current function

따라서 여러분은 길을 찾을 때까지 계속해서 시도하고 중단점을 다음과 같이 설정해야 합니다.

0x400440
0x40046c
0x400490
0x4004f4
0x40051e
0x400524

다른 답변에서는 다음과 같은 정보를 유지해야 합니다.

스트라이프되지 않은 버전의 파일에서 다음을 확인할 수 있습니다.

(gdb) disas main
Dump of assembler code for function main:
   0x0000000000400524 <+0>: push   %rbp
   0x0000000000400525 <+1>: mov    %rsp,%rbp
   0x0000000000400528 <+4>: mov    $0x40062c,%edi
   0x000000000040052d <+9>: callq  0x400418 <puts@plt>
   0x0000000000400532 <+14>:    mov    $0x0,%eax
   0x0000000000400537 <+19>:    leaveq 
   0x0000000000400538 <+20>:    retq   
End of assembler dump.

이제 우리는 메인이0x0000000000400524,0x0000000000400539동일한 오프셋을 사용하여 스트라이프 이진수를 살펴보면 동일한 결과를 얻을 수 있습니다.

(gdb) disas 0x0000000000400524,0x0000000000400539
Dump of assembler code from 0x400524 to 0x400539:
   0x0000000000400524:  push   %rbp
   0x0000000000400525:  mov    %rsp,%rbp
   0x0000000000400528:  mov    $0x40062c,%edi
   0x000000000040052d:  callq  0x400418 <puts@plt>
   0x0000000000400532:  mov    $0x0,%eax
   0x0000000000400537:  leaveq 
   0x0000000000400538:  retq   
End of assembler dump.

따라서 (기호가 있는 다른 코드를 사용하는 것과 같이) 메인이 시작되는 부분에 대한 팁을 얻을 수 없는 한, 다른 방법은 첫 번째 어셈블리 지침에 대한 정보를 얻을 수 있으므로 특정 위치에서 분해하고 일치하는지 확인할 수 있습니다.코드에 대한 액세스 권한이 전혀 없는 경우에도 ELF 정의를 읽어 코드에 표시할 섹션 수를 파악하고 계산된 주소를 사용할 수 있습니다.그래도 코드의 섹션에 대한 정보가 필요합니다!

그건 힘든 일이야, 친구!행운을 빕니다.

베코

하는게 어때요?info files섹션 목록(주소 포함)을 가져오고 거기서 이동합니까?

예:

gdb) info files

Symbols from "/home/bob/tmp/t".
Local exec file:
`/home/bob/tmp/t', file type elf64-x86-64.
Entry point: 0x400490
0x0000000000400270 - 0x000000000040028c is .interp
0x000000000040028c - 0x00000000004002ac is .note.ABI-tag
    ....

0x0000000000400448 - 0x0000000000400460 is .init
    ....

디스어셈블.init:

(gdb) disas 0x0000000000400448,0x0000000000400460
Dump of assembler code from 0x400448 to 0x400460:
   0x0000000000400448:  sub    $0x8,%rsp
   0x000000000040044c:  callq  0x4004bc
   0x0000000000400451:  callq  0x400550
   0x0000000000400456:  callq  0x400650
   0x000000000040045b:  add    $0x8,%rsp
   0x000000000040045f:  retq   

그럼 나머지는 분해하세요.

만약 제가 당신이라면, 그리고 당신의 실행 파일이 만들어진 것과 같은 GCC 버전을 가지고 있다면, 저는 더미 비스트립 실행 파일에서 호출되는 함수들의 순서를 조사할 것입니다.통화 순서는 아마도 대부분의 일반적인 경우에 유사하므로 시작 순서를 끝까지 가는 데 도움이 될 수 있습니다.main그에 비해그러나 최적화는 아마도 방해가 될 것입니다.

만약 당신의 바이너리가 벗겨지고 최적화된다면,main바이너리에 "프로시저"로 존재하지 않을 수 있습니다. 이러한 유형의 프로시저보다 훨씬 더 나은 방법은 없을 수 있습니다.

파라딘 프로젝트의 언스트립이라는 새로운 무료 도구가 있습니다(완전 공개:저는 이 프로젝트에서 당신의 프로그램 바이너리를 다시 작성하고 심볼 정보를 추가하며, 당신을 위해 벗겨진 엘프 바이너리의 모든 (또는 거의 모든) 기능을 매우 정확하게 복구할 것입니다.주 기능을 "주"로 식별하지는 않지만 주 기능을 찾을 수 있으며, 위에서 이미 언급한 휴리스틱을 적용하여 어떤 기능이 주 기능인지 파악할 수 있습니다.

http://www.paradyn.org/html/tools/unstrip.html

죄송합니다만 이것은 agdb 전용 솔루션이 아닙니다.

IIRC,x/i <location>당신의 친구입니다.물론 어떤 위치에서 분해할 것인지 스스로 결정해야 합니다.

언급URL : https://stackoverflow.com/questions/5475790/how-to-disassemble-the-main-function-of-a-stripped-application

반응형