| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- 레나 튜토리얼
- sk 쉴더스 루키즈 31기
- 자바
- Dreamhack
- sk 쉴더스
- 레나튜토리얼
- 깃
- 백엔드
- 위상 정렬
- 루키즈31기
- 웹개발
- 루키즈
- 악성코드 분석
- 알고리즘
- React
- sk쉴더스 루키즈
- SK쉴더스루키즈
- sk 쉴더스 루키즈
- 루키즈 31기
- 동적분석
- 우테코
- 프리코스
- 우아한테크코스
- top-cert
- 예술의 전당
- linux
- webhacking
- c
- Practical Malware Analysis Labs
- 리버싱
- Today
- Total
yon11b
대구·경북 사이버공격 방어대회 2026 예선전 reversing 문제(UNSAT) 본문

문제파일: 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 !!


참고) 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 명령어로 확인

흐름 정리

- _start에서 __libc_start_main을 호출하는 부분 확인
- 직접 call이 아니라 GOT를 통한 indirect call임
- 실행 중 값이 0x404000으로 되어 있음
- 0x404000을 까보니 숨겨진 검증 코드 존재(밑에서 확인할 거임)
- 따라서 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 처리 후에는 메모리가 이렇게 된다.


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: retrsi: 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 0x404051rdx는 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] ; 0x404078r11에 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

정답

정리
- main을 봤는데 printf DH{flag}가 있어서 이 로직을 만족시키려고 확인을 해봤다.
- unsat이라고, 절대 만족시킬 수 없는 조건이 있었다.
- 그래서 실행 시작 위치를 보니, main이 아니라 start에서 시작했다.
- 직접 실행을 해보니 start에서 GOT로 0x404000으로 이동하는데 여기서는 인자값 XOR 0x5a == 테이블 값 비교를 하는 로직이 있다는 것을 확인했다.
- 이 조건을 만족하면 숨겨져 있는 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를 출력할 수 있는 함수 주소이다.

전체 흐름 한 눈에 보기

'보안 > 리버싱' 카테고리의 다른 글
| [Lena Reversing] 8번-Artgem 초스피드 크랙!(배운 것 응용해보기) (1) | 2026.07.05 |
|---|---|
| [Lena Reversing] 6번-PC 설젼 체험판(델파이 프로그램) (0) | 2026.06.13 |
| [Lena Reversing] 5번-VisualSite Designer 체험판 크랙하기 (0) | 2026.06.07 |
| [Lena Reversing] 4번-PixtopianBook 체험판 크랙하기 (0) | 2026.05.30 |
| [Lena Reversing] 3번-잔소리 제거하기 (0) | 2026.05.30 |