Search

[HackCTF] static

Tags
reversing
Category
HackCTF
Create Time
2022/01/07 12:50

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 ...
WebAssembly
복사
형광펜 친 부분을 토대로 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; }
WebAssembly
복사
어셈으로 첨에 분석한게 맞다. 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; }
C
복사
위 함수의 체크 로직은 크게 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
C
복사
sVar1 변수는 argv[1] 길이다. 따라서 0x16 사이즈의 인자를 줘야함
local_70="bi0s" 이다. 따라서 각 문자의 아스키값을 더해서 local_20에 저장한다.
계산해보면 0x16e이고, 이를 0x1e로 나누면 0xc가 나옴
2.
두번째 요약 : 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;
C
복사
param_2가 내가 입력한 argv[1]이다.
반복문을 돌면서 local_24를 문자열의 인덱스로 보고 짝수, 홀수 일때에 따라서 +4 or -4를 param_2 에 더하고 local_68 배열에 저장한다.
그다음 local_68 값을 0xc와 xor한다
3.
세번째 요약 : 미리 인코딩? 되어 들어있는 값과, 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;
C
복사
여긴 그냥 직접 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:00080x7fffffffddc8 —▸ 0x7fffffffef5e ◂— 0x2f3d5f0073306962 /* 'bi0s' */ 02:0010│ rdi 0x7fffffffddd0 ◂— 'iQiQiQiQiQiQiQiQiQiQiQ' ...04:00200x7fffffffdde0 ◂— 0x516951695169 /* 'iQiQiQ' */ 05:00280x7fffffffdde8 ◂— 0x0 06:00300x7fffffffddf0 ◂— '1617181926381617919194' 07:00380x7fffffffddf8 ◂— '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"
C
복사
지금 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)
C
복사

4. 몰랐던 개념

none