본문 바로가기

3. Pwnable (포너블)/1) Write UP

[6주차] pwnable.kr 문제 풀이_pwnabless

 pwnable.kr/play.php

 

https://pwnable.kr/play.php

 

pwnable.kr

1. random

문제

 

문제 풀이

(1)

문제에서 제시한 ssh random@pwnable.kr -p2222 로 접속 / pw 입력

 

(2)

ls 명령어와 ls -l 명령어를 통해 파일 목록 및 권한 확인

 

(3)

cat random.c 명령어를 입력해 C코드 확인

#include <stdio.h>

int main(){
	unsigned int random;
	random = rand();	// random value!

	unsigned int key=0;
	scanf("%d", &key);

	if( (key ^ random) == 0xcafebabe ){
		printf("Good!\n");
		setregid(getegid(), getegid());
		system("/bin/cat flag");
		return 0;
	}

	printf("Wrong, maybe you should try 2^32 cases.\n");
	return 0;
}

rand() 함수를 통해 생성된 랜덤 값을 random 변수에 저장

 

scanf 함수를 통해 사용자 입력을 받아 key 변수에 저장

 

key와 random의 XOR을 계산한 값이 0xcafebabe일 경우 flag 획득

 

XOR(^): 두 비트가 다를 때 1, 같을 때 0이 되는 연산

 

rand() 함수는 프로그램 실행 시 랜덤 값이 한 번 생성되어 정해지면
프로그램을 여러번 실행하더라도 처음 생성된 랜덤 값으로 동일하게 출력됨

 

따라서 진정한 랜덤 값을 생성하고 싶을 경우 srand() 함수를 함께 사용해야 함
→ 해당 함수는 프로그램 실행 시 마다 다른 랜덤 값 출력

 

시드(seed) 난수 생성을 시작하기 위한 초기값
srand() 시드를 설정하는 함수
rand() 설정된 시드 값을 기반으로 난수 생성

srand()의 시드를 임의의 값으로 설정 시 == 랜덤 값(난수) 동일하게 출력 (e.g. srnad(1234))

srand()의 시드를 매번 달라지는 값으로 설정 시 == 랜덤 값(난수) 상이하게 출력 (e.g. srand(time(NULL)) → 현재 시간으로 시드 설정

 

해당 문제는 srnad()를 사용해 시드 값을 설정하지 않았기 때문에,

rand()가 처음 생성한 랜덤값을 찾아내면 flag 획득 가능

 

 

(4)

디버깅을 통해 자세히 비교

 

disass main 입력

 

<main+33> 구간을 통해 rand 함수가 실행되고 있음

<main+38> 구간을 통해 rand 값이 [rbp-0x1c]에 저장됨을 알 수 있음

<main+41> 구간을 통해 key 값이 [rbp-0x20]에 저장됨을 알 수 있음

 

rand 값을 자세히 알아보기 위해 b *main+38 입력 후 다시 실행(r)

<main+38> mov DWORD PTR [rbp-0x1c],eax

 

eax값을 [rbp-0x1c]에 저장했기 때문에

eax에 저장된 값을 확인하기 위해 info registers eax 입력

따라서 0x6b8b4567은 rand 값

(1804289383은 0x6b8b4567의 10진수 값)

 

 

상단의 C코드를 다시 보면

if( (key ^ random) == 0xcafebabe ){
		printf("Good!\n");
		setregid(getegid(), getegid());
		system("/bin/cat flag");
		return 0;
	}

key 값과 random 값을 XOR 연산을 취해 0xcafebabe가 나와야 함

 

이를 다르게 나타내면

key == 0xcafebabe ^ random과 같음

 

따라서 상단에서 구한 rand의 값과 문제에서의 0xcafebabe를

XOR 연산을 취하면 key값을 알아낼 수 있음

 

random 파일을 실행해 해당 10진수 값을 입력하면 flag 획득 가능

 

 


2. input2

문제

문제 풀이

(1)

문제에서 제시한 ssh input2@pwnable.kr -p2222 로 접속 / pw 입력

 

(2)

ls 명령어와 ls -l 명령어를 통해 파일 목록 및 권한 확인

 

(3)

cat input2.c 명령어를 입력해 C코드 확인

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(int argc, char* argv[], char* envp[]){
	printf("Welcome to pwnable.kr\n");
	printf("Let's see if you know how to give input to program\n");
	printf("Just give me correct inputs then you will get the flag :)\n");

	// argv
	if(argc != 100) return 0;
	if(strcmp(argv['A'],"\x00")) return 0;
	if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
	printf("Stage 1 clear!\n");	

	// stdio
	char buf[4];
	read(0, buf, 4);
	if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
	read(2, buf, 4);
        if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
	printf("Stage 2 clear!\n");
	
	// env
	if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
	printf("Stage 3 clear!\n");

	// file
	FILE* fp = fopen("\x0a", "r");
	if(!fp) return 0;
	if( fread(buf, 4, 1, fp)!=1 ) return 0;
	if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
	fclose(fp);
	printf("Stage 4 clear!\n");	

	// network
	int sd, cd;
	struct sockaddr_in saddr, caddr;
	sd = socket(AF_INET, SOCK_STREAM, 0);
	if(sd == -1){
		printf("socket error, tell admin\n");
		return 0;
	}
	saddr.sin_family = AF_INET;
	saddr.sin_addr.s_addr = INADDR_ANY;
	saddr.sin_port = htons( atoi(argv['C']) );
	if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
		printf("bind error, use another port\n");
    		return 1;
	}
	listen(sd, 1);
	int c = sizeof(struct sockaddr_in);
	cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
	if(cd < 0){
		printf("accept error, tell admin\n");
		return 0;
	}
	if( recv(cd, buf, 4, 0) != 4 ) return 0;
	if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
	printf("Stage 5 clear!\n");

	// here's your flag
	setregid(getegid(), getegid());
	system("/bin/cat flag");	
	return 0;
}

