블로그 이전했습니다. https://jeongzero.oopy.io/
[HackCTF] static
본문 바로가기
워게임/HackCTF

[HackCTF] static

728x90

1. 문제


1) mitigation 확인

카나리가 안걸려있다

2) 문제 확인

뭐지...

3) 코드흐름 파악

바이너리가 스트립되있고, 뭔가 조져놔서 main이 안잡힌다. 그래서 어셈으로 첨에 대충 봤다

저기 Nope.. 이 나오는걸로 따라가서 메인처럼 보이는 부분을 찾았다

.text:0000000000000A3A                 push    rbp
.text:0000000000000A3B                 mov     rbp, rsp
.text:0000000000000A3E                 sub     rsp, 30h
.text:0000000000000A42                 mov     [rbp-24h], edi //argc 임
.text:0000000000000A45                 mov     [rbp-30h], rsi
.text:0000000000000A49                 mov     dword ptr [rbp-18h], 0
.text:0000000000000A50                 cmp     dword ptr [rbp-18h], 1
.text:0000000000000A54                 jnz     short loc_A58
.text:0000000000000A54 ; ---------------------------------------------------------------------------
.text:0000000000000A56                 db 2 dup(0CCh)
.text:0000000000000A58 ; ---------------------------------------------------------------------------
.text:0000000000000A58
.text:0000000000000A58 loc_A58:                                ; CODE XREF: .text:0000000000000A54↑j
.text:0000000000000A58                 lea     rdi, aTeamName  ; "team_name"
.text:0000000000000A5F                 call    _getenv
.text:0000000000000A64                 mov     [rbp-10h], rax
.text:0000000000000A68                 cmp     qword ptr [rbp-10h], 0
.text:0000000000000A6D                 jz      loc_AF3
.text:0000000000000A73                 mov     rax, [rbp-10h]
.text:0000000000000A77                 mov     edx, 4
.text:0000000000000A7C                 lea     rsi, aBi0s      ; "bi0s"
.text:0000000000000A83                 mov     rdi, rax
.text:0000000000000A86                 call    _strncmp
.text:0000000000000A8B                 test    eax, eax
.text:0000000000000A8D                 jnz     short loc_AF3
.text:0000000000000A8F                 cmp     dword ptr [rbp-24h], 2
.text:0000000000000A93                 jz      short loc_AA8

...

형광펜 친 부분을 토대로 team_name=bi0s 가 환경변수에 들어있는지 확인하고, 인자가 2개인지 확인하는걸 알수 있다. 저 2개를 만족하면 loc_AA8로 이동한다. 그 이상은 귀찮아서 기드라를 이용했다.

2. 접근방법


/* WARNING: Removing unreachable block (ram,0x00100b11) */
/* WARNING: Removing unreachable block (ram,0x00100a56) */

undefined8 main_(int param_1,long param_2)

{
  int iVar1;
  char *__s1;
  
  __s1 = getenv("team_name");
  if ((__s1 == (char *)0x0) || (iVar1 = strncmp(__s1,"bi0s",4), iVar1 != 0)) {
    printf("Nope.");
  }
  else {
    if (param_1 == 2) {
      iVar1 = FUN_0010087c(__s1,*(undefined8 *)(param_2 + 8),*(undefined8 *)(param_2 + 8));
      if (iVar1 == 1) {
        FUN_00100830(*(undefined8 *)(param_2 + 8));
      }
      else {
        printf("Better luck next time!");
      }
    }
    else {
      printf("usage: chall <input>");
    }
  }
  return 0;
}

어셈으로 첨에 분석한게 맞다. argv[1]에 인자를 하나주고, FUN_0010087c 함수를 호출하는데, 이 함수의 반환값이 1이 되야지 되는것 같다. 저 함수를 분석해보자.

/* WARNING: Removing unreachable block (ram,0x001009c7) */
/* WARNING: Removing unreachable block (ram,0x00100a2c) */
/* WARNING: Removing unreachable block (ram,0x0010092b) */

undefined8 FUN_0010087c(char *param_1,char *param_2)

