yon11b

대구·경북 사이버공격 방어대회 2026 예선전 reversing 문제(UNSAT) 본문

보안/리버싱

대구·경북 사이버공격 방어대회 2026 예선전 reversing 문제(UNSAT)

yon11b 2026. 6. 15. 14:22
반응형

 
문제파일: chall

unsat
- 어떤 논리식의 변수에 어떤 값을 대입해도 결코 참(True)이 될 수 없는 상태
- 제약 조건들이 서로 모순되어 해결책이 전혀 없는 상태
 
전체 흐름 미리보기

 

UNSAT 인 부분

가장 기본인 main부터 보자.
 

main 함수

.text:000000000040140B        push    rbx
.text:000000000040140C        sub     rsp, 20h
.text:0000000000401410        cmp     edi, 2           ; edi = argc
.text:0000000000401413        jnz     short loc_401427 ; 인자 1개 아니면 wrong 출력
.text:0000000000401415        mov     rbx, [rsi+8]     ; rbx = rsi+8 = argv[1]
.text:0000000000401419        mov     rdi, rbx         ; strlen의 첫번째 인자로 rdi 사용
.text:000000000040141C        call    _strlen          ; strlen의 결과는 rax에 담아짐
.text:0000000000401421        cmp     rax, 40h         ; 0x40 == 64byte이면,
.text:0000000000401425        jz      short loc_401446 ; DH{flag} 출력하는 함수로 ㄱㄱ
.text:0000000000401427        "Wrong 출력"

rax: strlen의 리턴값
조건1: 일단 인자의 길이가 64byte여야 한다.
 
loc_401446 일부: DH{flag} 출력하는 로직이 담긴 함수

.text:0000000000401458        call    sub_4012D3
.text:000000000040145D        test    eax, eax         ; eax & eax
.text:000000000040145F        jz      short loc_401427 ; zero면 Wrong 출력
.text:0000000000401461        mov     rsi, rbx
.text:0000000000401464        mov     edi, offset aDhS ; "DH{%s}\n"

 
sub_4012D3의 결과가 0이 아니어야 DH{flag}를 출력한다.
 

sub_4012D3 함수

sub_4012D3 함수 안에도 또 다른 함수가 여러 개가 있다.

  • sub_40121F
  • sub_40125A
  • sub_4013F8
  • sub_4011ED
  • sub_4012BD

디컴파일
sub_4012BD() 리턴값을 리턴한다. -> sub_4012BD() 리턴값이 0이 아니어야 한다.

uint64_t sub_4012D3(uint8_t *input)
{
    uint64_t a;
    uint64_t b;
    uint64_t c;
    uint64_t d;

    uint64_t x;
    uint64_t y;
    uint64_t z;
    uint64_t t;
    uint64_t final;

    a = sub_4011F6(input + 0x00);
    b = sub_4011F6(input + 0x08);
    c = sub_4011F6(input + 0x10);
    d = sub_4011F6(input + 0x18);

    x = sub_40121F(qword_403480 ^ a);

    y = sub_4011ED(d, 0x11);
    y = sub_40121F(y ^ c);

    t = sub_40125A(input);

    if (((x + y) & 0xffff) != 0x7A41)
        return 0;

    z = sub_40121F(b + qword_403478);

    if (((t ^ z) & 0xfffff) != 0x5D391)
        return 0;

    if (((qword_403470 + a + sub_4011ED(c, 9)) & 0xffffff) != 0x6E5A21)
        return 0;

    final = t;
    final ^= x;
    final ^= qword_403470;
    final ^= sub_4011ED(z, 7);
    final ^= sub_4011ED(y, 0x13);

    return sub_4012BD(final);
}

 

sub_4012BD 함수

.text:00000000004012BD
.text:00000000004012BD sub_4012BD      proc near         ; CODE XREF: sub_4012D3+120↓p
.text:00000000004012BD ; __unwind {
.text:00000000004012BD      imul    rdi, rdi
.text:00000000004012C1      add     rdi, 7
.text:00000000004012C5      and     edi, 0Fh
.text:00000000004012C8      cmp     rdi, 3
.text:00000000004012CC      setz    al
.text:00000000004012CF      movzx   eax, al
.text:00000000004012D2      retn
.text:00000000004012D2 ; } // starts at 4012BD
.text:00000000004012D2 sub_4012BD      endp
.text:00000000004012D2

 
위 어셈을 디컴파일하면 다음과 같다.