각 문단별 마지막 printf문을 살펴보면 각 stage가 있고

이를 모두 해결해야만 flag를 획득할 수 있음

 

 

각 stage별 코드를 살펴보면

 

[ stage1 ]

	// argv
	if(argc != 100) return 0;
	if(strcmp(argv['A'],"\x00")) return 0;
	if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
	printf("Stage 1 clear!\n");

조건1. argc(인자의 개수)가 100개여야 함

조건2. argv['A'](인자의 위치, A=65)가 "\x00"이어야 함

조건3. argv['B'](인자의 위치, B=66)가 \x20\x0a\x0d"이어야 함

 

argv는 list형태로 입력받기 때문에 배열을 생성하는 코드를 작성하면 됨

 

그러나 현재 경로(디렉토리)에서는 파일 생성 및 작성 권한이 없음

따라서 권한 부여가 되어있는 tmp 디렉토리로 이동해 익스플로잇 코드를 작성해야 함

1. cd ./tmp → tmp 디렉토리로 이동

2. mkdir stage → stage라는 디렉토리 생성

3. cd stage → stage 디렉토리로 이동

 

아래의 명령어를 통해 stage1이라는 파일 생성

(pwntools 이용)

 

from pwn import *

argv = ['a' for i in range(0,100)]

argv[65] = "\x00"
argv[66] = "\x20\x0a\x0d"

p = process(executable = '/home/input2/input2', argv=argv)
p.interactive()

문자 'a'를 0부터 99까지의 인덱스를 가지는 배열을 만듦(총 100개)

 

그 중

인덱스 65에 해당하는 부분에는 "\x00"으로

인덱스 66에 해당하는 부분에는 "\x20\x0a\x0d"으로 변경

 

그 후 '/home/input2/input2' 경로 실행

 

interactive를 통해 직접 입출력 가능하도록 함

stage 1 clear

 

 

[ stage2 ]

	// stdio
	char buf[4];
	read(0, buf, 4);
	if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
	read(2, buf, 4);
        if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
	printf("Stage 2 clear!\n");

4byte 크기의 buffer 선언

 

read 함수 == 리눅스에서 파일 디스크립터(fd)로부터 데이터를 읽는 함수

 

fd(0) == stdin이므로