{
  size_t sVar1;
  ulong uVar2;
  char *local_70;
  byte local_68 [32];
  undefined8 local_48;
  undefined8 local_40;
  undefined4 local_38;
  undefined2 local_34;
  undefined local_32;
  undefined4 local_30;
  undefined4 local_28;
  uint local_24;
  int local_20;
  int local_1c;
  
  local_20 = 0;
  local_24 = 0;
  local_48 = 0x3931383137313631;
  local_40 = 0x3731363138333632;
  local_38 = 0x31393139;
  local_34 = 0x3439;
  local_32 = 0;
  local_70 = param_1; // 'bios'
  sVar1 = strlen(param_2);
---------------------------------------------------------------------------------------
  if (sVar1 == 0x16) {
    local_1c = 0;
    while (uVar2 = SEXT48(local_1c), sVar1 = strlen(local_70), uVar2 < sVar1) {
      local_20 = local_20 + local_70[local_1c];
      local_1c = local_1c + 1;
    }
    local_30 = 0;
    local_20 = local_20 / 0x1e;
--------------------------------------------------------------------------------------
    while (local_24 != 0x16) {
      if ((local_24 & 1) == 0) {
        local_68[(int)local_24] = param_2[(int)local_24] + 4;
      }
      else {
        local_68[(int)local_24] = param_2[(int)local_24] - 4;
      }
      local_68[(int)local_24] = local_68[(int)local_24] ^ (byte)local_20;
      local_24 = local_24 + 1;
    }
    local_28 = 0;
    sVar1 = strlen((char *)local_68);
    local_1c = (int)sVar1;
--------------------------------------------------------------------------------------
    do {
      local_1c = local_1c + -1;
      if (local_1c < 0) {
        return 1;
      }
      sVar1 = strlen((char *)local_68);
    } while (*(char *)((long)&local_70 + (sVar1 - (long)local_1c) + 7) ==
             *(char *)((long)&local_48 + (long)local_1c));
  }
  return 0;
}