check = ((x * x + 7) & 0xf) == 3;
check = ((x * x + 7) mod 16) == 3;

 
그런데 어떤 정수든 x*x mod 16 값은 항상 0, 1, 4, 9뿐이다.
그러면 (x*x + 7) mod 16은 7, 8, 11, 0 뿐이다.
즉 3이 절대 나올 수 없다. → 항상 0을 리턴함! → unsat !!

ghidra로 본 모습(오른쪽에 디컴파일된 c코드로 나와있다)

 
참고) x*x mod 16  = 0,1,4,9 에 대한 수학적 증명
x가 짝수이면 x = 2k

x² = 4k²
 

그래서 4의 배수만 나온다.

0 또는 4
 

x가 홀수이면 x = 2k+1

x² = 4k(k+1) + 1
 

여기서 k(k+1)은 연속된 두 수의 곱이라 항상 짝수다.
그래서 4k(k+1)은 8의 배수이고,

1 또는 9
 

결론:

x*x mod 16은 아무 값이나 안 나오고
항상 0, 1, 4, 9 중 하나만 나온다.
 

이렇게 제곱해서 나올 수 있는 나머지를 mod 16에서의 quadratic residue, 즉 제곱잉여라고 한다.
 

일단 찾은 조건 이용해서 실행은 해보자.

조건1: 일단 인자의 길이가 64byte여야 한다.

 
역시나 Wrong이 출력된다.

실행하면서 찾아보자

흠 그럼 어디를 봐야할까… 일단 실행 시작 위치가 main은 맞나?

 

readelf -h chall | grep Entry

 

main이 아니라 start(0x401070)가 시작 주소였다!
0x401070: start 함수 주소
 

 
여기서 중요한 건 `__libc_start_main`이 아니라 `__libc_start_main_ptr`을 호출한다는 점이다.

`call cs:__libc_start_main_ptr`은 `__libc_start_main_ptr` 주소로 직접 점프하는 것이 아니라,
`__libc_start_main_ptr`에 들어있는 값을 읽어서 그 주소로 call하는 명령이다.


IDA에서 `__libc_start_main_ptr`을 따라가면 `0x403428`로 이동한다.
해당 주소는 `.got` 섹션에 있으며, 다음과 같이 표시된다.

.got:0000000000403428 __libc_start_main_ptr dq offset __libc_start_main

 
의미
__libc_start_main_ptr이라는 위치에
__libc_start_main의 주소를 8바이트 크기로 저장해둔다

즉 `0x403428`에는 `__libc_start_main` 함수의 주소가 저장되어 있다.

정리하면 다음과 같다.
__libc_start_main        = 실제 외부 함수
__libc_start_main_ptr  = 그 함수 주소를 담고 있는 GOT 엔트리(실제 외부 함수를 불러오는 역할)


GOT란?
Global Offset Table
프로그램이 외부 함수 주소를 저장해두는 표
예를 들어 printf("hello");를 쓰면, printf 함수는 내 바이너리 안에 있는 게 아니라 libc.so 안에 있다. 그런데 libc는 실행할 때마다 주소가 바뀔 수 있다. 그래서 프로그램은 바로 printf 주소를 하드코딩하지 않고, GOT에 주소를 저장해두고 그걸 참조한다.
 
예) GOT에 저장된 printf의 실제 위치

printf@GOT = 0x7ffff7e12345

 
코드에서 printf를 불러올 때

call [printf@GOT]

중요: __libc_start_main의 실제 런타임 주소”는 실행 동적 링커가 relocation 하면서 채운다.

더보기

왜 main()함수의 주소는 실행해도 안 바뀌는데, libc_start_main의 주소는 실행하면 바뀌는가?

 

답변: 원래 실행하면 메모리에 올라가면서 주소가 바뀔 수 있다.
근데 항상 모든 주소가 바뀌는 건 아님.

이 경우 main 주소가 똑같이 보이는 이유는 보통 PIE가 꺼져 있기 때문이다.

PIE: No PIE
 

이면 실행파일의 .text 영역이 거의 고정 주소에 로드된다.

예를 들어 IDA에서:

main = 0x401407
start = 0x401070
 

이면 실행 중에도 보통 그대로:

main = 0x401407
start = 0x401070
 

로 보인다.

반대로 libc는 공유 라이브러리라서 ASLR 영향을 받는다.

실행파일 코드 영역 → No PIE면 고정
libc 영역          → ASLR 때문에 매번 바뀜
stack/heap         → ASLR 때문에 바뀜
 