read(0, buf, 4) == stdin(표준입력)으로부터 4byte만큼 읽어 buf에 저장

 

memcmp()는 두 메모리 블록이 같은지 비교하는 함수

 

조건1. stdin과 "\x00\x0a\x00\xff" 비교

 

fd(2) == stderr이므로

read(2, buf, 4) == stderr(표준에러)로부터 4byte만큼 읽어 buf에 저장

 

조건2. stderr과 "\x00\x0a\x02\xff" 비교

 

 

아래 명령어를 통해 stage2.py 파일 생성

(상단의 stage1.py를 수정해서 작성해도 되지만, 편의상 각 stage별 코드 구분을 위해 새파일에 작성)

 

from pwn import *

#stage 1
argv = ['a' for i in range(0,100)]

argv[65] = "\x00"
argv[66] = "\x20\x0a\x0d"

#stage 2
with open('stderr', 'wb') as f:
    f.write(b"\x00\x0a\x02\xff")

p = process(executable='/home/input2/input2', argv=argv, stderr=open('stderr', 'rb'))
p.send(b"\x00\x0a\x00\xff")
p.interactive()

위의 stage 1 코드와 이어서 작성

 

stderr이라는 파일을 바이너리 쓰기(wb)모드로 열고

해당 파일 안에 "\x00\x0a\x02\xff"를 바이트 형식(b)으로 작성

 

'/home/input2/input2' 경로 실행

stderr 인자로 'stderr' 파일을 바이너리 모드로 읽어(rb) 전달

 

send 함수를 통해 stdin에 "\x00\x0a\x00\xff" 전달 (바이트 형식(b))

stage 2 clear

 

 

[ stage3 ]

	// env
	if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
	printf("Stage 3 clear!\n");

getenv() 함수는  환경 변수(environment variable)의 값을 가져오는 함수

 

따라서 환경변수 "\xde\xad\xbe\xef"가 가지는 값이 "\xca\xfe\xba\xbe"와 일치해야 함

 

아래 명령어를 통해 stage3.py 파일 생성

 

from pwn import *

#stage 1
argv = ['a' for i in range(0,100)]

argv[65] = "\x00"
argv[66] = "\x20\x0a\x0d"

#stage 2
with open('stderr', 'wb') as f:
    f.write(b"\x00\x0a\x02\xff")

#stage 3
env = {"\xde\xad\xbe\xef" : "\xca\xfe\xba\xbe"}

p = process(executable='/home/input2/input2', argv=argv, stderr=open('stderr', 'rb'), env=env)
p.send(b"\x00\x0a\x00\xff")
p.interactive()

위의 stage 2 코드와 이어서 작성

 

환경변수의 값이 key값이 되도록 env라는 딕셔너리를 만든 후 process에 넣음

stage 3 clear

 

 

[ stage 4 ]

	// file
	FILE* fp = fopen("\x0a", "r");
	if(!fp) return 0;
	if( fread(buf, 4, 1, fp)!=1 ) return 0;
	if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
	fclose(fp);
	printf("Stage 4 clear!\n");

"\x0a"라는 이름의 파일을 읽기(r) 모드로 열고

해당 파일 내용의 첫 4byte만큼 읽어 "\x00\x00\x00\x00"와 일치하는지 확인

 

아래 명령어를 통해 stage4.py 파일 생성

 

from pwn import *

#stage 1
argv = ['a' for i in range(0,100)]

argv[65] = "\x00"
argv[66] = "\x20\x0a\x0d"

#stage 2
with open('stderr', 'wb') as f:
    f.write(b"\x00\x0a\x02\xff")

#stage 3
env = {"\xde\xad\xbe\xef" : "\xca\xfe\xba\xbe"}

#stage 4
with open('\x0a', 'wb') as f:
f.write(b"\x00\x00\x00\x00")

p = process(executable='/home/input2/input2', argv=argv, stderr=open('stderr', 'rb'), env=env)
p.send(b"\x00\x0a\x00\xff")
p.interactive()

위의 stage 3 코드와 이어서 작성

 

\x0a이라는 파일을 바이너리 쓰기(wb)모드로 열고

