[Dreamhack] REV Small Counter
이번 문제는 Dreamhack CTF Season 3 Round #4 (🌱Div2)에 출제된 리버싱 문제이다. 오랜만에 풀어보는 리버싱 문제인데, 리눅스 기반 ELF 바이너리다. 일단 실행시켜보자.
root@dig06161-virtual-machine:/home/dig06161/file/dreamhack/Small_Counter# ./chall
---Counter---
10
9
8
7
6
5
4
3
2
1
---END---
root@dig06161-virtual-machine:/home/dig06161/file/dreamhack/Small_Counter#
10부터 1까지 출력한다. 여기서 flag를 출력하는 부분을 찾아 실행해야 할 것 같다. 우선 ghidra를 통해 바이너리를 열어보자. main함수의 어셈블리와 디컴파일 코드는 다음과 같다.
0x0000555555555494 <+0>: endbr64
0x0000555555555498 <+4>: push rbp
0x0000555555555499 <+5>: mov rbp,rsp
0x000055555555549c <+8>: sub rsp,0xf0
0x00005555555554a3 <+15>: mov DWORD PTR [rbp-0x4],0x0
0x00005555555554aa <+22>: lea rax,[rip+0xb53] # 0x555555556004
0x00005555555554b1 <+29>: mov rdi,rax
0x00005555555554b4 <+32>: call 0x555555555090 <puts@plt>
0x00005555555554b9 <+37>: mov DWORD PTR [rbp-0x4],0xa
0x00005555555554c0 <+44>: jmp 0x5555555555a0 <main+268>
0x00005555555554c5 <+49>: mov eax,DWORD PTR [rbp-0x4]
0x00005555555554c8 <+52>: mov esi,eax
0x00005555555554ca <+54>: lea rax,[rip+0xb41] # 0x555555556012
0x00005555555554d1 <+61>: mov rdi,rax
0x00005555555554d4 <+64>: mov eax,0x0
0x00005555555554d9 <+69>: call 0x5555555550b0 <printf@plt>
0x00005555555554de <+74>: cmp DWORD PTR [rbp-0x4],0x3
0x00005555555554e2 <+78>: jne 0x55555555559c <main+264>
0x00005555555554e8 <+84>: movabs rax,0x38383830357b4d49
0x00005555555554f2 <+94>: movabs rdx,0x6a37386a32336a39
0x00005555555554fc <+104>: mov QWORD PTR [rbp-0xf0],rax
0x0000555555555503 <+111>: mov QWORD PTR [rbp-0xe8],rdx
0x000055555555550a <+118>: movabs rax,0x3035363435676a39
0x0000555555555514 <+128>: movabs rdx,0x6a68383234303438
0x000055555555551e <+138>: mov QWORD PTR [rbp-0xe0],rax
0x0000555555555525 <+145>: mov QWORD PTR [rbp-0xd8],rdx
0x000055555555552c <+152>: movabs rax,0x6838306969326968
0x0000555555555536 <+162>: movabs rdx,0x3833356a68693437
0x0000555555555540 <+172>: mov QWORD PTR [rbp-0xd0],rax
0x0000555555555547 <+179>: mov QWORD PTR [rbp-0xc8],rdx
0x000055555555554e <+186>: movabs rax,0x3667376a33343568
0x0000555555555558 <+196>: movabs rdx,0x68696a386b6a356b
0x0000555555555562 <+206>: mov QWORD PTR [rbp-0xc0],rax
0x0000555555555569 <+213>: mov QWORD PTR [rbp-0xb8],rdx
0x0000555555555570 <+220>: mov DWORD PTR [rbp-0xb0],0x7d663232
0x000055555555557a <+230>: mov BYTE PTR [rbp-0xac],0x0
0x0000555555555581 <+237>: lea rcx,[rbp-0xf0]
0x0000555555555588 <+244>: lea rax,[rbp-0x50]
0x000055555555558c <+248>: mov edx,0x45
0x0000555555555591 <+253>: mov rsi,rcx
0x0000555555555594 <+256>: mov rdi,rax
0x0000555555555597 <+259>: call 0x5555555550c0 <memcpy@plt>
0x000055555555559c <+264>: sub DWORD PTR [rbp-0x4],0x1
0x00005555555555a0 <+268>: cmp DWORD PTR [rbp-0x4],0x0
0x00005555555555a4 <+272>: jg 0x5555555554c5 <main+49>
0x00005555555555aa <+278>: cmp DWORD PTR [rbp-0x4],0x5
0x00005555555555ae <+282>: jne 0x5555555555fe <main+362>
0x00005555555555b0 <+284>: lea rax,[rip+0xa5f] # 0x555555556016
0x00005555555555b7 <+291>: mov rdi,rax
0x00005555555555ba <+294>: call 0x555555555090 <puts@plt>
0x00005555555555bf <+299>: mov eax,DWORD PTR [rbp-0x4]
0x00005555555555c2 <+302>: mov DWORD PTR [rbp-0x8],eax
0x00005555555555c5 <+305>: mov edx,DWORD PTR [rbp-0x8]
0x00005555555555c8 <+308>: lea rcx,[rbp-0xa0]
0x00005555555555cf <+315>: lea rax,[rbp-0x50]
0x00005555555555d3 <+319>: mov rsi,rcx
0x00005555555555d6 <+322>: mov rdi,rax
0x00005555555555d9 <+325>: call 0x5555555551c9 <flag_gen>
0x00005555555555de <+330>: lea rax,[rbp-0xa0]
0x00005555555555e5 <+337>: mov rsi,rax
0x00005555555555e8 <+340>: lea rax,[rip+0xa2d] # 0x55555555601c
0x00005555555555ef <+347>: mov rdi,rax
0x00005555555555f2 <+350>: mov eax,0x0
0x00005555555555f7 <+355>: call 0x5555555550b0 <printf@plt>
0x00005555555555fc <+360>: jmp 0x55555555560d <main+377>
0x00005555555555fe <+362>: lea rax,[rip+0xa1c] # 0x555555556021
0x0000555555555605 <+369>: mov rdi,rax
0x0000555555555608 <+372>: call 0x555555555090 <puts@plt>
0x000055555555560d <+377>: mov eax,0x0
0x0000555555555612 <+382>: leave
0x0000555555555613 <+383>: ret
undefined8 main(void)
{
undefined8 local_f8;
undefined8 local_f0;
undefined8 local_e8;
undefined8 local_e0;
undefined8 local_d8;
undefined8 local_d0;
undefined8 local_c8;
undefined8 local_c0;
undefined4 local_b8;
undefined local_b4;
undefined local_a8 [80];
char local_58 [72];
uint local_10;
uint local_c;
local_c = 0;
puts("---Counter---");
for (local_c = 10; 0 < (int)local_c; local_c = local_c - 1) {
printf("%d\n",(ulong)local_c);
if (local_c == 3) {
local_f8 = 0x38383830357b4d49;
local_f0 = 0x6a37386a32336a39;
local_e8 = 0x3035363435676a39;
local_e0 = 0x6a68383234303438;
local_d8 = 0x6838306969326968;
local_d0 = 0x3833356a68693437;
local_c8 = 0x3667376a33343568;
local_c0 = 0x68696a386b6a356b;
local_b8 = 0x7d663232;
local_b4 = 0;
memcpy(local_58,&local_f8,0x45);
}
}
if (local_c == 5) {
puts("Nice!");
local_10 = local_c;
flag_gen(local_58,(long)local_a8,local_c);
printf("\n%s\n",local_a8);
}
else {
puts("---END---");
}
return 0;
}
코드를 분석해보면 플레그를 만들어주는 함수는 flag_gen 함수이고 for 가 동작하는 반복문과 별개의 코드로 if가 동작해야지 플래그를 출력한다. if가 동작하기 위해서는 반복문이 끝난 후 local_c가 5를 가지고 있어야 동작한다. 다만 반복문이 종료되면 local_c가 0을 가지게 된다. 따라서 gdb를 통해 bp를 반복분이 끝나는 시점에 걸고 local_c 값을 강제로 5로 바꾸면 풀 수 있을 것이다.
다만 바이너리에 bp를 걸면 좀 이상하게 동작한다. PIE가 걸려있어 바이너리를 실행할 때 마다 주소값이 바뀌는 문제가 있다. gdb에서는 디버깅의 편의를 위해 PIE 보호기법이 걸린 바이너리는 코드영역 주소값을 0x555555555000로 가진다. 따라서 한번 실행한 후 main함수를 disass 하면 0x0000555555555494주소를 가진다. 이 부분에 bp를 걸고 이후 main+278부분에 bp를 걸어주면 중단지점을 설정할 수 있다. 이후 set 기능을 이용해 rbp - 4 부분을 5로 쓰면 플래그를 얻을 수 있다.
pwndbg> $rbp-4
Undefined command: "$rbp-4". Try "help".
pwndbg> p $rbp-4
$1 = (void *) 0x7fffffffe32c
pwndbg> x/x 0x7fffffffe32c
0x7fffffffe32c: 0x00000000
pwndbg> set *0x7fffffffe32c=5
pwndbg> x/x 0x7fffffffe32c
0x7fffffffe32c: 0x00000005
pwndbg> c
Continuing.
Nice!
DH{389998e56e90e8eb34238948469ce중략...}
[Inferior 1 (process 48876) exited normally]
pwndbg>