그래서 이 상황은 이렇게 보면 된다.

0x401070 start
0x401407 main
0x403428 GOT

얘네는 실행파일 내부 주소라서 No PIE면 고정
 

하지만:

__libc_start_main 실제 주소
printf 실제 주소
strlen 실제 주소
 

얘네는 libc 안에 있으므로 실행할 때마다 바뀔 수 있다.

__libc_start_main이란?

__libc_start_main은 리눅스 glibc에서 프로그램의 진짜 main()을 호출하기 전에 실행되는 시작 관리자 함수다.
보통 흐름은 이렇게 된다.

커널이 프로그램 실행
→ ELF entry point 실행
→ _start
→ __libc_start_main(main, argc, argv, init, fini, ...)
→ main(argc, argv, envp)

프로그램을 실행하면 libc_start_main이 메모리에 올라오게 되고, 그 주소에서 main을 첫번째 인자로 가지게 되는 것이다. 
 
gdb로 실행하면서 살펴보자.
 

GDB 실행

gdb ./chall

 
일단 인자 aa로 아무거나 줌

set args aa

 
start 함수에 bp 걸어놓음

b *0x401070

 
실행시킴

run

 
bp 걸어놓은 곳 어셈 10줄 살펴보기 (그냥)

x/10i $rip

 

그렇게 계속 ni로 한줄씩 실행하다 갑자기 40108f에서 404000으로 점프했다?

0x40108F는 start 함수에서 `__libc_start_main_ptr` GOT 엔트리에 저장된 주소를 읽고, 그 주소를 `__libc_start_main`의 호출 대상처럼 간접 call하는 명령어 위치이다.
=> 쉽게 말하면 __libc_start_main의 실제 주소로 이동해야 한다.

그럼 404000이 런타임시 할당된 libc_start_main의 주소인가? 라는 생각을 할 수 있다.
 
그런데 libc_start_main은 라이브러리라서 보통 주소가 0x7f... 로 나온다.  0x404000으로 나온 건 이상하다는 것이다.
일반적인 경우

__libc_start_main@GOT → 진짜 __libc_start_main 주소 (0x7ffff7xxxxxx)

 
지금 상황

__libc_start_main@GOT → 0x404000

 
직접 __libc_start_main_ptr GOT 엔트리를 확인해봐도 여기에 저장된 주소가 0x404000으로 나온다.

0x404000은 실행 파일 영역이다.
info proc mappings 명령어로 확인

 
흐름 정리

  1. _start에서 __libc_start_main을 호출하는 부분 확인
  2. 직접 call이 아니라 GOT를 통한 indirect call임
  3. 실행 중 값이 0x404000으로 되어 있음
  4. 0x404000을 까보니 숨겨진 검증 코드 존재(밑에서 확인할 거임)
  5. 따라서 GOT를 이용해 실행 흐름을 숨긴 문제라고 판단

실행 시에 어떻게 [0x403428] = 0x404000이 되는지 궁금하다!

readelf -r ./chall
relocation 테이블 보는 명령어
*relocation 테이블: ELF 파일 안에 들어있는 주소 수정 지시서(실행 전에 이 위치에 이 값을 계산해서 써라)

이 부분을 보자.
Offset      Type                  Addend / Symbol
403428      R_X86_64_GLOB_DAT      __libc_start_main
404148      R_X86_64_RELATIVE      403428
404158      R_X86_64_RELATIVE      404000
 

첫 번째 줄:

403428  R_X86_64_GLOB_DAT  __libc_start_main
 

의미: ld-linux가 0x403428에 실제 __libc_start_main 주소를 써준다.
 
즉 정상적으로는 [0x403428] = libc 안의 __libc_start_main 실제 주소가 되어야 한다.
 
그런데 아래 두 줄이 수상하다.

404148  R_X86_64_RELATIVE  403428
404158  R_X86_64_RELATIVE  404000
 

 
R_X86_64_RELATIVE의 공식은:

[Offset] = base + Addend
 

 
No PIE 기준으로 base를 0처럼 보면:

[0x404148] = 0x403428
[0x404158] = 0x404000
 

 
즉 실행 중 relocation 처리 후에는 메모리가 이렇게 된다.

gdb로 확인
IDA에서도 확인할 수 있다.
0x404148: 0x403428
0x404150: 0x8
0x404158: 0x404000
 

 
이 구조는 Elf64_Rela relocation entry 구조와 맞는다.