해당 파일 안에 "\x00\x00\x00\x00"를 바이트 형식(b)으로 작성

stage 4 clear

 

 

[ stage 5 ]

	// network
	int sd, cd;
	struct sockaddr_in saddr, caddr;
	sd = socket(AF_INET, SOCK_STREAM, 0);
	if(sd == -1){
		printf("socket error, tell admin\n");
		return 0;
	}
	saddr.sin_family = AF_INET;
	saddr.sin_addr.s_addr = INADDR_ANY;
	saddr.sin_port = htons( atoi(argv['C']) );
	if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
		printf("bind error, use another port\n");
    		return 1;
	}
	listen(sd, 1);
	int c = sizeof(struct sockaddr_in);
	cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
	if(cd < 0){
		printf("accept error, tell admin\n");
		return 0;
	}
	if( recv(cd, buf, 4, 0) != 4 ) return 0;
	if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
	printf("Stage 5 clear!\n");

TCP 소켓 생성을 생성해 sd라는 변수에 대입함

 

AF_INET == IPv4

SOCK_STREAM == 입출력을 stream 방식으로 설정

INADDR_ANY == 모든 IP에서의 연결 허용

 

saddr.sin_port = htons( atoi(argv['C']) );

argv['C'](인자의 위치, C=67)의 문자열을 숫자로 변환해(atoi) sin_port에 대입

이는 소켓 통신 시 포트로 사용할 예정

 

listen(sd, 1);

연결 대기상태로 전환 (최대 1개의 연결만 처리)

 

cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);

클라이언트의 연결 접속 요청 결과를 cd라는 변수에 대입

(cd < 0) → 연결 접속 요청 거부

 

if( recv(cd, buf, 4, 0) != 4 ) return 0;

소켓을 열고 클라이언트로부터 4byte의 문자열을 입력받음


if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;

4byte의 값이 "\xde\xad\xbe\xef"인지 확인

 

∴ argv['C']의 문자열을 정수형으로 변환한 값을 포트 번호로 삼아 TCP 소켓 서버를 열고

     클라이언트로부터 입력받은 4byte의 값이 "\xde\xad\xbe\xef"와 일치하는지 확인 

 

 

아래 명령어를 통해 stage5.py 파일 생성

 

rom pwn import *

#stage 1
argv = ['a' for i in range(0,100)]

argv[65] = "\x00"
argv[66] = "\x20\x0a\x0d"
argv[67] = "2025"

#stage 2
with open('stderr', 'wb') as f:
    f.write(b"\x00\x0a\x02\xff")

#stage 3
env = {"\xde\xad\xbe\xef" : "\xca\xfe\xba\xbe"}

#stage 4
with open('\x0a', 'wb') as f:
    f.write(b"\x00\x00\x00\x00")

p = process(executable='/home/input2/input2', argv=argv, stderr=open('stderr', 'rb'), env=env)
p.send(b"\x00\x0a\x00\xff")

#stage 5
s = remote('localhost', 2025)
s.send(b"\xde\xad\xbe\xef")

p.interactive()

위의 stage 4 코드와 이어서 작성

 

코드 상단에 argv[67] = "2025"를 입력해 임의의 포트 번호 생성

remote() 함수는 pwntools의 TCP 클라이언트 함수임

 

localhost에서 TCP 서버의 포트 2025로 접속 시도

해당 TCP 서버에 "\xde\xad\xbe\xef" 작성

stage 5 clear

 

 

(4)

 모든 스테이지를 클리어했으나, flag값은 나오지 않음

 

Why?

현재 작업하고 있는 경로는 /tmp/stage임

flag는 /home/input2/flag 경로에 있음

 

해당 flag 파일을 현재 경로에 복사할 수 없기 때문에

심볼릭 링크를 활용해 /home/input2/flag 경로에 접속할 수 있도록 해줘야 함

 

ln -s /home/input2/flag flag

 

 

flag라는 이름의 파일을 만들어 /home/input2/flag 경로로 이동할 수 있도록 링크를 걸어둠

(바로가기 기능과 비슷)

 

그 후 다시 마지막 파일인 stage5.py를 실행하면 flag 획득 가능

 