위 함수의 체크 로직은 크게 3부분이다.

  1. 첫번째 요약 : 'bi0s' 문자열의 각 문자들의 아스키 값을 더하고 특정 연산을 통해 xor 키 값 도출
      if (sVar1 == 0x16) {
        local_1c = 0;
        while (uVar2 = SEXT48(local_1c), sVar1 = strlen(local_70), uVar2 < sVar1) {
          local_20 = local_20 + local_70[local_1c];
          local_1c = local_1c + 1;
        }
        local_30 = 0;
        local_20 = local_20 / 0x1e;
    		=> local_20 : 0xc
    • sVar1 변수는 argv[1] 길이다. 따라서 0x16 사이즈의 인자를 줘야함
    • local_70="bi0s" 이다. 따라서 각 문자의 아스키값을 더해서 local_20에 저장한다.
    • 계산해보면 0x16e이고, 이를 0x1e로 나누면 0xc가 나옴

  1. 두번째 요약 : argv[1] 문자열의 각 문자와 위에서 구한 0xc와 xor연산진행
        while (local_24 != 0x16) {
          if ((local_24 & 1) == 0) {
            local_68[(int)local_24] = param_2[(int)local_24] + 4;
          }
          else {
            local_68[(int)local_24] = param_2[(int)local_24] - 4;
          }
          local_68[(int)local_24] = local_68[(int)local_24] ^ (byte)local_20;
          local_24 = local_24 + 1;
        }
        local_28 = 0;
        sVar1 = strlen((char *)local_68);
        local_1c = (int)sVar1;
    • param_2가 내가 입력한 argv[1]이다.
    • 반복문을 돌면서 local_24를 문자열의 인덱스로 보고 짝수, 홀수 일때에 따라서 +4 or -4를 param_2 에 더하고 local_68 배열에 저장한다.
    • 그다음 local_68 값을 0xc와 xor한다

  1. 세번째 요약 : 미리 인코딩? 되어 들어있는 값과, 1,2,번 연산을 통해 계산된 값을 비교한다.
      do {
          local_1c = local_1c + -1;
          if (local_1c < 0) {
            return 1;
          }
          sVar1 = strlen((char *)local_68);
        } while (*(char *)((long)&local_70 + (sVar1 - (long)local_1c) + 7) ==
                 *(char *)((long)&local_48 + (long)local_1c));
      }
      return 0;
    • 여긴 그냥 직접 gdb로 보는게 더 편하다. 참고로 main에 bp 안걸리므로 getenv에 bp 걸고 finish로 나오면, main 디버깅 가능. 물론 main이라고 나오진 않지만 아이다 offset으로 확인해서 메인이라고 판단가능.

      ─────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────
       ► 0x5555555549fb    movzx  edx, byte ptr [rbp + rax - 0x60]
         0x555555554a00    mov    eax, dword ptr [rbp - 0x14]
         0x555555554a03    cdqe   
         0x555555554a05    movzx  eax, byte ptr [rbp + rax - 0x40]
         0x555555554a0a    cmp    dl, al
         0x555555554a0c    je     0x555555554a15 <0x555555554a15>
       
         0x555555554a0e    mov    eax, 0
         0x555555554a13    jmp    0x555555554a33 <0x555555554a33>
          ↓
         0x555555554a33    add    rsp, 0x68
         0x555555554a37    pop    rbx
         0x555555554a38    pop    rbp
      ──────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────
      00:0000│ rsp  0x7fffffffddc0 —▸ 0x7fffffffe2e7 ◂— 'aaaaaaaaaaaaaaaaaaaaaa'
      01:0008│      0x7fffffffddc8 —▸ 0x7fffffffef5e ◂— 0x2f3d5f0073306962 /* 'bi0s' */
      02:0010│ rdi  0x7fffffffddd0 ◂— 'iQiQiQiQiQiQiQiQiQiQiQ'
      ... ↓
      04:0020│      0x7fffffffdde0 ◂— 0x516951695169 /* 'iQiQiQ' */
      05:0028│      0x7fffffffdde8 ◂— 0x0
      06:0030│      0x7fffffffddf0 ◂— '1617181926381617919194'
      07:0038│      0x7fffffffddf8 ◂— '26381617919194'
      ────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────
       ► f 0     5555555549fb
         f 1     555555554ac2
         f 2     7ffff7a03bf7 __libc_start_main+231
      ─────────────────────────────────────────────────────────────────────────────────────────────────────
      pwndbg> x/s $rbp-0x60
      0x7fffffffddd0:	"iQiQiQiQiQiQiQiQiQiQiQ"
      pwndbg> x/s $rbp-0x40
      0x7fffffffddf0:	"1617181926381617919194"
      • 지금 bp 걸린곳부터 밑에 cmp dl, al을 분석하면 rbp-0x40에 들어있는 인코딩 값과 내 입력값을 비교하는걸 알수 있다. 참고로 인코딩값을 리틀엔디언으로 써야함.

3. 풀이


여기까지 분석했다면 끝이다.

  • ( input + 4 ) ^ 0xc = encode

    ⇒ input = (encode ^ 0xc ) - 4

  • ( input - 4 ) ^ 0xc = encode

    ⇒ input = (encode ^ 0xc ) + 4

짝수 , 홀수 인덱스에 따라서 저렇게 xor을 역연산 한뒤, input값을 알아내면 된다.

익스코드

key='4919197161836291817161'
fuck=''
xor_=0xc
for i in range(22):
        if i&1==0:
                fuck+=chr( (ord(key[i])^xor_)-4 )
        else:
                fuck+=chr( (ord(key[i])^xor_)+4 )

print(fuck)

4. 몰랐던 개념


  • none
728x90

'워게임 > HackCTF' 카테고리의 다른 글

[HackCTF] 탈옥  (0) 2020.12.05
[HackCTF] BabyMips  (0) 2020.12.04
[HackCTF] keygen  (0) 2020.12.02
[HackCTF] strncmp  (0) 2020.12.01
[HackCTF] adultfsb  (1) 2020.04.12