typedef struct {
    uint64_t r_offset;   // 값을 쓸 위치
    uint64_t r_info;     // relocation 타입
    uint64_t r_addend;   // 써 넣을 값
} Elf64_Rela;
 

 
그래서 0x404148부터 해석하면:

0x404148 = r_offset = 0x403428
0x404150 = r_info   = 0x8        // R_X86_64_RELATIVE
0x404158 = r_addend = 0x404000
 

 
즉 이 relocation entry의 의미는:

[0x403428] = base + 0x404000
 

 
No PIE라서 결과는:

[0x403428] = 0x404000
 

이다.
 
이걸 gdb로 실제 확인했다.
먼저 프로그램 시작 직후 멈췄다.

starti
 

 
결과:

0x00007ffff7fe4780 in ?? () from /lib64/ld-linux-x86-64.so.2
 

해석:
프로그램의 _start가 실행되기 전에 ld-linux가 먼저 실행 중이다.
즉 relocation 처리 단계다.
그다음 0x403428이 바뀌는 순간을 잡았다.

watch *(long long*)0x403428
continue
 

 
첫 번째 watchpoint 결과:

Old value = 0
New value = 140737351872416
 

 
확인:

x/gx 0x403428
 

 
결과:

0x403428: 0x00007ffff7dd6fa0
 

 
해석:

[0x403428] = 실제 __libc_start_main 주소
 

즉 R_X86_64_GLOB_DAT relocation에 의해 정상적으로 libc 주소가 들어갔다.
 
그 상태에서 다시 실행했다.

continue
 

 
두 번째 watchpoint 결과:

Old value = 140737351872416
New value = 4210688
 

 
4210688을 hex로 보면:

0x404000
 

 
확인:

x/gx 0x403428
 

 
결과:

0x403428: 0x0000000000404000
 

 
해석:

[0x403428] = 0x404000
 

즉 아까 실제 __libc_start_main 주소가 들어갔던 GOT 엔트리가 다시 0x404000으로 덮였다.
 
R_X86_64_RELATIVE
relocation 타입 중 하나
지정된 위치에 base + addend 값을 써라

Offset   Type                Symbol / Addend
403428   R_X86_64_GLOB_DAT   __libc_start_main
4040c0   R_X86_64_GLOB_DAT   __libc_start_main
404148   R_X86_64_RELATIVE   403428
404158   R_X86_64_RELATIVE   404000

 
0x404000 주소부터 명령어 30개를 디스어셈블해서 봐보자.

x/30i 0x404000
0x404000:    cmp    rsi,0x2
0x404004:    jne    0x404051
0x40400a:    mov    r10,QWORD PTR [rdx+0x8]
0x40400e:    test   r10,r10
0x404011:    je     0x404051
0x404017:    lea    r11,[rip+0x5a]          ; 0x404078
0x40401e:    movzx  eax,BYTE PTR [r10]
0x404022:    xor    al,0x5a                 ; 1. 입력값이랑 XOR == 테이블값
0x404024:    cmp    al,BYTE PTR [r11]
0x404027:    jne    0x404051                ; 통과하면 밑에 로직 이어서.
0x40402d:    inc    r10
0x404030:    inc    r11
0x404033:    cmp    r11,0x4040b8            ; 2. 64 바이트 검증 (초기에 r11=0x404078)
0x40403a:    jne    0x40401e                ; 64 바이트가 아니라면 다시 위로 감.
0x404040:    cmp    BYTE PTR [r10],0x0      ; 65번째 문자 == 00 검사
0x404044:    jne    0x404051
0x40404a:    lea    rdi,[rip+0x7]           ; 0x404058(fake main 주소)
0x404051:    jmp    QWORD PTR [rip+0x69]    ; Wrong: 0x4040c0에 저장된 값으로 jmp (바로 libc_start_main으로 감)
0x404057:    nop
0x404058:    push   rbp                     ; fake main
0x404059:    mov    rbp,rsp
0x40405c:    mov    rsi,QWORD PTR [rsi+0x8]
0x404060:    lea    rdi,[rip+0x51]          ; 0x4040b8(DH{%s})
0x404067:    xor    eax,eax
0x404069:    call   0x401060 <printf@plt>
0x40406e:    xor    eax,eax
0x404070:    pop    rbp
0x404071:    ret

 
밑쪽에 함수가 하나 더 있다. 얘부터 봐보자.
0.0x404058 fake main 함수 -> DH{flag} 출력