3. leg

문제

 

문제 풀이

(1)

문제에서 제시한 ssh leg@pwnable.kr -p2222 로 접속 / pw 입력

 

(2)

ls 명령어와 ls -l 명령어를 통해 파일 목록 및 권한 확인

 

(3)

해당 문제에서는 C코드가 문제에서 따로 제시되어 있기에

pwnable.kr/bin/leg.c에 접속해 C코드 확인

#include <stdio.h>
#include <fcntl.h>
int key1(){
	asm("mov r3, pc\n");
}
int key2(){
	asm(
	"push	{r6}\n"
	"add	r6, pc, $1\n"
	"bx	r6\n"
	".code   16\n"
	"mov	r3, pc\n"
	"add	r3, $0x4\n"
	"push	{r3}\n"
	"pop	{pc}\n"
	".code	32\n"
	"pop	{r6}\n"
	);
}
int key3(){
	asm("mov r3, lr\n");
}
int main(){
	int key=0;
	printf("Daddy has very strong arm! : ");
	scanf("%d", &key);
	if( (key1()+key2()+key3()) == key ){
		printf("Congratz!\n");
		int fd = open("flag", O_RDONLY);
		char buf[100];
		int r = read(fd, buf, 100);
		write(0, buf, r);
	}
	else{
		printf("I have strong leg :P\n");
	}
	return 0;
}

 

 

main 함수를 먼저 살펴보면

int main(){
	int key=0;
	printf("Daddy has very strong arm! : ");
	scanf("%d", &key);
	if( (key1()+key2()+key3()) == key ){
		printf("Congratz!\n");
		int fd = open("flag", O_RDONLY);
		char buf[100];
		int r = read(fd, buf, 100);
		write(0, buf, r);
	}
	else{
		printf("I have strong leg :P\n");
	}
	return 0;
}

scanf 함수를 통해 사용자 입력을 받고 이를 key라는 변수에 저장함

그 후 key1 + key2 + key3 값이 key값과 같을 경우 flag 획득 가능

 

 

key1, key2, key3 함수를 살펴보면

int key1(){
	asm("mov r3, pc\n");
}
int key2(){
	asm(
	"push	{r6}\n"
	"add	r6, pc, $1\n"
	"bx	r6\n"
	".code   16\n"
	"mov	r3, pc\n"
	"add	r3, $0x4\n"
	"push	{r3}\n"
	"pop	{pc}\n"
	".code	32\n"
	"pop	{r6}\n"
	);
}
int key3(){
	asm("mov r3, lr\n");
}

어셈블리어처럼 보임

문제에서 제시해준 asm 파일을 통해 자세히 살펴볼 예정

 

 

(4)

디버깅을 통해 자세히 비교

pwnable.kr/bin/leg.asm에 접속해 디스어셈블된 코드 확인

 

key1 함수 코드를 살펴보면

