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부분이다.
- 첫번째 요약 : '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가 나옴
- 두번째 요약 : 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,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에 들어있는 인코딩 값과 내 입력값을 비교하는걸 알수 있다. 참고로 인코딩값을 리틀엔디언으로 써야함.
- 여긴 그냥 직접 gdb로 보는게 더 편하다. 참고로 main에 bp 안걸리므로 getenv에 bp 걸고 finish로 나오면, main 디버깅 가능. 물론 main이라고 나오진 않지만 아이다 offset으로 확인해서 메인이라고 판단가능.
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 |
Uploaded by Notion2Tistory v1.1.0