0x404058:    push   rbp                         ; fake main
0x404059:    mov    rbp,rsp
0x40405c:    mov    rsi,QWORD PTR [rsi+0x8]     ; rsi = argv[1]
0x404060:    lea    rdi,[rip+0x51]              ; rdi = 0x4040b8
0x404067:    xor    eax,eax 
0x404069:    call   0x401060 <printf@plt>
0x40406e:    xor    eax,eax
0x404070:    pop    rbp
0x404071:    ret

rsi: main주소 / rsi+8: 첫번째 인자 주소
 

printf((char *)0x4040b8, argv[1]);
printf("DH{%s}", argv[1]);

이 부분에서 DH{flag}를 출력한다는 것을 알았다. 그럼 우리는 이제 이 함수를 실행하도록 로직을 짜야할 것이다.
 
 
1. 인자가 정확히 1개 있어야 한다. (이 조건은 아까 main에서 우리가 찾은 조건이랑 똑같음)

0x404000: cmp rsi,0x2
0x404004: jne 0x404051

rsi는 argc 역할이다. argc == 2여야 통과한다.
즉, 실행 형태는 ./chall [입력값] 이런 식이여야 한다.
 
2. argv[1]를 가져온다.

0x40400a: mov r10,QWORD PTR [rdx+0x8]
0x40400e: test r10,r10
0x404011: je 0x404051

rdx는 argv 배열이고, [rdx+0x8]은 argv[1]이다.
r10 = argv[1]
r10에 입력 문자열 주소를 할당했다.

if ((r10 & r10) == 0) {
    goto 0x404051;
}

r10이 NULL이면 0x404051로 가라.
 
조건1. 인자가 하나 있어야 한다.
 
3. XOR 연산 조건을 하나 더 추가한다.
조건2. 내 인자값 XOR 0x5a == 테이블 값 인지 확인

0x404017: lea r11,[rip+0x5a]        ; 0x404078
0x40401e: movzx eax,BYTE PTR [r10]
0x404022: xor al,0x5a
0x404024: cmp al,BYTE PTR [r11]     ; xor 연산 결과가 맞지 않으면
0x404027: jne 0x404051              ; libc_starts_main 함수로 ㄱㄱ

 
한 줄씩 해석

0x404017: lea r11,[rip+0x5a]    ; 0x404078

r11에 0x404078 주소를 넣는다. 여기가 정답 검증용 테이블 시작 주소다.
 

0x40401e: movzx eax,BYTE PTR [r10]

r10이 가리키는 입력값 1바이트를 eax에 가져온다. 우리는 2번에서 r10 = argv[1] 할당했음.
-> argv[1][i]를 가져오는 것
 

0x404022: xor al,0x5a

가져온 입력값 1바이트에 0x5a를 XOR 한다.
 

입력값[i] ^ 0x5a
0x404024: cmp al,BYTE PTR [r11]

XOR 결과와 테이블의 현재 1바이트를 비교한다.
 

if ( (입력문자 ^ 0x5a) != 테이블값(r11) )
    실패;

인지 검사하는 것.
 
4. (반복문) 입력 길이는 정확히 64 바이트여야 한다.

0x40402d: inc r10                  ; r10++
0x404030: inc r11                  ; r11++
0x404033: cmp r11,0x4040b8
0x40403a: jne 0x40401e             ; 위 cmp 조건 안 맞으면 다시 xor 연산하는 곳으로 올라감
                                   ; (반복문)

r10(인자), r11(테이블)을 1씩 증가시킨다. (배열에서 다음 idx로 이동하는 것)
3번에서 r11 = 0x404078을 해줬는데, 0x404033에서 r11이 0x4040b8인지 비교한다.
즉, r11이 0x4040b8이 되기 전까지 반복한다.
 
성공하려면 (입력문자 ^ 0x5a) == 테이블값이어야 한다.
우리는 입력문자를 구해야 하므로, 입력문자 = 테이블값 ^ 0x5a를 해주면 된다.
 
그래서 테이블 범위는 `0x404078 ~ 0x4040b7`라는 것을 알 수 있다.
테이블 길이는: 0x4040b8 - 0x404078 = 0x40 = 64바이트