(gdb) disass key1
Dump of assembler code for function key1:
   0x00008cd4 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
   0x00008cd8 <+4>:	add	r11, sp, #0
   0x00008cdc <+8>:	mov	r3, pc
   0x00008ce0 <+12>:	mov	r0, r3
   0x00008ce4 <+16>:	sub	sp, r11, #0
   0x00008ce8 <+20>:	pop	{r11}		; (ldr r11, [sp], #4)
   0x00008cec <+24>:	bx	lr
End of assembler dump.

<key1+8> 구간에서 C코드에 작성된 내용 확인 가능

<key1+12> 구간에서 r3를 r0에 저장

 

따라서 r0에는 pc 값이 저장됨

 

pc?

program counter → 다음에 실행해야 할 명령어의 주소를 가리킴

⚲ ARM 아키텍처에서의 명령어 실행 과정
fetch(명령어 로드) → decode(해석) → execute(실행)

명령어가 로드되어 실행되기까지 2단계를 거쳐야 함

ARM 아키텍처의 주소 단위는 WORD이므로,
실행까지 2WORD 단계를 거쳐야 함

1WORD = 4byte이므로, 2WORD = 8byte임

따라서
pc(다음 실행될 명령어 주소) == 현재 명령어 주소 + 8byte
현재 실행되는 명령어 주소 == pc - 8byte

 

즉, <key+8> 코드가 실행되는 과정이라면

fetch decode execute
<key1+16> <key1+12> <key1+8>
sub sp, r11, #0 mov r0, r3 mov r3, pc

<key1+8> 구간으로부터 2단계를 거친 <key1+16> 구간이 pc의 값이 됨

pc의 주소(<key1+16>의 주소)는 0x00008ce4

 

 

key2 함수 코드를 살펴보면

(gdb) disass key2
Dump of assembler code for function key2:
   0x00008cf0 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
   0x00008cf4 <+4>:	add	r11, sp, #0
   0x00008cf8 <+8>:	push	{r6}		; (str r6, [sp, #-4]!)
   0x00008cfc <+12>:	add	r6, pc, #1
   0x00008d00 <+16>:	bx	r6
   0x00008d04 <+20>:	mov	r3, pc
   0x00008d06 <+22>:	adds	r3, #4
   0x00008d08 <+24>:	push	{r3}
   0x00008d0a <+26>:	pop	{pc}
   0x00008d0c <+28>:	pop	{r6}		; (ldr r6, [sp], #4)
   0x00008d10 <+32>:	mov	r0, r3
   0x00008d14 <+36>:	sub	sp, r11, #0
   0x00008d18 <+40>:	pop	{r11}		; (ldr r11, [sp], #4)
   0x00008d1c <+44>:	bx	lr
End of assembler dump.

<key2+20> 구간에서 pc를 r3에 저장

<key2+22> 구간에서 r3에 4를 더함

fetch decode execute
<key2+24> <key2+22> <key2+20>
push {r3} adds r3, #4 mov r3, pc

<key2+20> 구간으로부터 2단계를 거친 <key2+24> 구간이 pc의 값이 됨

<key2+24>의 주소는 0x00008d08임

 

여기서 <key2+22> 구간에서 r3+4를 연산했으므로

pc의 주소(<key2+24>의 주소)는 0x00008d08 + 4 = 0x00008d0c

 

 

key3 함수 코드를 살펴보면

(gdb) disass key3
Dump of assembler code for function key3:
   0x00008d20 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
   0x00008d24 <+4>:	add	r11, sp, #0
   0x00008d28 <+8>:	mov	r3, lr
   0x00008d2c <+12>:	mov	r0, r3
   0x00008d30 <+16>:	sub	sp, r11, #0
   0x00008d34 <+20>:	pop	{r11}		; (ldr r11, [sp], #4)
   0x00008d38 <+24>:	bx	lr
End of assembler dump.

<key3+8> 구간에서 lr를 r3에 저장

<key3+12> 구간에서 r3을 r0에 저장

 

lr?

link register → 함수를 호출한 후 반환하는 주소 저장 (되돌아갈 함수의 주소 저장)

 

key3 함수 호출이 종료된 후 되돌아가는 함수는 main 함수임

 

main 함수의 코드를 살펴보면

		..생략..
 0x00008d78 <+60>:	add	r4, r4, r3
 0x00008d7c <+64>:	bl	0x8d20 <key3>
 0x00008d80 <+68>:	mov	r3, r0
 0x00008d84 <+72>:	add	r2, r4, r3
 		..생략..

<main+64> 구간에서 key3 함수 호출이 종료된 후

<main+68> 구간으로 다시 돌아옴

 

따라서 key3에서 lr은 <main+68>의 주소를 저장하고 있으므로

lr의 주소는 0x00008d80

 

 

(5)

상단의 C코드의 내용을 바탕으로 key1 + key2 + key3를 계산하면

key1 key2 key3
0x00008ce4 0x00008d0c 0x00008d80

 

0x8ce4 + 0x8d0c + 0x8d80 = 0x1A770

 

key1 + key2 + key3의 값은 key와 비교해서 일치해야 하는데,

key는 int형으로 선언되어 있음

 

따라서 0x1A770의 10진수 값인 108400이 key값이 됨

 

leg 파일을 실행해 해당 10진수 값을 입력하면 flag 획득 가능