조건3. 테이블 범위가 64바이트이므로 입력문자도 64바이트이다.

 
조건 정리
조건1. 인자가 하나 있어야 한다.
조건2. 내 입력값 XOR 0x5a == 테이블 값 인지 확인. 테이블 범위는 0x404078~ 0x4040b7 이다.
조건3. 입력값은 64바이트여야 한다.
 
 
5. main 함수 주소를 fake main 함수 주소로 덮어씌우기

0x404040:    cmp    BYTE PTR [r10],0x0      ; 65번째 문자 == 00 검사
0x404044:    jne    0x404051                ; 00 아니면 바로 main jmp
0x40404a:    lea    rdi,[rip+0x7]           ; 0x404058 / 00검사 통과하면 실행
0x404051:    jmp    QWORD PTR [rip+0x69]    ; Wrong: 0x4040c0에 저장된 값으로 jmp (바로 libc_start_main으로 감)

rdi는 __libc_start_main의 첫 번째 인자, 즉 원래 main 함수 주소였다.
근데 여기서는 rip+0x7==0x404058(fake main 주소, DH{flag})를 담는다.

커널이 프로그램 실행
→ ELF entry point 실행
→ _start
→ __libc_start_main(main, argc, argv, init, fini, ...)
→ main(argc, argv, envp)

그런데 rdi를 0x404058(fake main 주소)로 덮어씌웠다.
0x404051에서는 libc_start_main()으로 jmp한다. 이때 libc_start_main()의 인자로 rdi가 들어가는 것이다.
조건 불만족 → start에서 할당했던 rdi 값(main주소)
조건 만족 → start 에서 할당했지만 lea rdi,[rip+0x7]이 실행되어 덮어씌워진 fake main 주소값
 
조건 정리
조건1. 인자가 하나 있어야 한다.
조건2. 내 입력값 XOR 0x5a == 테이블 값 인지 확인. 테이블 범위는 0x404078~ 0x4040b7 이다.
조건3. 입력값은 64바이트여야 한다. (65번째는 NULL)
 
중간 정리

조건 1, 2, 3를 다 만족해야 fake main 함수로 갈 수 있다.

조건을 만족하지 못한다면 원래 main으로 jmp해서 Wrong이 출력될 것이다.
 

 

테이블 확인

조건1. 인자가 하나 있어야 한다.
조건2. 내 입력값 XOR 0x5a == 테이블 값 인지 확인. 테이블 범위는 0x404078~ 0x4040b7 이다.
조건3. 입력값은 64바이트여야 한다. (65번째는 NULL)

 
이걸 0x5a랑 XOR 복호화 해주면 된다.
gdb에서 바로 XOR 계산해주는 코드

python
mem = gdb.selected_inferior().read_memory(0x404078, 0x40).tobytes()
print(bytes([b ^ 0x5a for b in mem]).decode())
end

정답

 

정리

  1. main을 봤는데 printf DH{flag}가 있어서 이 로직을 만족시키려고 확인을 해봤다.
  2. unsat이라고, 절대 만족시킬 수 없는 조건이 있었다.
  3. 그래서 실행 시작 위치를 보니, main이 아니라 start에서 시작했다.
  4. 직접 실행을 해보니 start에서 GOT로 0x404000으로 이동하는데 여기서는 인자값 XOR 0x5a == 테이블 값 비교를 하는 로직이 있다는 것을 확인했다.
  5. 이 조건을 만족하면 숨겨져 있는 main으로 이동이 가능했고 여기서 DH{flag} 출력이 가능했다.

조건1. 인자가 하나 있어야 한다.
조건2. 내 입력값 XOR 0x5a == 테이블 값 인지 확인. 테이블 범위는 0x404078~ 0x4040b7 이다.
조건3. 입력값은 64바이트여야 한다. (65번째는 NULL)
테이블값의 주소를 찾아서 0x5a를 해주면 정답인 인자값을 구할 수 있다.
 

실행화면

조건에 맞지 않는 인자를 넣었을 때

입력: aa
libc_start_main에 들어간 인자값: 0x401407

 
main 주소 > unsat 일어나서 무조건 Wrong 출력하는 곳

조건에 맞는 인자를 넣었을 때

입력: 정답값
처음 start에서 rdi에 main 주소를 넣었었지만 조건 1, 2, 3를 만족시켜서 rdi에 fake main 주소(0x404058)가 덮어씌워지게 되었다.
 
libc_start_main에 들어간 인자값: 0x404058

 
0x404058은 DH를 출력할 수 있는 함수 주소이다.

 

전체 흐름 한 눈에 보기

728x90